import path from "node:path";
import { updateConfig } from "c12/update";
import { Command } from "commander";
import { consola } from "consola";
import kleur from "kleur";
import _ from "lodash";
import prompts from "prompts";

import { AddCommand, Component, UIConfig } from "../types";
import { compareUIConfig } from "../utils/compareUIConfig";
import { addModuleToConfig, getUIConfig } from "../utils/config";
import { DEFAULT_CONFIG, DEFAULT_CONFIG_NUXT4, PACKAGE_MANAGER_CHOICES } from "../utils/constants";
import { detectNuxtVersion } from "../utils/detectNuxtVersion";
import { fetchComponents } from "../utils/fetchComponents";
import { fileExists } from "../utils/fileExists";
import { installPackages } from "../utils/installPackages";
import { installValidator } from "../utils/installValidator";
import { logger } from "../utils/logger";
import { printFancyBoxMessage } from "../utils/printFancyBoxMessage";
import { promptUserForComponents } from "../utils/promptForComponents";
import { writeFile } from "../utils/writeFile";

let allComponents: Component[] = [];
const currentDirectory = process.cwd();

/**
 * Finds a component definition by its name (case-insensitive).
 */
const findComponent = (name: string) =>
  allComponents.find((c) => c.value.toLowerCase() === name.toLowerCase());

/**
 * Handles writing a file with overwrite checks.
 */
async function safeWriteFile(
  targetPath: string,
  content: string,
  forceOverwrite: boolean,
  promptMessage: string
) {
  const exists = await fileExists(targetPath);

  if (exists && !forceOverwrite) {
    const { value: overwrite } = await prompts({
      type: "confirm",
      name: "value",
      message: promptMessage,
      initial: false,
    });
    if (!overwrite) {
      consola.info(`Skipped: ${kleur.cyan(path.basename(targetPath))}`);
      return false;
    }
  }

  await writeFile(targetPath, content);
  return true;
}

/**
 * Writes all files in a given category (utils, composables, plugins).
 */
async function writeCategoryFiles(
  category: string,
  items: Array<{ fileName: string; fileContent: string; dirPath?: string }>,
  baseDir: string,
  forceOverwrite: boolean
) {
  for (const item of items) {
    const targetPath = path.join(currentDirectory, baseDir, item.fileName);
    await safeWriteFile(
      targetPath,
      item.fileContent,
      forceOverwrite,
      `The ${category} file ${kleur.bold(item.fileName)} already exists. Overwrite?`
    );
  }
}

/**
 * Main command logic for adding components.
 */
export const runAddCommand = async (components: string[], options: AddCommand) => {
  // Step 1 — Load and verify UI config
  let uiConfig: UIConfig;

  if (options.skipConfig) {
    const nuxtVersion = detectNuxtVersion();
    const base = nuxtVersion === 4 ? DEFAULT_CONFIG_NUXT4 : DEFAULT_CONFIG;
    let pm = options.packageManager;
    if (!pm) {
      const { packageManager } = await prompts({
        type: "select",
        name: "packageManager",
        message: "Which package manager are you using?",
        choices: PACKAGE_MANAGER_CHOICES,
      });
      pm = packageManager ?? "npm";
    }
    uiConfig = { ...base, packageManager: pm };
  } else {
    uiConfig = await getUIConfig();
    if (!(await compareUIConfig())) {
      uiConfig = await getUIConfig({ force: true });
    }
    if (_.isEmpty(uiConfig)) {
      consola.error("Config file not set. Exiting...");
      process.exit(1);
    }
  }

  // Step 2 — Fetch all available components
  allComponents = await fetchComponents();

  // Step 3 — If no components were passed, prompt user to select them
  let componentNames = components;
  if (componentNames.length === 0) {
    const response = await promptUserForComponents(options.all, allComponents);
    if (!response || response.length === 0) {
      consola.info("No components selected. Exiting...");
      process.exit(0);
    }
    componentNames = response;
  }

  // Step 4 — Validate component names
  const notFound = componentNames.filter((name) => !findComponent(name));
  if (notFound.length > 0) {
    consola.error(`Not found: ${kleur.bgRed(notFound.join(", "))}`);
  }

  // Step 5 — Collect found components and their dependencies
  const found: Component[] = componentNames
    .map((name) => findComponent(name))
    .filter(Boolean) as Component[];

  for (const comp of [...found]) {
    if (comp.components) {
      comp.components.forEach((dep) => {
        if (!found.find((c) => c.value === dep)) {
          found.push(findComponent(dep)!);
        }
      });
    }
  }

  // Step 6 — Write files for each component
  for (const component of found) {
    for (const file of component.files) {
      let dirPath = uiConfig.componentsLocation;
      let filePath = path.join(currentDirectory, dirPath, file.fileName);

      // Ask for custom location if not using default
      if (!uiConfig.useDefaultFilename) {
        const { value: newDir } = await prompts({
          type: "text",
          name: "value",
          message: `Where should we add the file ${kleur.cyan(file.fileName)}?`,
          initial: dirPath,
        });
        if (newDir) {
          dirPath = newDir;
          filePath = path.join(currentDirectory, dirPath, file.fileName);
        }
      }

      const fileWritten = await safeWriteFile(
        filePath,
        file.fileContent,
        uiConfig.force,
        `The file ${kleur.bold(file.fileName)} already exists. Overwrite?`
      );
      if (!fileWritten) continue;

      // Component-specific logic hooks
      if (component.value === "vue-sonner" || component.value === "sonner") await addSonner();
      if (component.value === "datatable") await addDataTable();

      // Write related files
      await writeCategoryFiles("utils", component.utils, uiConfig.utilsLocation, uiConfig.force);
      await writeCategoryFiles(
        "composables",
        component.composables,
        uiConfig.composablesLocation,
        uiConfig.force
      );
      await writeCategoryFiles(
        "plugins",
        component.plugins,
        uiConfig.pluginsLocation ?? "",
        uiConfig.force
      );
    }
  }

  // Step 7 — Add Nuxt modules
  await addModuleToConfig(_.uniq(found.flatMap((c) => c.nuxtModules || [])));

  // Step 8 — Install dependencies if necessary
  const deps = _.uniq(found.flatMap((c) => c.deps || []));
  const devDeps = _.uniq(found.flatMap((c) => c.devDeps || []));
  if (deps.length > 0 || devDeps.length > 0) {
    if (options.all) {
      await installPackages(uiConfig.packageManager, deps, devDeps);
    } else {
      const { confirmInstall } = await prompts({
        type: "confirm",
        name: "confirmInstall",
        message: `Install packages: ${kleur.cyan([...deps, ...devDeps].join(", "))}?`,
        initial: true,
      });
      if (confirmInstall) {
        await installPackages(uiConfig.packageManager, deps, devDeps);
      }
    }
  }

  // Step 9 — Install validator if required
  if (found.some((c) => c.askValidator)) {
    await installValidator(uiConfig.packageManager);
  }

  // Step 10 — Success message & instructions
  printFancyBoxMessage(
    "All Done!",
    `Run the ${kleur.cyan("ui-thing@latest --help")} command to learn more.\n`,
    { box: { title: "Components Added" } }
  );

  found
    .filter((c) => c.docsPath)
    .forEach((c) => consola.info(`Docs: https://uithing.com${c.docsPath}`));

  const instructions = _.compact(found.flatMap((c) => c.instructions));
  if (instructions.length > 0) {
    logger.info("Instructions:");
    instructions.forEach((i) => logger.info(`- ${i}`));
  }
};

/**
 * CLI Command Registration
 */
export const add = new Command()
  .name("add")
  .command("add")
  .description("Add a list of components to your project.")
  .option("-a --all", "Add all components to your project.", false)
  .option(
    "--skip-config",
    "Add components without a ui-thing config file. Uses auto-detected Nuxt defaults."
  )
  .option("--package-manager <pm>", "Package manager to use when --skip-config is set.")
  .argument("[componentNames...]", "Components that you want to add.")
  .action(runAddCommand);

/**
 * Component-specific setup helpers
 */
async function addSonner() {
  await updateConfig({
    configFile: "nuxt.config",
    cwd: currentDirectory,
    onUpdate(config: any) {
      config.imports ||= { imports: [] };
      if (!config.imports.imports.find((i: any) => i.from === "vue-sonner" && i.name === "toast")) {
        config.imports.imports.push({ from: "vue-sonner", name: "toast", as: "useSonner" });
      }
    },
  });
}

async function addDataTable() {
  await updateConfig({
    configFile: "nuxt.config",
    cwd: currentDirectory,
    onUpdate(cfg: any) {
      cfg.app ||= { head: { script: [] } };
      const scripts = [
        "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/pdfmake.min.js",
        "https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.12/vfs_fonts.min.js",
      ];
      scripts.forEach((src) => {
        if (!cfg.app.head.script.find((i: any) => i.src === src)) {
          cfg.app.head.script.push({ src, defer: true });
        }
      });
    },
  });
}
