#!/usr/bin/env node

import { spawnSync } from "node:child_process";
import {
  cpSync,
  existsSync,
  mkdirSync,
  readFileSync,
  rmSync,
  symlinkSync,
  writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";

const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
const version = packageJson.version;

const usage = `Usage: lazyclaude-ai [--dry-run] <install|doctor|path|run|update|uninstall> [...args]
       lazyclaude-ai --version

Commands:
  install       Register the packaged LazyClaude plugin in Claude Code.
  doctor        Validate the installed LazyClaude plugin path.
  path          Print the installed Claude plugin path.
  run -- ...    Run Claude Code after the global plugin install.
  update        Reinstall this package version and refresh the Claude plugin registry.
  uninstall     Remove LazyClaude-managed install state.
`;

const parseArgs = (argv) => {
  const args = [...argv];
  const wantsVersion = args[0] === "--version" || args[0] === "-v";
  if (wantsVersion) return { dryRun: false, command: "version", rest: [] };
  const dryRun = args[0] === "--dry-run";
  if (dryRun) args.shift();
  return { dryRun, command: args[0], rest: args.slice(1) };
};

const lazyHome = () => resolve(process.env.LAZYCLAUDE_HOME ?? join(homedir(), ".lazyclaude"));
const claudeHome = () => resolve(process.env.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_HOME ?? join(homedir(), ".claude"));
const versionRoot = (home = lazyHome()) => join(home, "lazyclaude-ai", version);
const currentRoot = (home = lazyHome()) => join(home, "current");
const pluginPathForRoot = (installRoot) => join(installRoot, "plugins", "lazyclaude");
const claudePluginRoot = (home = claudeHome()) => join(home, "plugins", "cache", "lazyclaude-ai", "lazyclaude", version);
const installedPluginsPath = (home = claudeHome()) => join(home, "plugins", "installed_plugins.json");
const knownMarketplacesPath = (home = claudeHome()) => join(home, "plugins", "known_marketplaces.json");
const claudeSettingsPath = (home = claudeHome()) => join(home, "settings.json");
const pluginKey = "lazyclaude@lazyclaude-ai";
const marketplaceName = "lazyclaude-ai";
const intendedPluginPath = (home = claudeHome()) => claudePluginRoot(home);
const marketplaceRoot = (home = lazyHome()) => join(home, "marketplaces", marketplaceName);
const marketplacePluginPath = (home = lazyHome()) => join(marketplaceRoot(home), "plugins", "lazyclaude");
const sourcePluginPath = () => join(root, "plugins", "lazyclaude");
const lazyClaudeSettingsKey = "lazyclaude";
const knownExternalLspPlugins = {
  "typescript-lsp@claude-plugins-official": ["typescript-language-server"],
  "rust-analyzer-lsp@claude-plugins-official": ["rust-analyzer"],
  "pyright-lsp@claude-plugins-official": ["pyright-langserver", "pyright"],
  "gopls-lsp@claude-plugins-official": ["gopls"],
};
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

const printUsage = () => {
  process.stderr.write(usage);
};

const fail = (message, status = 1) => {
  process.stderr.write(`${message}\n`);
  process.exit(status);
};

const shouldAnimateInstall = () => {
  if (process.env.LAZYCLAUDE_SPINNER === "1") return true;
  if (process.env.LAZYCLAUDE_SPINNER === "0") return false;
  return Boolean(process.stdout.isTTY && !process.env.CI);
};

const createInstallProgress = () => {
  const animated = shouldAnimateInstall();
  let frameIndex = 0;
  return {
    step(label) {
      if (!animated) {
        process.stdout.write(`INSTALL_STEP: ${label}\n`);
        return;
      }
      const frame = spinnerFrames[frameIndex % spinnerFrames.length];
      frameIndex += 1;
      process.stdout.write(`\x1b[36m${frame}\x1b[0m ${label}\n`);
    },
    done(label) {
      if (!animated) {
        process.stdout.write(`INSTALL_STEP: ${label}\n`);
        return;
      }
      process.stdout.write(`\x1b[32m✔\x1b[0m ${label}\n`);
    },
  };
};

const currentExists = (home = claudeHome()) => existsSync(claudePluginRoot(home));

const readInstalledPlugins = (home = claudeHome()) => {
  const registryPath = installedPluginsPath(home);
  if (!existsSync(registryPath)) return { version: 2, plugins: {} };
  const parsed = JSON.parse(readFileSync(registryPath, "utf8"));
  return {
    ...parsed,
    version: parsed.version ?? 2,
    plugins: parsed.plugins ?? {},
  };
};

const writeInstalledPlugins = (registry, home = claudeHome()) => {
  const registryPath = installedPluginsPath(home);
  mkdirSync(dirname(registryPath), { recursive: true });
  writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
};

const readClaudeSettings = (home = claudeHome()) => {
  const settingsPath = claudeSettingsPath(home);
  if (!existsSync(settingsPath)) return {};
  return JSON.parse(readFileSync(settingsPath, "utf8"));
};

const writeClaudeSettings = (settings, home = claudeHome()) => {
  const settingsPath = claudeSettingsPath(home);
  mkdirSync(dirname(settingsPath), { recursive: true });
  writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
};

const hudCommandForPlugin = (pluginPath) => `node "${join(pluginPath, "bin", "lazyclaude-hud.js")}"`;

const readKnownMarketplaces = (home = claudeHome()) => {
  const path = knownMarketplacesPath(home);
  if (!existsSync(path)) return {};
  return JSON.parse(readFileSync(path, "utf8"));
};

const writeKnownMarketplaces = (knownMarketplaces, home = claudeHome()) => {
  const path = knownMarketplacesPath(home);
  mkdirSync(dirname(path), { recursive: true });
  writeFileSync(path, `${JSON.stringify(knownMarketplaces, null, 2)}\n`);
};

const marketplaceSource = (marketplacePath) => ({
  source: "directory",
  path: marketplacePath,
});

const writeLocalMarketplace = (legacyHome = lazyHome()) => {
  const rootPath = marketplaceRoot(legacyHome);
  const targetPlugin = marketplacePluginPath(legacyHome);
  rmSync(rootPath, { recursive: true, force: true });
  mkdirSync(dirname(targetPlugin), { recursive: true });
  cpSync(sourcePluginPath(), targetPlugin, { recursive: true });
  const marketplaceManifestPath = join(rootPath, ".claude-plugin", "marketplace.json");
  mkdirSync(dirname(marketplaceManifestPath), { recursive: true });
  writeFileSync(
    marketplaceManifestPath,
    `${JSON.stringify(
      {
        $schema: "https://anthropic.com/claude-code/marketplace.schema.json",
        name: marketplaceName,
        description: "Local LazyClaude npm-installed marketplace.",
        owner: {
          name: "LazyClaude contributors",
        },
        plugins: [
          {
            name: "lazyclaude",
            displayName: "LazyClaude",
            version,
            description: packageJson.description,
            author: {
              name: "LazyClaude contributors",
            },
            source: "./plugins/lazyclaude",
            category: "development",
            homepage: "https://github.com/wjgoarxiv/lazyclaude",
          },
        ],
      },
      null,
      2,
    )}\n`,
  );
  return rootPath;
};

const registerMarketplace = (marketplacePath, home = claudeHome()) => {
  const settings = readClaudeSettings(home);
  settings.enabledPlugins = settings.enabledPlugins ?? {};
  settings.enabledPlugins[pluginKey] = true;
  settings.extraKnownMarketplaces = settings.extraKnownMarketplaces ?? {};
  settings.extraKnownMarketplaces[marketplaceName] = {
    source: marketplaceSource(marketplacePath),
  };
  writeClaudeSettings(settings, home);

  const knownMarketplaces = readKnownMarketplaces(home);
  knownMarketplaces[marketplaceName] = {
    source: marketplaceSource(marketplacePath),
    installLocation: marketplacePath,
    lastUpdated: new Date().toISOString(),
  };
  writeKnownMarketplaces(knownMarketplaces, home);
};

const installHudStatusLine = (pluginPath, home = claudeHome()) => {
  const settings = readClaudeSettings(home);
  const command = hudCommandForPlugin(pluginPath);
  const existingLazyClaude = settings[lazyClaudeSettingsKey] ?? {};
  const now = new Date().toISOString();
  const previousStatusLine =
    existingLazyClaude.statusLineManaged === true
      ? existingLazyClaude.previousStatusLine
      : settings.statusLine;

  settings.statusLine = {
    type: "command",
    command,
  };
  settings[lazyClaudeSettingsKey] = {
    ...existingLazyClaude,
    statusLineManaged: true,
    statusLineCommand: command,
    statusLineVersion: version,
    statusLineInstalledAt: existingLazyClaude.statusLineInstalledAt ?? now,
    statusLineUpdatedAt: now,
    previousStatusLine: previousStatusLine ?? null,
  };
  writeClaudeSettings(settings, home);
};

const uninstallHudStatusLine = (home = claudeHome()) => {
  const settingsPath = claudeSettingsPath(home);
  if (!existsSync(settingsPath)) return;

  const settings = readClaudeSettings(home);
  const lazyClaude = settings[lazyClaudeSettingsKey];
  if (lazyClaude?.statusLineManaged !== true) return;

  if (settings.statusLine?.command === lazyClaude.statusLineCommand) {
    if (lazyClaude.previousStatusLine) {
      settings.statusLine = lazyClaude.previousStatusLine;
    } else {
      delete settings.statusLine;
    }
  } else if (settings.statusLine?.command) {
    process.stdout.write("HUD_WARNING: statusLine changed by user; leaving it untouched\n");
  }

  const remainingLazyClaude = { ...lazyClaude };
  for (const key of [
    "statusLineManaged",
    "statusLineCommand",
    "statusLineVersion",
    "statusLineInstalledAt",
    "statusLineUpdatedAt",
    "previousStatusLine",
  ]) {
    delete remainingLazyClaude[key];
  }

  if (Object.keys(remainingLazyClaude).length > 0) {
    settings[lazyClaudeSettingsKey] = remainingLazyClaude;
  } else {
    delete settings[lazyClaudeSettingsKey];
  }
  writeClaudeSettings(settings, home);
};

const unregisterMarketplace = (home = claudeHome()) => {
  const settingsPath = claudeSettingsPath(home);
  if (existsSync(settingsPath)) {
    const settings = readClaudeSettings(home);
    if (settings.enabledPlugins) delete settings.enabledPlugins[pluginKey];
    if (settings.extraKnownMarketplaces) delete settings.extraKnownMarketplaces[marketplaceName];
    writeClaudeSettings(settings, home);
  }

  const knownPath = knownMarketplacesPath(home);
  if (existsSync(knownPath)) {
    const knownMarketplaces = readKnownMarketplaces(home);
    delete knownMarketplaces[marketplaceName];
    writeKnownMarketplaces(knownMarketplaces, home);
  }
};

const registerClaudePlugin = (installRoot, marketplacePath, home = claudeHome()) => {
  const registry = readInstalledPlugins(home);
  const now = new Date().toISOString();
  const existing = registry.plugins[pluginKey]?.[0] ?? {};
  registry.plugins[pluginKey] = [
    {
      scope: "user",
      installPath: installRoot,
      version,
      installedAt: existing.installedAt ?? now,
      lastUpdated: now,
      installedBy: "lazyclaude-ai",
      enabled: true,
    },
  ];
  writeInstalledPlugins(registry, home);
  registerMarketplace(marketplacePath, home);
  installHudStatusLine(installRoot, home);
};

const unregisterClaudePlugin = (home = claudeHome()) => {
  const registryPath = installedPluginsPath(home);
  if (!existsSync(registryPath)) return;
  const registry = readInstalledPlugins(home);
  delete registry.plugins[pluginKey];
  writeInstalledPlugins(registry, home);
};

const requireInstalledPluginPath = (home = claudeHome()) => {
  if (!currentExists(home)) {
    fail("LazyClaude is not installed. Run `lazyclaude install` first.");
  }
  const path = intendedPluginPath(home);
  if (!existsSync(join(path, ".claude-plugin", "plugin.json"))) {
    fail("LazyClaude install is incomplete. Run `lazyclaude install` again.");
  }
  return path;
};

const commandExists = (command) => {
  const result = spawnSync(command, ["--version"], {
    encoding: "utf8",
    stdio: "ignore",
  });
  return !result.error;
};

const printLspDiagnostics = (pluginPath, registry) => {
  const lspPath = join(pluginPath, ".lsp.json");
  const config = JSON.parse(readFileSync(lspPath, "utf8"));
  const lazyClaudeServers = Object.keys(config);
  process.stdout.write(`LAZYCLAUDE_LSP_SERVERS: ${lazyClaudeServers.join(", ") || "none"}\n`);

  for (const server of lazyClaudeServers) {
    const command = config[server]?.command?.[0];
    if (command && !commandExists(command)) {
      process.stdout.write(`LAZYCLAUDE_LSP_WARNING: ${server} requires ${command}; install it or remove that server from .lsp.json\n`);
    }
  }

  const externalWarnings = [];
  for (const [key, commands] of Object.entries(knownExternalLspPlugins)) {
    if (!registry.plugins[key]?.length) continue;
    if (commands.some(commandExists)) continue;
    externalWarnings.push({ key, command: commands[0] });
    process.stdout.write(`EXTERNAL_LSP_WARNING: ${key} requires ${commands[0]}\n`);
  }

  if (externalWarnings.length > 0) {
    process.stdout.write("These warnings are not emitted by LazyClaude's .lsp.json; they come from other installed Claude Code LSP plugins.\n");
  }
};

const resetCurrentPointer = (home, target) => {
  const current = currentRoot(home);
  rmSync(current, { recursive: true, force: true });
  try {
    symlinkSync(target, current, "dir");
  } catch {
    mkdirSync(current, { recursive: true });
    cpSync(target, current, { recursive: true });
  }
};

const install = ({ dryRun }) => {
  const home = claudeHome();
  const legacyHome = lazyHome();
  const targetPlugin = claudePluginRoot(home);
  const sourcePlugin = sourcePluginPath();
  const targetMarketplace = marketplaceRoot(legacyHome);
  const progress = createInstallProgress();

  if (dryRun) {
    process.stdout.write(`DRY_RUN: install LazyClaude ${version}\n`);
    process.stdout.write(`Would copy: ${sourcePlugin} -> ${targetPlugin}\n`);
    process.stdout.write(`Would create local marketplace: ${targetMarketplace}\n`);
    process.stdout.write(`Would register Claude plugin: ${pluginKey}\n`);
    process.stdout.write(`Would set Claude statusLine HUD: ${hudCommandForPlugin(targetPlugin)}\n`);
    process.stdout.write("Launch with: claude\n");
    return;
  }

  if (!existsSync(sourcePlugin)) {
    fail(`Packaged LazyClaude plugin payload is missing: ${sourcePlugin}`);
  }

  progress.step(`Installing LazyClaude ${version}`);
  progress.step("Plugin payload: copying Claude Code plugin");
  rmSync(targetPlugin, { recursive: true, force: true });
  mkdirSync(dirname(targetPlugin), { recursive: true });
  cpSync(sourcePlugin, targetPlugin, { recursive: true });
  progress.step("Marketplace: writing local Claude marketplace");
  const localMarketplace = writeLocalMarketplace(legacyHome);
  progress.step("Claude registry: enabling plugin and HUD");
  registerClaudePlugin(targetPlugin, localMarketplace, home);

  progress.step("Compatibility cache: refreshing local current pointer");
  const legacyRoot = versionRoot(legacyHome);
  rmSync(legacyRoot, { recursive: true, force: true });
  mkdirSync(dirname(pluginPathForRoot(legacyRoot)), { recursive: true });
  cpSync(sourcePlugin, pluginPathForRoot(legacyRoot), { recursive: true });
  resetCurrentPointer(legacyHome, legacyRoot);

  progress.done("LazyClaude install complete");
  process.stdout.write(`INSTALL_PASS: LazyClaude ${version} installed\n`);
  process.stdout.write(`Claude plugin: ${pluginKey}\n`);
  process.stdout.write(`Marketplace: ${localMarketplace}\n`);
  process.stdout.write(`Plugin path: ${intendedPluginPath(home)}\n`);
  process.stdout.write("HUD: LazyClaude statusLine installed\n");
  process.stdout.write("Launch with: claude\n");
};

const doctor = ({ dryRun }) => {
  if (dryRun) {
    const pluginPath = intendedPluginPath();
    process.stdout.write("DRY_RUN: doctor LazyClaude install\n");
    process.stdout.write(`Would check plugin files under: ${pluginPath}\n`);
    process.stdout.write(`Would run: claude plugin validate ${pluginPath}\n`);
    process.stdout.write(`Would check Claude plugin registry: ${pluginKey}\n`);
    process.stdout.write(`Would check Claude statusLine HUD: ${hudCommandForPlugin(pluginPath)}\n`);
    return;
  }

  const pluginPath = requireInstalledPluginPath();
  const registry = readInstalledPlugins();
  const registryEntry = registry.plugins[pluginKey]?.[0];
  if (!registryEntry) fail(`LazyClaude is missing from Claude plugin registry: ${pluginKey}`);
  const requiredFiles = [
    ".claude-plugin/plugin.json",
    ".mcp.json",
    ".lsp.json",
    "hooks/hooks.json",
    "bin/lazyclaude-hook.js",
    "bin/lazyclaude-mcp.js",
    "skills/ulw-loop/SKILL.md",
    "skills/ulw-plan/SKILL.md",
  ];

  for (const file of requiredFiles) {
    const path = join(pluginPath, file);
    if (!existsSync(path)) fail(`LazyClaude install is missing ${file}. Run \`lazyclaude install\` again.`);
  }

  printLspDiagnostics(pluginPath, registry);

  const claude = spawnSync("claude", ["--version"], { encoding: "utf8" });
  if (claude.error) {
    process.stdout.write("CLAUDE_WARNING: claude executable not found in PATH\n");
  } else {
    process.stdout.write(`CLAUDE_VERSION: ${(claude.stdout || claude.stderr).trim()}\n`);
    const validation = spawnSync("claude", ["plugin", "validate", pluginPath], { encoding: "utf8" });
    if (validation.status !== 0) {
      if (validation.stdout) process.stderr.write(validation.stdout);
      if (validation.stderr) process.stderr.write(validation.stderr);
      fail("Claude plugin validation failed.");
    }
    process.stdout.write("CLAUDE_PLUGIN_VALIDATE_PASS\n");
    const details = spawnSync("claude", ["plugin", "details", pluginKey], { encoding: "utf8" });
    if (details.status !== 0) {
      if (details.stdout) process.stderr.write(details.stdout);
      if (details.stderr) process.stderr.write(details.stderr);
      fail("Claude plugin details failed.");
    }
    process.stdout.write("CLAUDE_PLUGIN_DETAILS_PASS\n");
  }

  process.stdout.write(`Plugin path: ${pluginPath}\n`);
  const settings = readClaudeSettings();
  if (settings.statusLine?.command === hudCommandForPlugin(pluginPath)) {
    process.stdout.write("HUD_STATUSLINE_PASS\n");
  } else {
    process.stdout.write("HUD_STATUSLINE_WARNING: LazyClaude HUD is not the active Claude statusLine\n");
  }
  process.stdout.write("Launch with: claude\n");
  process.stdout.write("DOCTOR_PASS\n");
};

const printPath = () => {
  process.stdout.write(`${requireInstalledPluginPath()}\n`);
};

const printVersion = () => {
  process.stdout.write(`${version}\n`);
};

const runClaude = ({ dryRun, rest }) => {
  const separatorIndex = rest.indexOf("--");
  const claudeArgs = separatorIndex === -1 ? rest : rest.slice(separatorIndex + 1);
  if (!dryRun) requireInstalledPluginPath();
  const command = ["claude", ...claudeArgs];

  if (dryRun) {
    process.stdout.write(command.join(" "));
    process.stdout.write("\n");
    return;
  }

  const result = spawnSync(command[0], command.slice(1), { stdio: "inherit" });
  if (result.error) fail(`failed to start claude: ${result.error.message}`);
  process.exit(result.status ?? 1);
};

const uninstall = ({ dryRun }) => {
  const home = lazyHome();
  const pluginCacheRoot = join(claudeHome(), "plugins", "cache", "lazyclaude-ai");
  if (dryRun) {
    process.stdout.write(`DRY_RUN: remove ${join(home, "lazyclaude-ai")}, ${currentRoot(home)}, ${marketplaceRoot(home)}, ${pluginCacheRoot}, ${pluginKey}, and its marketplace/settings entries\n`);
    return;
  }
  rmSync(currentRoot(home), { recursive: true, force: true });
  rmSync(join(home, "lazyclaude-ai"), { recursive: true, force: true });
  rmSync(marketplaceRoot(home), { recursive: true, force: true });
  unregisterClaudePlugin();
  unregisterMarketplace();
  uninstallHudStatusLine();
  rmSync(pluginCacheRoot, { recursive: true, force: true });
  process.stdout.write("UNINSTALL_PASS\n");
};

const main = () => {
  const parsed = parseArgs(process.argv.slice(2));
  const { command } = parsed;

  if (!command) {
    printUsage();
    process.exit(64);
  }

  switch (command) {
    case "install":
    case "update":
      install(parsed);
      break;
    case "doctor":
      doctor(parsed);
      break;
    case "path":
      printPath(parsed);
      break;
    case "version":
      printVersion();
      break;
    case "run":
      runClaude(parsed);
      break;
    case "uninstall":
      uninstall(parsed);
      break;
    default:
      printUsage();
      fail(`Unknown command: ${command}`, 64);
  }
};

main();
