#!/usr/bin/env node

/**
 * code-graph-builder MCP server launcher & setup wizard
 *
 * Usage:
 *   npx code-graph-builder              # interactive setup (first run)
 *   npx code-graph-builder --server     # start MCP server (used by MCP clients)
 *   npx code-graph-builder --setup      # re-run setup wizard
 *   npx code-graph-builder --pip        # force python3 direct mode
 */

import { spawn, execFileSync, execSync } from "node:child_process";
import { createInterface } from "node:readline";
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync } from "node:fs";
import { homedir, platform } from "node:os";
import { join } from "node:path";

const PYTHON_PACKAGE = "code-graph-builder";
const MODULE_PATH = "code_graph_builder.mcp.server";
const WORKSPACE_DIR = join(homedir(), ".code-graph-builder");
const ENV_FILE = join(WORKSPACE_DIR, ".env");
const IS_WIN = platform() === "win32";

// ---------------------------------------------------------------------------
// Tree-style UI helpers
// ---------------------------------------------------------------------------

const T = {
  // Box drawing
  TOP:    "╭",
  BOT:    "╰",
  SIDE:   "│",
  TEE:    "├",
  BEND:   "╰",
  DASH:   "─",
  // Status
  OK:     "✓",
  FAIL:   "✗",
  WARN:   "⚠",
  WORK:   "…",
  DOT:    "●",
  // Indents
  PIPE:   "│  ",
  SPACE:  "   ",
  BRANCH: "├─ ",
  LAST:   "╰─ ",
};

/**
 * Interactive single-select menu.
 * Arrow keys to navigate, Space to select, Enter to confirm.
 * Returns the index of the selected option, or -1 if cancelled (Ctrl+C).
 *
 * @param {string[]} options - Display labels for each option
 * @param {string} prefix - Tree prefix for each line (e.g. "  │  ")
 * @param {number} defaultIndex - Initially highlighted index
 * @returns {Promise<number>}
 */
function selectMenu(options, prefix = "  ", defaultIndex = 0) {
  return new Promise((resolve) => {
    const out = process.stderr;
    let cursor = defaultIndex;
    let selected = -1;

    const RADIO_ON  = "◉";
    const RADIO_OFF = "○";
    const DIM   = "\x1b[2m";
    const BOLD  = "\x1b[1m";
    const CYAN  = "\x1b[36m";
    const RESET = "\x1b[0m";

    function render(initial = false) {
      // Move cursor up to overwrite previous render (skip on first draw)
      if (!initial) {
        out.write(`\x1b[${options.length}A`);
      }
      for (let i = 0; i < options.length; i++) {
        const isActive = i === cursor;
        const isSelected = i === selected;
        const radio = (isSelected || (selected === -1 && isActive)) && isActive
          ? `${CYAN}${RADIO_ON}${RESET}`
          : `${DIM}${RADIO_OFF}${RESET}`;
        const label = isActive
          ? `${BOLD}${CYAN}${options[i]}${RESET}`
          : `${options[i]}`;
        // Clear line then write
        out.write(`\x1b[2K${prefix}${radio} ${label}\n`);
      }
    }

    // Hide cursor
    out.write("\x1b[?25l");
    render(true);

    const stdin = process.stdin;
    const wasRaw = stdin.isRaw;
    stdin.setRawMode(true);
    stdin.resume();

    function cleanup() {
      stdin.setRawMode(wasRaw || false);
      stdin.removeListener("data", onKey);
      // Show cursor
      out.write("\x1b[?25h");
    }

    function onKey(buf) {
      const key = buf.toString();

      // Ctrl+C
      if (key === "\x03") {
        cleanup();
        resolve(-1);
        return;
      }

      // Arrow up / k
      if (key === "\x1b[A" || key === "k") {
        cursor = (cursor - 1 + options.length) % options.length;
        render();
        return;
      }

      // Arrow down / j
      if (key === "\x1b[B" || key === "j") {
        cursor = (cursor + 1) % options.length;
        render();
        return;
      }

      // Space — toggle selection
      if (key === " ") {
        selected = cursor;
        render();
        return;
      }

      // Enter — confirm
      if (key === "\r" || key === "\n") {
        if (selected === -1) selected = cursor;
        cleanup();
        resolve(selected);
        return;
      }
    }

    stdin.on("data", onKey);
  });
}

function box(title) {
  const pad = 54;
  const inner = `  ${title}  `;
  const fill = pad - inner.length;
  const left = Math.floor(fill / 2);
  const right = fill - left;
  return [
    `  ${T.TOP}${"─".repeat(pad)}╮`,
    `  ${T.SIDE}${" ".repeat(left)}${inner}${" ".repeat(right)}${T.SIDE}`,
    `  ${T.BOT}${"─".repeat(pad)}╯`,
  ].join("\n");
}

// ---------------------------------------------------------------------------
// Utilities
// ---------------------------------------------------------------------------

function commandExists(cmd) {
  try {
    const checker = IS_WIN ? "where" : "which";
    execFileSync(checker, [cmd], { stdio: "pipe" });
    return true;
  } catch {
    return false;
  }
}

function findPython() {
  const candidates = IS_WIN
    ? ["python", "python3", "py"]
    : ["python3", "python"];
  for (const cmd of candidates) {
    try {
      const ver = execFileSync(cmd, ["--version"], { stdio: "pipe" }).toString().trim();
      if (ver.includes("3.")) return { cmd, ver };
    } catch { /* skip */ }
  }
  return null;
}

const pythonInfo = findPython();
const PYTHON_CMD = pythonInfo?.cmd || null;
const PYTHON_VER = pythonInfo?.ver || null;

function pythonPackageInstalled() {
  if (!PYTHON_CMD) return false;
  try {
    execFileSync(PYTHON_CMD, ["-c", `import ${MODULE_PATH.split(".")[0]}`], {
      stdio: "pipe",
    });
    return true;
  } catch {
    return false;
  }
}

function getPackageVersion() {
  if (!PYTHON_CMD) return null;
  try {
    return execFileSync(PYTHON_CMD, ["-c",
      `import code_graph_builder; print(getattr(code_graph_builder, '__version__', 'unknown'))`
    ], { stdio: "pipe" }).toString().trim();
  } catch {
    return null;
  }
}

function loadEnvFile() {
  if (!existsSync(ENV_FILE)) return {};
  const vars = {};
  for (const line of readFileSync(ENV_FILE, "utf-8").split("\n")) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) continue;
    const eq = trimmed.indexOf("=");
    if (eq === -1) continue;
    const key = trimmed.slice(0, eq).trim();
    let val = trimmed.slice(eq + 1).trim();
    if ((val.startsWith('"') && val.endsWith('"')) ||
        (val.startsWith("'") && val.endsWith("'"))) {
      val = val.slice(1, -1);
    }
    vars[key] = val;
  }
  return vars;
}

function saveEnvFile(vars) {
  mkdirSync(WORKSPACE_DIR, { recursive: true });
  const lines = [
    "# code-graph-builder configuration",
    "# Generated by setup wizard. Edit freely.",
    "",
  ];
  for (const [key, val] of Object.entries(vars)) {
    if (val) lines.push(`${key}=${val}`);
  }
  lines.push("");
  writeFileSync(ENV_FILE, lines.join("\n"), "utf-8");
}

function mask(s) {
  if (!s || s.length < 8) return s ? "****" : "(not set)";
  return s.slice(0, 4) + "****" + s.slice(-4);
}

function findPip() {
  for (const cmd of IS_WIN ? ["pip", "pip3"] : ["pip3", "pip"]) {
    if (commandExists(cmd)) return [cmd];
  }
  if (PYTHON_CMD) {
    try {
      execFileSync(PYTHON_CMD, ["-m", "pip", "--version"], { stdio: "pipe" });
      return [PYTHON_CMD, "-m", "pip"];
    } catch { /* skip */ }
  }
  return null;
}

/**
 * Clear npx cache for code-graph-builder to ensure latest version.
 */
function clearNpxCache() {
  try {
    const cacheDir = execSync("npm config get cache", { stdio: "pipe", shell: true })
      .toString().trim();
    const npxCacheDir = join(cacheDir, "_npx");

    if (existsSync(npxCacheDir)) {
      for (const entry of readdirSync(npxCacheDir)) {
        const pkgJsonPath = join(npxCacheDir, entry, "node_modules", "code-graph-builder", "package.json");
        const altPkgJson = join(npxCacheDir, entry, "package.json");
        try {
          let found = false;
          if (existsSync(pkgJsonPath)) {
            found = true;
          } else if (existsSync(altPkgJson)) {
            const content = readFileSync(altPkgJson, "utf-8");
            if (content.includes("code-graph-builder")) found = true;
          }
          if (found) {
            rmSync(join(npxCacheDir, entry), { recursive: true, force: true });
          }
        } catch { /* skip */ }
      }
    }
  } catch { /* cache clear is best-effort */ }
}

// ---------------------------------------------------------------------------
// Interactive setup wizard
// ---------------------------------------------------------------------------

async function runSetup() {
  let rl = createInterface({
    input: process.stdin,
    output: process.stderr,
  });

  let ask = (q) => new Promise((resolve) => rl.question(q, resolve));
  const log = (msg = "") => process.stderr.write(msg + "\n");

  log();
  log(box("code-graph-builder  Setup Wizard"));
  log();

  // --- Step 0: Clear npx cache ---
  log(`  ${T.DOT} Preparing`);
  log(`  ${T.SIDE}`);
  log(`  ${T.BRANCH} Clearing npx cache...`);

  await clearNpxCache();

  log(`  ${T.LAST} ${T.OK} Cache cleared`);
  log();

  // Load existing config
  const existing = loadEnvFile();

  // --- Step 1: Workspace ---
  log(`  ${T.DOT} Step 1/3  Workspace`);
  log(`  ${T.SIDE}`);
  log(`  ${T.BRANCH} Stores indexed repos, graphs, and embeddings`);

  const workspace =
    (await ask(`  ${T.SIDE}  Path [${WORKSPACE_DIR}]: `)).trim() || WORKSPACE_DIR;

  log(`  ${T.LAST} ${T.OK} ${workspace}`);
  log();

  // --- Step 2: LLM Provider ---
  log(`  ${T.DOT} Step 2/3  LLM Provider`);
  log(`  ${T.SIDE}`);
  log(`  ${T.BRANCH} For natural language queries & descriptions`);
  log(`  ${T.SIDE}  Use ↑↓ to navigate, Space to select, Enter to confirm`);
  log(`  ${T.SIDE}`);

  if (existing.LLM_API_KEY) {
    log(`  ${T.SIDE}  Current: ${mask(existing.LLM_API_KEY)} → ${existing.LLM_BASE_URL || "?"}`);
    log(`  ${T.SIDE}`);
  }

  const llmOptions = [
    "Moonshot / Kimi      platform.moonshot.cn",
    "OpenAI               platform.openai.com",
    "DeepSeek             platform.deepseek.com",
    "OpenRouter           openrouter.ai",
    "LiteLLM Proxy        localhost:4000",
    "Custom endpoint",
    "Skip (configure later)",
  ];

  const llmProviders = [
    { name: "Moonshot",   url: "https://api.moonshot.cn/v1",   model: "kimi-k2.5" },
    { name: "OpenAI",     url: "https://api.openai.com/v1",    model: "gpt-4o" },
    { name: "DeepSeek",   url: "https://api.deepseek.com/v1",  model: "deepseek-chat" },
    { name: "OpenRouter",  url: "https://openrouter.ai/api/v1", model: "anthropic/claude-sonnet-4" },
    { name: "LiteLLM",    url: "http://localhost:4000/v1",     model: "gpt-4o" },
  ];

  // Close readline before raw mode menu, reopen after
  rl.close();
  const llmChoice = await selectMenu(llmOptions, `  ${T.SIDE}  `, 6);
  rl = createInterface({ input: process.stdin, output: process.stderr });
  ask = (q) => new Promise((resolve) => rl.question(q, resolve));

  let llmKey = existing.LLM_API_KEY || "";
  let llmBaseUrl = existing.LLM_BASE_URL || "";
  let llmModel = existing.LLM_MODEL || "";
  let llmProviderName = "skipped";

  if (llmChoice >= 0 && llmChoice < 5) {
    // Known provider
    const provider = llmProviders[llmChoice];
    llmBaseUrl = provider.url;
    llmModel = provider.model;
    llmProviderName = provider.name;

    log(`  ${T.SIDE}`);
    llmKey = (await ask(`  ${T.SIDE}  API Key (sk-...): `)).trim() || existing.LLM_API_KEY || "";

    if (llmKey) {
      const urlOverride = (await ask(`  ${T.SIDE}  Base URL [${llmBaseUrl}]: `)).trim();
      if (urlOverride) llmBaseUrl = urlOverride;
      const modelOverride = (await ask(`  ${T.SIDE}  Model [${llmModel}]: `)).trim();
      if (modelOverride) llmModel = modelOverride;
    }
  } else if (llmChoice === 5) {
    // Custom
    llmProviderName = "Custom";
    const defUrl = llmBaseUrl || existing.LLM_BASE_URL || "";
    const defModel = llmModel || existing.LLM_MODEL || "gpt-4o";
    const defKey = existing.LLM_API_KEY || "";
    log(`  ${T.SIDE}`);
    llmBaseUrl = (await ask(`  ${T.SIDE}  API Base URL${defUrl ? ` [${defUrl}]` : ""}: `)).trim() || defUrl;
    llmModel = (await ask(`  ${T.SIDE}  Model${defModel ? ` [${defModel}]` : ""}: `)).trim() || defModel;
    llmKey = (await ask(`  ${T.SIDE}  API Key${defKey ? ` [${mask(defKey)}]` : " (sk-...)"}: `)).trim() || defKey;
  }
  // llmChoice === 6 or -1 → skip

  if (llmKey) {
    log(`  ${T.LAST} ${T.OK} ${llmProviderName} / ${llmModel}`);
  } else {
    log(`  ${T.LAST} ${T.WARN} Skipped (configure later in ${ENV_FILE})`);
  }
  log();

  // --- Step 3: Embedding Provider ---
  log(`  ${T.DOT} Step 3/3  Embedding Provider`);
  log(`  ${T.SIDE}`);
  log(`  ${T.BRANCH} For semantic code search`);
  log(`  ${T.SIDE}  Use ↑↓ to navigate, Space to select, Enter to confirm`);
  log(`  ${T.SIDE}`);

  if (existing.DASHSCOPE_API_KEY || existing.EMBED_API_KEY) {
    const ek = existing.DASHSCOPE_API_KEY || existing.EMBED_API_KEY;
    log(`  ${T.SIDE}  Current: ${mask(ek)} → ${existing.DASHSCOPE_BASE_URL || existing.EMBED_BASE_URL || "?"}`);
    log(`  ${T.SIDE}`);
  }

  const embedOptions = [
    "DashScope / Qwen     dashscope.console.aliyun.com  (free tier)",
    "OpenAI Embeddings    platform.openai.com",
    "Custom endpoint",
    "Skip (configure later)",
  ];

  const embedProvidersList = [
    { name: "DashScope", url: "https://dashscope.aliyuncs.com/api/v1", model: "text-embedding-v4", keyEnv: "DASHSCOPE_API_KEY", urlEnv: "DASHSCOPE_BASE_URL" },
    { name: "OpenAI",    url: "https://api.openai.com/v1",             model: "text-embedding-3-small", keyEnv: "OPENAI_API_KEY", urlEnv: "OPENAI_BASE_URL" },
  ];

  rl.close();
  const embedChoice = await selectMenu(embedOptions, `  ${T.SIDE}  `, 3);
  rl = createInterface({ input: process.stdin, output: process.stderr });
  ask = (q) => new Promise((resolve) => rl.question(q, resolve));

  let embedKey = "";
  let embedUrl = "";
  let embedModel = "";
  let embedKeyEnv = "DASHSCOPE_API_KEY";
  let embedUrlEnv = "DASHSCOPE_BASE_URL";
  let embedProviderName = "skipped";

  if (embedChoice >= 0 && embedChoice < 2) {
    // Known provider
    const ep = embedProvidersList[embedChoice];
    embedUrl = ep.url;
    embedModel = ep.model;
    embedKeyEnv = ep.keyEnv;
    embedUrlEnv = ep.urlEnv;
    embedProviderName = ep.name;

    log(`  ${T.SIDE}`);
    embedKey = (await ask(`  ${T.SIDE}  API Key: `)).trim() ||
      existing[embedKeyEnv] || existing.DASHSCOPE_API_KEY || "";

    if (embedKey) {
      const urlOverride = (await ask(`  ${T.SIDE}  Base URL [${embedUrl}]: `)).trim();
      if (urlOverride) embedUrl = urlOverride;
      const modelOverride = (await ask(`  ${T.SIDE}  Model [${embedModel}]: `)).trim();
      if (modelOverride) embedModel = modelOverride;
    }
  } else if (embedChoice === 2) {
    // Custom
    embedProviderName = "Custom";
    const defEmbedUrl = existing.EMBED_BASE_URL || existing.DASHSCOPE_BASE_URL || "";
    const defEmbedModel = existing.EMBED_MODEL || "text-embedding-3-small";
    const defEmbedKey = existing.EMBED_API_KEY || existing.DASHSCOPE_API_KEY || "";
    log(`  ${T.SIDE}`);
    embedUrl = (await ask(`  ${T.SIDE}  API Base URL${defEmbedUrl ? ` [${defEmbedUrl}]` : ""}: `)).trim() || defEmbedUrl;
    embedModel = (await ask(`  ${T.SIDE}  Model${defEmbedModel ? ` [${defEmbedModel}]` : ""}: `)).trim() || defEmbedModel;
    embedKey = (await ask(`  ${T.SIDE}  API Key${defEmbedKey ? ` [${mask(defEmbedKey)}]` : ""}: `)).trim() || defEmbedKey;
    embedKeyEnv = "EMBED_API_KEY";
    embedUrlEnv = "EMBED_BASE_URL";
  }
  // embedChoice === 3 or -1 → skip

  if (embedKey) {
    log(`  ${T.LAST} ${T.OK} ${embedProviderName} / ${embedModel}`);
  } else {
    log(`  ${T.LAST} ${T.WARN} Skipped (configure later in ${ENV_FILE})`);
  }

  rl.close();

  // --- Save config ---
  const config = {
    CGB_WORKSPACE: workspace,
    LLM_API_KEY: llmKey,
    LLM_BASE_URL: llmBaseUrl,
    LLM_MODEL: llmModel,
  };

  if (embedKey) {
    config[embedKeyEnv] = embedKey;
    config[embedUrlEnv] = embedUrl;
    if (embedModel) config.EMBED_MODEL = embedModel;
  }

  saveEnvFile(config);

  log();
  log(`  ${T.DOT} Configuration saved`);
  log(`  ${T.SIDE}`);
  log(`  ${T.BRANCH} File:      ${ENV_FILE}`);
  log(`  ${T.BRANCH} LLM:       ${llmKey ? `${llmProviderName} / ${llmModel}` : "not configured"}`);
  log(`  ${T.BRANCH} Embedding: ${embedKey ? `${embedProviderName} / ${embedModel}` : "not configured"}`);
  log(`  ${T.LAST} Workspace: ${workspace}`);
  log();

  // --- Verification ---
  log(`  ${T.DOT} Verification`);
  log(`  ${T.SIDE}`);

  // 1. Python
  if (!PYTHON_CMD) {
    log(`  ${T.BRANCH} ${T.FAIL} Python 3 not found`);
    log(`  ${T.LAST}   Install Python 3.10+ and re-run: npx code-graph-builder@latest --setup`);
    log();
    return;
  }
  log(`  ${T.BRANCH} ${T.OK} ${PYTHON_VER}`);

  // 2. Package — auto-install or upgrade
  const pip = findPip();
  if (!pythonPackageInstalled()) {
    log(`  ${T.SIDE}  ${T.WORK} Installing ${PYTHON_PACKAGE}...`);
    if (pip) {
      try {
        execSync(
          [...pip, "install", "--upgrade", PYTHON_PACKAGE].map(s => `"${s}"`).join(" "),
          { stdio: "pipe", shell: true }
        );
      } catch { /* handled below */ }
    }
  } else {
    // Already installed — upgrade to latest
    log(`  ${T.SIDE}  ${T.WORK} Upgrading ${PYTHON_PACKAGE} to latest...`);
    if (pip) {
      try {
        execSync(
          [...pip, "install", "--upgrade", PYTHON_PACKAGE].map(s => `"${s}"`).join(" "),
          { stdio: "pipe", shell: true }
        );
      } catch { /* upgrade is best-effort */ }
    }
  }

  if (pythonPackageInstalled()) {
    const ver = getPackageVersion();
    log(`  ${T.BRANCH} ${T.OK} ${PYTHON_PACKAGE} ${ver || ""}`);
  } else {
    log(`  ${T.BRANCH} ${T.FAIL} Package not installed`);
    log(`  ${T.LAST}   Run manually: pip install ${PYTHON_PACKAGE}`);
    log();
    return;
  }

  // 3. MCP server smoke test
  log(`  ${T.SIDE}  ${T.WORK} MCP server smoke test...`);

  const verified = await new Promise((resolve) => {
    const envVars = loadEnvFile();
    const mergedEnv = { ...process.env, ...envVars };
    if (!mergedEnv.CGB_WORKSPACE) mergedEnv.CGB_WORKSPACE = WORKSPACE_DIR;

    const child = spawn(PYTHON_CMD, ["-m", MODULE_PATH], {
      stdio: ["pipe", "pipe", "pipe"],
      env: mergedEnv,
      shell: IS_WIN,
    });

    let stdout = "";
    let resolved = false;

    const finish = (success, detail) => {
      if (resolved) return;
      resolved = true;
      try { child.kill(); } catch {}
      resolve({ success, detail });
    };

    const timer = setTimeout(() => finish(false, "Server did not respond within 15s"), 15000);

    child.stderr.on("data", () => {});

    child.stdout.on("data", (chunk) => {
      stdout += chunk.toString();
      const lines = stdout.split("\n");
      for (const line of lines) {
        const trimmed = line.trim();
        if (!trimmed || !trimmed.startsWith("{")) continue;
        try {
          const msg = JSON.parse(trimmed);
          if (msg.result && msg.result.capabilities) {
            const toolsReq = JSON.stringify({
              jsonrpc: "2.0", id: 2, method: "tools/list", params: {},
            });
            child.stdin.write(toolsReq + "\n");
            stdout = "";
            return;
          }
          if (msg.result && msg.result.tools) {
            clearTimeout(timer);
            finish(true, `${msg.result.tools.length} tools`);
            return;
          }
        } catch { /* partial JSON */ }
      }
    });

    child.on("error", (err) => {
      clearTimeout(timer);
      finish(false, err.message);
    });

    child.on("exit", (code) => {
      clearTimeout(timer);
      if (!resolved) finish(false, `Server exited with code ${code}`);
    });

    const initReq = JSON.stringify({
      jsonrpc: "2.0",
      id: 1,
      method: "initialize",
      params: {
        protocolVersion: "2024-11-05",
        capabilities: {},
        clientInfo: { name: "setup-verify", version: "1.0.0" },
      },
    });
    child.stdin.write(initReq + "\n");
  });

  if (verified.success) {
    log(`  ${T.BRANCH} ${T.OK} MCP server (${verified.detail})`);
  } else {
    log(`  ${T.BRANCH} ${T.FAIL} MCP smoke test: ${verified.detail}`);
  }

  // 4. Claude Code registration
  if (commandExists("claude")) {
    try {
      try {
        execSync("claude mcp remove code-graph-builder", { stdio: "pipe", shell: true });
      } catch { /* not found */ }

      const addCmd = IS_WIN
        ? 'claude mcp add --scope user --transport stdio code-graph-builder -- cmd /c npx -y code-graph-builder@latest --server'
        : 'claude mcp add --scope user --transport stdio code-graph-builder -- npx -y code-graph-builder@latest --server';

      execSync(addCmd, { stdio: "pipe", shell: true });
      log(`  ${T.LAST} ${T.OK} Claude Code MCP registered (global)`);
    } catch {
      log(`  ${T.LAST} ${T.WARN} Claude Code auto-register failed`);
      log(`       Run manually:`);
      if (IS_WIN) {
        log(`       claude mcp add --scope user --transport stdio code-graph-builder -- cmd /c npx -y code-graph-builder@latest --server`);
      } else {
        log(`       claude mcp add --scope user --transport stdio code-graph-builder -- npx -y code-graph-builder@latest --server`);
      }
    }
  } else {
    log(`  ${T.LAST} ${T.WARN} Claude Code CLI not found`);
    log();
    log(`       Add to your MCP client config manually:`);
    log();
    log(`       {`);
    log(`         "mcpServers": {`);
    log(`           "code-graph-builder": {`);
    log(`             "command": "npx",`);
    log(`             "args": ["-y", "code-graph-builder@latest", "--server"]`);
    log(`           }`);
    log(`         }`);
    log(`       }`);
  }

  log();
  log(`  ${T.DOT} Setup complete`);
  log();
}

// ---------------------------------------------------------------------------
// Start MCP server
// ---------------------------------------------------------------------------

function runServer(cmd, args) {
  const envVars = loadEnvFile();
  const mergedEnv = { ...process.env, ...envVars };

  if (!mergedEnv.CGB_WORKSPACE) {
    mergedEnv.CGB_WORKSPACE = WORKSPACE_DIR;
  }

  const child = spawn(cmd, args, {
    stdio: "inherit",
    env: mergedEnv,
    shell: IS_WIN,
  });

  child.on("error", (err) => {
    process.stderr.write(`Failed to start MCP server: ${err.message}\n`);
    process.exit(1);
  });

  child.on("exit", (code) => {
    process.exit(code ?? 0);
  });
}

function autoInstallAndStart(extraArgs) {
  const pip = findPip();
  if (!pip) {
    process.stderr.write(
      `code-graph-builder requires Python 3.10+ with pip.\n\n` +
        (PYTHON_CMD
          ? `Python found (${PYTHON_CMD}) but pip is not available.\n\n`
          : `Python 3 not found on PATH.\n\n`) +
        `Please install Python 3.10+ first, then run:\n` +
        `  npx code-graph-builder --server\n`
    );
    process.exit(1);
  }

  process.stderr.write(`Installing ${PYTHON_PACKAGE}...\n`);

  try {
    execSync(
      [...pip, "install", PYTHON_PACKAGE].map(s => `"${s}"`).join(" "),
      { stdio: "inherit", shell: true }
    );
  } catch (err) {
    process.stderr.write(
      `\nFailed to install ${PYTHON_PACKAGE}.\n` +
        `Try manually: ${pip.join(" ")} install ${PYTHON_PACKAGE}\n`
    );
    process.exit(1);
  }

  if (!pythonPackageInstalled()) {
    process.stderr.write(
      `\nInstallation completed but package not importable.\n` +
        `Try manually: ${pip.join(" ")} install ${PYTHON_PACKAGE}\n`
    );
    process.exit(1);
  }

  process.stderr.write(`${PYTHON_PACKAGE} installed successfully.\n`);
  runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
}

// ---------------------------------------------------------------------------
// Uninstall
// ---------------------------------------------------------------------------

async function runUninstall() {
  const rl = createInterface({ input: process.stdin, output: process.stderr });
  const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
  const log = (msg = "") => process.stderr.write(msg + "\n");

  log();
  log(box("code-graph-builder  Uninstall"));
  log();

  const pip = findPip();
  const hasPythonPkg = pythonPackageInstalled();
  const hasWorkspace = existsSync(WORKSPACE_DIR);
  const hasEnv = existsSync(ENV_FILE);

  let hasClaudeConfig = false;
  try {
    const mcpList = execFileSync("claude", ["mcp", "list"], { stdio: "pipe" }).toString();
    hasClaudeConfig = mcpList.includes("code-graph-builder");
  } catch { /* claude CLI not available */ }

  log(`  ${T.DOT} Components detected`);
  log(`  ${T.SIDE}`);
  if (hasPythonPkg)    log(`  ${T.BRANCH} Python package:  code-graph-builder`);
  else                 log(`  ${T.BRANCH} Python package:  (not installed)`);
  if (hasWorkspace)    log(`  ${T.BRANCH} Workspace data:  ${WORKSPACE_DIR}`);
  else                 log(`  ${T.BRANCH} Workspace data:  (not found)`);
  if (hasEnv)          log(`  ${T.BRANCH} Config file:     ${ENV_FILE}`);
  if (hasClaudeConfig) log(`  ${T.BRANCH} Claude Code MCP: registered`);
  log(`  ${T.LAST}`);
  log();

  const answer = (await ask("  Proceed with uninstall? [y/N]: ")).trim().toLowerCase();
  rl.close();

  if (answer !== "y" && answer !== "yes") {
    log("\n  Uninstall cancelled.\n");
    process.exit(0);
  }

  log();
  log(`  ${T.DOT} Removing`);
  log(`  ${T.SIDE}`);

  // Claude Code MCP entry
  if (hasClaudeConfig) {
    try {
      execSync("claude mcp remove code-graph-builder", { stdio: "pipe", shell: true });
      log(`  ${T.BRANCH} ${T.OK} Claude Code MCP entry`);
    } catch {
      log(`  ${T.BRANCH} ${T.WARN} Claude Code MCP entry (manual removal needed)`);
    }
  }

  // Python package
  if (hasPythonPkg && pip) {
    try {
      execSync(
        [...pip, "uninstall", "-y", PYTHON_PACKAGE].map(s => `"${s}"`).join(" "),
        { stdio: "pipe", shell: true }
      );
      log(`  ${T.BRANCH} ${T.OK} Python package`);
    } catch {
      log(`  ${T.BRANCH} ${T.WARN} Python package (try: pip uninstall code-graph-builder)`);
    }
  }

  // Workspace data
  if (hasWorkspace) {
    try {
      rmSync(WORKSPACE_DIR, { recursive: true, force: true });
      log(`  ${T.BRANCH} ${T.OK} Workspace data`);
    } catch (err) {
      log(`  ${T.BRANCH} ${T.WARN} Workspace: ${err.message}`);
    }
  }

  // npx cache
  log(`  ${T.SIDE}  ${T.WORK} Clearing npx cache...`);
  await clearNpxCache();
  log(`  ${T.LAST} ${T.OK} npx cache`);

  log();
  log(`  ${T.DOT} Uninstall complete`);
  log();
}

function startServer(extraArgs = []) {
  if (pythonPackageInstalled()) {
    runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
  } else if (commandExists("uvx")) {
    runServer("uvx", [PYTHON_PACKAGE, ...extraArgs]);
  } else if (commandExists("uv")) {
    runServer("uv", ["tool", "run", PYTHON_PACKAGE, ...extraArgs]);
  } else if (commandExists("pipx")) {
    runServer("pipx", ["run", PYTHON_PACKAGE, ...extraArgs]);
  } else {
    autoInstallAndStart(extraArgs);
  }
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

const args = process.argv.slice(2);
const mode = args[0];

if (mode === "--setup") {
  runSetup();
} else if (mode === "--server" || mode === "--pip" || mode === "--python") {
  if (mode === "--pip" || mode === "--python") {
    if (!PYTHON_CMD || !pythonPackageInstalled()) {
      process.stderr.write(
        `Error: Python package '${PYTHON_PACKAGE}' is not installed.\n` +
          `Run: pip install ${PYTHON_PACKAGE}\n`
      );
      process.exit(1);
    }
    runServer(PYTHON_CMD, ["-m", MODULE_PATH]);
  } else {
    startServer(args.slice(1));
  }
} else if (mode === "--uninstall") {
  runUninstall();
} else if (mode === "--help" || mode === "-h") {
  const log = (msg) => process.stderr.write(msg + "\n");
  log("");
  log(box("code-graph-builder"));
  log("");
  log("  Usage:");
  log("");
  log("    npx code-graph-builder              Interactive setup wizard");
  log("    npx code-graph-builder --server     Start MCP server");
  log("    npx code-graph-builder --setup      Re-run setup wizard");
  log("    npx code-graph-builder --uninstall  Completely uninstall");
  log("    npx code-graph-builder --help       Show this help");
  log("");
  log(`  Config: ${ENV_FILE}`);
  log("");
} else {
  if (!existsSync(ENV_FILE)) {
    runSetup();
  } else {
    startServer(args);
  }
}
