import path from 'path';
import fs from 'fs';
import { sync as spawnSync } from 'cross-spawn';

import { getStorybookInfo } from '@storybook/core-common';
import { readConfig, writeConfig } from '@storybook/csf-tools';

import { commandLog } from './helpers';
import {
  JsPackageManagerFactory,
  useNpmWarning,
  type PackageManagerName,
} from './js-package-manager';

const logger = console;

const LEGACY_CONFIGS = ['addons', 'config', 'presets'];

const postinstallAddon = async (addonName: string, isOfficialAddon: boolean) => {
  let skipMsg = null;
  if (!isOfficialAddon) {
    skipMsg = 'unofficial addon';
  } else if (!fs.existsSync('.storybook')) {
    skipMsg = 'no .storybook config';
  } else {
    skipMsg = 'no codmods found';
    LEGACY_CONFIGS.forEach((config) => {
      try {
        const codemod = require.resolve(
          // @ts-expect-error (it is broken)
          `${getPackageName(addonName, isOfficialAddon)}/postinstall/${config}.js`
        );
        commandLog(`Running postinstall script for ${addonName}`)();
        let configFile = path.join('.storybook', `${config}.ts`);
        if (!fs.existsSync(configFile)) {
          configFile = path.join('.storybook', `${config}.js`);
          if (!fs.existsSync(configFile)) {
            fs.writeFileSync(configFile, '', 'utf8');
          }
        }
        spawnSync('npx', ['jscodeshift', '-t', codemod, configFile], {
          stdio: 'inherit',
          shell: true,
        });
        skipMsg = null;
      } catch (err) {
        // resolve failed, skip
      }
    });
  }

  if (skipMsg) {
    commandLog(`Skipping postinstall for ${addonName}, ${skipMsg}`)();
  }
};

const getVersionSpecifier = (addon: string) => {
  const groups = /^(...*)@(.*)$/.exec(addon);
  return groups ? [groups[1], groups[2]] : [addon, undefined];
};

/**
 * Install the given addon package and add it to main.js
 *
 * Usage:
 * - sb add @storybook/addon-docs
 * - sb add @storybook/addon-interactions@7.0.1
 *
 * If there is no version specifier and it's a storybook addon,
 * it will try to use the version specifier matching your current
 * Storybook install version.
 */
export async function add(
  addon: string,
  options: { useNpm: boolean; packageManager: PackageManagerName; skipPostinstall: boolean }
) {
  const { useNpm, packageManager: pkgMgr } = options;
  if (useNpm) {
    useNpmWarning();
  }
  const packageManager = JsPackageManagerFactory.getPackageManager({ useNpm, force: pkgMgr });
  const packageJson = packageManager.retrievePackageJson();
  const [addonName, versionSpecifier] = getVersionSpecifier(addon);

  const { mainConfig, version: storybookVersion } = getStorybookInfo(packageJson);
  if (!mainConfig) {
    logger.error('Unable to find storybook main.js config');
    return;
  }
  const main = await readConfig(mainConfig);
  const addons = main.getFieldValue(['addons']);
  if (addons && !Array.isArray(addons)) {
    logger.error('Expected addons array in main.js config');
  }

  logger.log(`Verifying ${addonName}`);
  const latestVersion = packageManager.latestVersion(addonName);
  if (!latestVersion) {
    logger.error(`Unknown addon ${addonName}`);
  }

  // add to package.json
  const isStorybookAddon = addonName.startsWith('@storybook/');
  const version = versionSpecifier || (isStorybookAddon ? storybookVersion : latestVersion);
  const addonWithVersion = `${addonName}@${version}`;
  logger.log(`Installing ${addonWithVersion}`);
  packageManager.addDependencies({ installAsDevDependencies: true }, [addonWithVersion]);

  // add to main.js
  logger.log(`Adding '${addon}' to main.js addons field.`);
  const updatedAddons = [...(addons || []), addonName];
  main.setFieldValue(['addons'], updatedAddons);
  await writeConfig(main);

  if (!options.skipPostinstall) {
    await postinstallAddon(addon, isStorybookAddon);
  }
}
