#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

const webRelativePath = path.join("web", "hwlab-cloud-web");
const sharedWebDepsName = "hwlab-cloud-web-deps";
const requiredWebPackages = ["vite", "vue", "@vitejs/plugin-vue", "vue-tsc", "vitest", "@playwright/test"];
const requiredWebBins = [path.join(".bin", "vite"), path.join(".bin", "vue-tsc"), path.join(".bin", "playwright")];
const allowedArgs = new Set(["--web", "--json", "--quiet", "--help"]);

const args = process.argv.slice(2);
const argSet = new Set(args);

try {
  for (const arg of args) {
    if (!allowedArgs.has(arg)) fail(`Unknown argument: ${arg}`);
  }

  if (argSet.has("--help")) {
    process.stdout.write(`Usage: node scripts/worktree-deps.mjs [--web] [--json] [--quiet]\n\nPrepares shared, ignored dependencies for HWLAB git worktrees.\n`);
    process.exit(0);
  }

  const repoRoot = findRepoRoot(process.cwd());
  const result = ensureWebDeps(repoRoot);
  writeResult(result);
} catch (error) {
  const message = error instanceof Error ? error.message : String(error);
  if (argSet.has("--json")) process.stdout.write(`${JSON.stringify({ ok: false, error: message })}\n`);
  else process.stderr.write(`hwlab worktree deps: ${message}\n`);
  process.exit(1);
}

function ensureWebDeps(repoRoot) {
  const webRoot = path.join(repoRoot, webRelativePath);
  const packagePath = path.join(webRoot, "package.json");
  const lockPath = path.join(webRoot, "bun.lock");
  if (!fs.existsSync(packagePath)) fail(`Missing ${path.relative(repoRoot, packagePath)}`);
  if (!fs.existsSync(lockPath)) fail(`Missing ${path.relative(repoRoot, lockPath)}`);

  const fixedRoot = fixedRepoRoot(repoRoot);
  const sharedRoot = path.join(fixedRoot, ".worktree", sharedWebDepsName);
  const sharedNodeModules = path.join(sharedRoot, "node_modules");
  const nodeModulesPath = path.join(webRoot, "node_modules");
  const existing = tryLstat(nodeModulesPath);

  if (hasRequiredWebDeps(nodeModulesPath)) {
    return {
      ok: true,
      mode: existing?.isSymbolicLink() ? "shared-symlink" : "local-existing",
      repoRoot,
      fixedRoot,
      webRoot,
      sharedRoot,
      nodeModulesPath,
      installRan: false,
      linked: existing?.isSymbolicLink() ?? false
    };
  }

  if (existing && !existing.isSymbolicLink()) {
    if (!existing.isDirectory()) fail(`${path.relative(repoRoot, nodeModulesPath)} exists but is not a directory or symlink`);
    runBunInstall(webRoot);
    if (!hasRequiredWebDeps(nodeModulesPath)) fail("Local web dependency install completed but required packages are still missing");
    return {
      ok: true,
      mode: "local-install",
      repoRoot,
      fixedRoot,
      webRoot,
      sharedRoot,
      nodeModulesPath,
      installRan: true,
      linked: false
    };
  }

  const shared = ensureSharedWebDeps(webRoot, sharedRoot, sharedNodeModules);
  linkNodeModules(nodeModulesPath, sharedNodeModules);
  if (!hasRequiredWebDeps(nodeModulesPath)) fail("Shared web dependency link completed but required packages are still missing");

  return {
    ok: true,
    mode: "shared-symlink",
    repoRoot,
    fixedRoot,
    webRoot,
    sharedRoot,
    nodeModulesPath,
    installRan: shared.installRan,
    linked: true
  };
}

function ensureSharedWebDeps(webRoot, sharedRoot, sharedNodeModules) {
  fs.mkdirSync(sharedRoot, { recursive: true });
  const packageChanged = copyIfDifferent(path.join(webRoot, "package.json"), path.join(sharedRoot, "package.json"));
  const lockChanged = copyIfDifferent(path.join(webRoot, "bun.lock"), path.join(sharedRoot, "bun.lock"));
  const needsInstall = packageChanged || lockChanged || !hasRequiredWebDeps(sharedNodeModules);
  if (needsInstall) runBunInstall(sharedRoot);
  if (!hasRequiredWebDeps(sharedNodeModules)) fail(`Shared dependency install is incomplete at ${sharedNodeModules}`);
  return { installRan: needsInstall };
}

function linkNodeModules(nodeModulesPath, sharedNodeModules) {
  const existing = tryLstat(nodeModulesPath);
  if (existing?.isSymbolicLink()) fs.unlinkSync(nodeModulesPath);
  else if (existing) fail(`${nodeModulesPath} already exists and is not a symlink`);
  const relativeTarget = path.relative(path.dirname(nodeModulesPath), sharedNodeModules) || sharedNodeModules;
  fs.symlinkSync(relativeTarget, nodeModulesPath, "dir");
}

function runBunInstall(cwd) {
  const result = spawnSync("bun", ["install", "--frozen-lockfile"], { cwd, encoding: "utf8", stdio: ["ignore", "inherit", "inherit"] });
  if (result.status !== 0) fail(`bun install --frozen-lockfile failed in ${cwd} with exit ${result.status ?? "unknown"}`);
}

function hasRequiredWebDeps(nodeModulesPath) {
  return requiredWebPackages.every((name) => fs.existsSync(path.join(nodeModulesPath, name))) && requiredWebBins.every((name) => fs.existsSync(path.join(nodeModulesPath, name)));
}

function copyIfDifferent(source, target) {
  if (fs.existsSync(target) && fs.readFileSync(source).equals(fs.readFileSync(target))) return false;
  fs.mkdirSync(path.dirname(target), { recursive: true });
  fs.copyFileSync(source, target);
  return true;
}

function findRepoRoot(startDir) {
  const gitResult = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd: startDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
  if (gitResult.status === 0 && gitResult.stdout.trim()) return path.resolve(gitResult.stdout.trim());

  let current = path.resolve(startDir);
  while (true) {
    if (fs.existsSync(path.join(current, "package.json")) && fs.existsSync(path.join(current, webRelativePath, "package.json"))) return current;
    const parent = path.dirname(current);
    if (parent === current) break;
    current = parent;
  }
  fail(`Unable to find HWLAB repo root from ${startDir}`);
}

function fixedRepoRoot(repoRoot) {
  const marker = `${path.sep}.worktree${path.sep}`;
  const index = repoRoot.indexOf(marker);
  return index >= 0 ? repoRoot.slice(0, index) : repoRoot;
}

function tryLstat(filePath) {
  try {
    return fs.lstatSync(filePath);
  } catch (error) {
    if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return null;
    throw error;
  }
}

function writeResult(result) {
  if (argSet.has("--json")) {
    process.stdout.write(`${JSON.stringify(result)}\n`);
    return;
  }
  if (argSet.has("--quiet")) return;
  const install = result.installRan ? "install=ran" : "install=skipped";
  const link = result.linked ? `node_modules -> ${path.relative(result.webRoot, path.join(result.sharedRoot, "node_modules"))}` : "node_modules=local";
  process.stdout.write(`hwlab worktree deps: web ready (${result.mode}, ${install}, ${link})\n`);
}

function fail(message) {
  throw new Error(message);
}
