#!/usr/bin/env node

import { spawnSync } from "node:child_process";
import {
  copyFile,
  mkdir,
  mkdtemp,
  readFile,
  rm,
  writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const KIT_VERSION = "1.0.0";
const kitRoot = dirname(fileURLToPath(import.meta.url));
const packageRoot = join(kitRoot, "packages");
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
const valueOptions = new Set([
  "--name",
  "-n",
  "--template",
  "-t",
  "--plugin-id",
  "--plugin-name",
  "--api",
  "--kit",
  "--features",
  "--description",
  "--author",
]);
const packageArchives = Object.freeze({
  "@thortiq/plugin-api": `thortiq-plugin-api-${KIT_VERSION}.tgz`,
  "@thortiq/plugin-sdk": `thortiq-plugin-sdk-${KIT_VERSION}.tgz`,
  "@thortiq/plugin-react": `thortiq-plugin-react-${KIT_VERSION}.tgz`,
  "@thortiq/plugin-cli": `thortiq-plugin-cli-${KIT_VERSION}.tgz`,
  "create-thortiq-plugin": `create-thortiq-plugin-${KIT_VERSION}.tgz`,
});

const help = `Thortiq plugin developer-kit bootstrap

Usage:
  node bootstrap.mjs [--skip-install] --name <name> --template <template> <destination>

Templates:
  vanilla | react | sandbox | live

Examples:
  node bootstrap.mjs --name my-plugin --template vanilla ./my-plugin
  node bootstrap.mjs --skip-install --name my-plugin --template sandbox ./my-plugin

All create-thortiq-plugin options are forwarded. The bootstrap runs the bundled
scaffolder, copies the exact Thortiq package tarballs into vendor/thortiq/,
rewrites the project to use those local packages, and installs third-party npm
dependencies unless --skip-install is supplied. It never writes inside a
Thortiq vault.`;

function fail(message) {
  process.stderr.write(`Error: ${message}\n\n${help}\n`);
  process.exitCode = 1;
}

function run(command, args, options = {}) {
  const result = spawnSync(command, args, {
    cwd: options.cwd,
    env: options.env ?? process.env,
    stdio: "inherit",
  });
  if (result.error) throw result.error;
  if (result.status !== 0) {
    throw new Error(`${command} exited with status ${result.status ?? "unknown"}.`);
  }
}

function parseArguments(argv) {
  const scaffold = [];
  const destinations = [];
  let skipInstall = false;
  for (let index = 0; index < argv.length; index += 1) {
    const token = argv[index];
    if (token === "--help" || token === "-h") return { showHelp: true };
    if (token === "--skip-install") {
      skipInstall = true;
      continue;
    }
    scaffold.push(token);
    if (valueOptions.has(token)) {
      const value = argv[++index];
      if (value === undefined) throw new Error(`Missing value for ${token}.`);
      scaffold.push(value);
      continue;
    }
    if (!token.startsWith("-")) destinations.push(token);
  }
  if (destinations.length !== 1) {
    throw new Error("Provide exactly one destination directory.");
  }
  return {
    showHelp: false,
    skipInstall,
    scaffold,
    destination: resolve(process.cwd(), destinations[0]),
  };
}

async function installBundledScaffolder(temporary) {
  const packageJson = {
    name: "thortiq-plugin-kit-bootstrap",
    private: true,
    dependencies: {
      "create-thortiq-plugin":
        `file:${join(packageRoot, packageArchives["create-thortiq-plugin"])}`,
    },
  };
  await writeFile(
    join(temporary, "package.json"),
    `${JSON.stringify(packageJson, null, 2)}\n`,
  );
  run(npmCommand, [
    "install",
    "--offline",
    "--ignore-scripts",
    "--no-audit",
    "--no-fund",
    "--package-lock=false",
  ], {
    cwd: temporary,
    env: {
      ...process.env,
      npm_config_update_notifier: "false",
    },
  });
  return join(
    temporary,
    "node_modules",
    "create-thortiq-plugin",
    "dist",
    "cli.js",
  );
}

async function useBundledPackages(destination) {
  const packagePath = join(destination, "package.json");
  const packageJson = JSON.parse(await readFile(packagePath, "utf8"));
  const vendorRelative = "vendor/thortiq";
  const vendor = join(destination, "vendor", "thortiq");
  await mkdir(vendor, { recursive: true });

  const required = new Set(["@thortiq/plugin-cli"]);
  for (const sectionName of ["dependencies", "devDependencies"]) {
    for (const name of Object.keys(packageJson[sectionName] ?? {})) {
      if (name.startsWith("@thortiq/")) required.add(name);
    }
  }

  for (const name of required) {
    const archive = packageArchives[name];
    if (!archive) throw new Error(`The kit has no package mapping for ${name}.`);
    await copyFile(join(packageRoot, archive), join(vendor, archive));
    const localReference = `file:${vendorRelative}/${archive}`;
    let replaced = false;
    for (const sectionName of ["dependencies", "devDependencies"]) {
      if (Object.hasOwn(packageJson[sectionName] ?? {}, name)) {
        packageJson[sectionName][name] = localReference;
        replaced = true;
      }
    }
    if (!replaced) {
      packageJson.devDependencies = {
        ...(packageJson.devDependencies ?? {}),
        [name]: localReference,
      };
    }
  }

  packageJson.devDependencies = Object.fromEntries(
    Object.entries(packageJson.devDependencies ?? {})
      .sort(([left], [right]) => left.localeCompare(right)),
  );
  await writeFile(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
  await rm(join(destination, "package-lock.json"), { force: true });
}

async function main() {
  if (Number(process.versions.node.split(".")[0]) < 20) {
    throw new Error("Node.js 20 or newer is required.");
  }
  const parsed = parseArguments(process.argv.slice(2));
  if (parsed.showHelp) {
    process.stdout.write(`${help}\n`);
    return;
  }

  const temporary = await mkdtemp(join(tmpdir(), "thortiq-plugin-bootstrap-"));
  try {
    const cli = await installBundledScaffolder(temporary);
    run(process.execPath, [cli, ...parsed.scaffold], { cwd: process.cwd() });
    await useBundledPackages(parsed.destination);
  } finally {
    await rm(temporary, { recursive: true, force: true });
  }

  if (parsed.skipInstall) {
    process.stdout.write(
      `\nPrepared ${parsed.destination} with bundled Thortiq packages.\n`
      + `Next: cd ${JSON.stringify(parsed.destination)} && npm install\n`,
    );
    return;
  }

  try {
    run(npmCommand, [
      "install",
      "--no-audit",
      "--no-fund",
      "--legacy-peer-deps",
    ], {
      cwd: parsed.destination,
      env: {
        ...process.env,
        npm_config_update_notifier: "false",
      },
    });
  } catch (error) {
    process.stderr.write(
      `\nThe project was created at ${parsed.destination}, but dependency installation failed.\n`
      + "Check your npm/network configuration, then run npm install in that directory.\n",
    );
    throw error;
  }
  process.stdout.write(
    `\nReady: ${parsed.destination}\nRead AGENTS.md and PLUGIN_TASK.md, then run the generated checks.\n`,
  );
}

main().catch(error => {
  fail(error instanceof Error ? error.message : String(error));
});
