const fs = require('fs');
const os = require('os');
const path = require('path');

const { configPath, packageRoot } = require('./config');
const { IS_FREE } = require('./tier');

// The npx package the installed MCP entries invoke (`npx -y <spec> mcp`). The
// MCP server is enterprise-only, so default to the enterprise package name —
// `npx cielara mcp` would run the free package, which has no MCP server.
const DEFAULT_PACKAGE_SPEC = IS_FREE ? 'cielara' : 'cielara-enterprise';

// SessionStart hook commands per tier. Free wires a single hook that
// backgrounds `cielara enable` (via the `session-start` subcommand) so each
// repo auto-enables the sidecar's grep enrichment. Enterprise keeps the
// lightweight auth-check it has always shipped.
const FREE_SESSION_START_COMMAND = 'cielara session-start';
const ENTERPRISE_SESSION_START_COMMAND = 'cielara auth-check';

function mcpCommand(options = {}) {
  if (options.local) {
    return {
      command: process.execPath,
      args: [path.join(__dirname, '..', 'bin', 'cielara.js'), 'mcp'],
    };
  }
  return {
    command: 'npx',
    args: ['-y', options.packageSpec || DEFAULT_PACKAGE_SPEC, 'mcp'],
  };
}

function mcpServerJSON(options = {}) {
  const command = mcpCommand(options);
  return {
    type: 'stdio',
    command: command.command,
    args: command.args,
    env: {
      CIELARA_CONNECTOR_CONFIG: configPath(),
    },
  };
}

function readJSON(file) {
  let text;
  try {
    text = fs.readFileSync(file, 'utf8');
  } catch (error) {
    if (error && error.code === 'ENOENT') {
      return {};
    }
    throw error;
  }
  // Treat empty / whitespace-only files as missing. A previous editor crash
  // or partial atomic write can leave a 0-byte client config behind, and
  // failing the whole install over that is unhelpful — JSON.parse('')
  // surfaces as "Unexpected end of JSON input", confusing customers.
  if (!text.trim()) {
    return {};
  }
  try {
    return JSON.parse(text);
  } catch (error) {
    error.message = `${error.message} (in ${file})`;
    throw error;
  }
}

function writeJSON(file, value) {
  fs.mkdirSync(path.dirname(file), { recursive: true });
  fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`);
}

function upsertJSONMCP(file, options = {}) {
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSON(options);
  writeJSON(file, data);
  return file;
}

function tomlString(value) {
  return JSON.stringify(String(value));
}

function tomlArray(values) {
  return `[${values.map(tomlString).join(', ')}]`;
}

function codexBlock(options = {}) {
  const command = mcpCommand(options);
  return [
    '[mcp_servers.cielara]',
    `command = ${tomlString(command.command)}`,
    `args = ${tomlArray(command.args)}`,
    'enabled = true',
    'startup_timeout_sec = 30',
    '',
    '[mcp_servers.cielara.env]',
    `CIELARA_CONNECTOR_CONFIG = ${tomlString(configPath())}`,
    '',
  ].join('\n');
}

function upsertTomlSection(content, block) {
  const lines = content.split(/\r?\n/);
  const start = lines.findIndex((line) => line.trim() === '[mcp_servers.cielara]');
  if (start < 0) {
    const prefix = content.trimEnd();
    return `${prefix}${prefix ? '\n\n' : ''}${block}`;
  }

  let end = lines.length;
  for (let i = start + 1; i < lines.length; i += 1) {
    const trimmed = lines[i].trim();
    if (trimmed.startsWith('[') && trimmed.endsWith(']') && !trimmed.startsWith('[mcp_servers.cielara')) {
      end = i;
      break;
    }
  }
  const next = [...lines.slice(0, start), block.trimEnd(), ...lines.slice(end)];
  return `${next.join('\n').trimEnd()}\n`;
}

function installCodex(options = {}) {
  const file = path.join(os.homedir(), '.codex', 'config.toml');
  fs.mkdirSync(path.dirname(file), { recursive: true });
  const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
  fs.writeFileSync(file, upsertTomlSection(current, codexBlock(options)));
  return file;
}

function removeCodexHooks() {
  const file = path.join(os.homedir(), '.codex', 'hooks.json');
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  let changed = false;
  if (Array.isArray(data?.hooks?.PostToolUse)) {
    const filtered = data.hooks.PostToolUse.filter(
      (entry) => !entry?.hooks?.some((h) => h?.command?.includes('cielara codex-hook')),
    );
    if (filtered.length !== data.hooks.PostToolUse.length) {
      data.hooks.PostToolUse = filtered;
      changed = true;
    }
  }
  // Clean up stale top-level key if present.
  if ('PostToolUse' in data) {
    delete data.PostToolUse;
    changed = true;
  }
  if (!changed) return null;
  writeJSON(file, data);
  return file;
}

function claudeDesktopConfigPath() {
  if (process.platform === 'darwin') {
    return path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
  }
  if (process.platform === 'win32') {
    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
    return path.join(appData, 'Claude', 'claude_desktop_config.json');
  }
  return path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json');
}

function installClaudeCode(options = {}) {
  return upsertJSONMCP(path.join(os.homedir(), '.claude.json'), options);
}

function installClaudeDesktop(options = {}) {
  return upsertJSONMCP(claudeDesktopConfigPath(), options);
}

function installCursor(options = {}) {
  return upsertJSONMCP(path.join(os.homedir(), '.cursor', 'mcp.json'), options);
}

function removeCursorHooks() {
  const file = path.join(os.homedir(), '.cursor', 'hooks.json');
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  let changed = false;
  if (Array.isArray(data?.hooks?.postToolUse)) {
    const filtered = data.hooks.postToolUse.filter(
      (entry) => !String(entry?.command || '').includes('cursor-hook'),
    );
    if (filtered.length !== data.hooks.postToolUse.length) {
      data.hooks.postToolUse = filtered;
      changed = true;
    }
  }
  // Clean up any stale stop hook from a previous version.
  if (Array.isArray(data?.hooks?.stop)) {
    const filtered = data.hooks.stop.filter(
      (entry) => !String(entry?.command || '').includes('cursor-stop-notify'),
    );
    if (filtered.length !== data.hooks.stop.length) {
      data.hooks.stop = filtered;
      if (data.hooks.stop.length === 0) delete data.hooks.stop;
      changed = true;
    }
  }
  if (!changed) return null;
  writeJSON(file, data);
  return file;
}

function mcpServerJSONBare(options = {}) {
  const command = mcpCommand(options);
  return {
    command: command.command,
    args: command.args,
    env: {
      CIELARA_CONNECTOR_CONFIG: configPath(),
    },
  };
}

function installAntigravity(options = {}) {
  const file = path.join(os.homedir(), '.gemini', 'antigravity', 'mcp_config.json');
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function removeAntigravityHooks() {
  const file = path.join(os.homedir(), '.gemini', 'config', 'hooks.json');
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  let changed = false;
  for (const key of Object.keys(data)) {
    if (JSON.stringify(data[key] || '').includes('agy-hook')) {
      delete data[key];
      changed = true;
    }
  }
  if (!changed) return null;
  writeJSON(file, data);
  return file;
}

function installCopilot(options = {}) {
  const file = path.join(os.homedir(), '.vscode', 'mcp.json');
  const data = readJSON(file);
  data.servers = data.servers || {};
  const command = mcpCommand(options);
  data.servers.cielara = {
    type: 'stdio',
    command: command.command,
    args: command.args,
    env: {
      CIELARA_CONNECTOR_CONFIG: configPath(),
    },
  };
  writeJSON(file, data);
  return file;
}

function installOpenClaw(options = {}) {
  const file = path.join(os.homedir(), '.openclaw', 'openclaw.json');
  const data = readJSON(file);
  data.mcp = data.mcp || {};
  data.mcp.servers = data.mcp.servers || {};
  data.mcp.servers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function installWindsurf(options = {}) {
  const file = path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json');
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function installKiroCli(options = {}) {
  const file = path.join(os.homedir(), '.kiro', 'settings', 'mcp.json');
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function installAmazonQ(options = {}) {
  const file = path.join(os.homedir(), '.aws', 'amazonq', 'mcp.json');
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function installGeminiCli(options = {}) {
  const file = path.join(os.homedir(), '.gemini', 'settings.json');
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function clineConfigPath() {
  if (process.platform === 'darwin') {
    return path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
  }
  if (process.platform === 'win32') {
    const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
    return path.join(appData, 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
  }
  return path.join(os.homedir(), '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json');
}

function installCline(options = {}) {
  const file = clineConfigPath();
  const data = readJSON(file);
  data.mcpServers = data.mcpServers || {};
  data.mcpServers.cielara = mcpServerJSONBare(options);
  writeJSON(file, data);
  return file;
}

function expandTargets(target) {
  const selected = target || 'all';
  if (selected === 'all') {
    return [
      'codex', 'claude-code', 'claude-desktop', 'cursor',
      'antigravity', 'copilot', 'openclaw',
      'windsurf', 'amazon-q', 'gemini-cli', 'cline', 'kiro-cli',
    ];
  }
  if (selected === 'claude') {
    return ['claude-code', 'claude-desktop'];
  }
  return selected.split(',').map((item) => item.trim()).filter(Boolean);
}

function installCommands() {
  const srcDir = path.join(packageRoot(), 'commands');
  const destDir = path.join(os.homedir(), '.claude', 'commands');
  if (!fs.existsSync(srcDir)) {
    return [];
  }
  fs.mkdirSync(destDir, { recursive: true });
  const installed = [];
  for (const file of fs.readdirSync(srcDir)) {
    if (!file.endsWith('.md')) {
      continue;
    }
    const dest = path.join(destDir, file);
    fs.copyFileSync(path.join(srcDir, file), dest);
    installed.push({ target: 'command', file: dest });
  }
  return installed;
}

function copyDirRecursive(src, dest) {
  fs.mkdirSync(dest, { recursive: true });
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
    const srcPath = path.join(src, entry.name);
    const destPath = path.join(dest, entry.name);
    if (entry.isDirectory()) {
      copyDirRecursive(srcPath, destPath);
    } else {
      fs.copyFileSync(srcPath, destPath);
    }
  }
}

// `only` (optional array of skill names) limits the install to those skills;
// omitted installs every skill present in skills/.
function installCodexSkills(only) {
  const srcDir = path.join(packageRoot(), 'skills');
  const destDir = path.join(os.homedir(), '.codex', 'skills');
  if (!fs.existsSync(srcDir)) {
    return [];
  }
  const installed = [];
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) {
      continue;
    }
    if (only && !only.includes(entry.name)) {
      continue;
    }
    const skillSrc = path.join(srcDir, entry.name);
    const skillDest = path.join(destDir, entry.name);
    copyDirRecursive(skillSrc, skillDest);
    installed.push({ target: 'codex-skill', file: skillDest });
  }
  return installed;
}

function installClaudeSkills(only) {
  const srcDir = path.join(packageRoot(), 'skills');
  const destDir = path.join(os.homedir(), '.claude', 'skills');
  if (!fs.existsSync(srcDir)) {
    return [];
  }
  const installed = [];
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) {
      continue;
    }
    if (only && !only.includes(entry.name)) {
      continue;
    }
    const skillSrc = path.join(srcDir, entry.name);
    const skillDest = path.join(destDir, entry.name);
    copyDirRecursive(skillSrc, skillDest);
    installed.push({ target: 'claude-skill', file: skillDest });
  }
  return installed;
}

function installClaudeSessionStartHook(hookCommand = ENTERPRISE_SESSION_START_COMMAND) {
  const settingsFile = path.join(os.homedir(), '.claude', 'settings.json');
  const data = readJSON(settingsFile);
  if (!data.hooks) data.hooks = {};
  if (!data.hooks.SessionStart) data.hooks.SessionStart = [];

  // Drop any cielara SessionStart hook we previously wrote (either tier's
  // command) so switching between free/enterprise doesn't leave a stale one.
  const isCielaraSessionHook = (entry) =>
    entry?.hooks?.some((h) =>
      h?.command === FREE_SESSION_START_COMMAND ||
      h?.command === ENTERPRISE_SESSION_START_COMMAND);

  const exists = data.hooks.SessionStart.some(
    (entry) => entry?.hooks?.some((h) => h?.command === hookCommand),
  );
  if (!exists) {
    data.hooks.SessionStart = data.hooks.SessionStart.filter(
      (entry) => !isCielaraSessionHook(entry),
    );
    data.hooks.SessionStart.push({
      matcher: '',
      hooks: [{ type: 'command', command: hookCommand }],
    });
    writeJSON(settingsFile, data);
  }
  return settingsFile;
}

// Free-tier install: the Claude Code SessionStart hook that backgrounds
// `cielara enable`, plus the `cielara-usage` skill (Claude + Codex). No MCP
// server entries, no slash commands, no grep PostToolUse hooks — those are
// enterprise-only. The per-repo grep enrichment is installed by the sidecar's
// own `enable`, which the SessionStart hook triggers.
const FREE_SKILLS = ['cielara-usage'];
function installFree() {
  const installed = [
    {
      target: 'claude-session-hook',
      file: installClaudeSessionStartHook(FREE_SESSION_START_COMMAND),
    },
  ];
  installed.push(...installClaudeSkills(FREE_SKILLS));
  installed.push(...installCodexSkills(FREE_SKILLS));
  return installed;
}

function removeClaudeSessionStartHook() {
  const file = path.join(os.homedir(), '.claude', 'settings.json');
  if (!fs.existsSync(file)) return null;
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  const sessionStart = data?.hooks?.SessionStart;
  if (!Array.isArray(sessionStart)) return null;
  const filtered = sessionStart.filter(
    (entry) => !entry?.hooks?.some((h) =>
      h?.command === FREE_SESSION_START_COMMAND ||
      h?.command === ENTERPRISE_SESSION_START_COMMAND),
  );
  if (filtered.length === sessionStart.length) return null;
  data.hooks.SessionStart = filtered;
  fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
  return file;
}

function install(options = {}) {
  // Free tier ships no MCP server, skills, commands, or grep hooks — only the
  // SessionStart hook that backgrounds `cielara enable`.
  if (IS_FREE) {
    return installFree(options);
  }

  // Enterprise install writes MCP server entries + skills + slash commands only.
  // It installs NO hooks: the cielara-code sidecar owns all SessionStart +
  // PostToolUse (grep) hooks via `cielara-code enable`. Proactively remove any
  // legacy connector-written hooks (from an older version) so an upgrade doesn't
  // leave stale `cielara *-hook` / `cielara auth-check` entries that now resolve
  // to removed subcommands.
  const installed = [];
  removeClaudeHooks();
  removeClaudeSessionStartHook();
  removeCodexHooks();
  removeCursorHooks();
  removeAntigravityHooks();

  for (const target of expandTargets(options.target)) {
    switch (target) {
      case 'codex':
        installed.push({ target, file: installCodex(options) });
        break;
      case 'claude-code':
        installed.push({ target, file: installClaudeCode(options) });
        break;
      case 'claude-desktop':
        installed.push({ target, file: installClaudeDesktop(options) });
        break;
      case 'cursor':
        installed.push({ target, file: installCursor(options) });
        break;
      case 'antigravity':
        installed.push({ target, file: installAntigravity(options) });
        break;
      case 'copilot':
        installed.push({ target, file: installCopilot(options) });
        break;
      case 'openclaw':
        installed.push({ target, file: installOpenClaw(options) });
        break;
      case 'windsurf':
        installed.push({ target, file: installWindsurf(options) });
        break;
      case 'amazon-q':
        installed.push({ target, file: installAmazonQ(options) });
        break;
      case 'gemini-cli':
        installed.push({ target, file: installGeminiCli(options) });
        break;
      case 'cline':
        installed.push({ target, file: installCline(options) });
        break;
      case 'kiro-cli':
        installed.push({ target, file: installKiroCli(options) });
        break;
      default:
        throw new Error(`Unknown install target: ${target}`);
    }
  }

  // Skills (askcielara, cielara-usage) for Claude Code + Codex.
  installed.push(...installClaudeSkills());
  installed.push(...installCodexSkills());

  return installed;
}

// ── Uninstall ────────────────────────────────────────────────────────────────
// Mirror of install(): remove `cielara` MCP server entries from every client
// config we ever wrote to, and delete copied commands/skills. Idempotent —
// targets that aren't present are silently skipped.

function removeFromJSON(file, parentKeys, removeKey) {
  if (!fs.existsSync(file)) return null;
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  let node = data;
  for (const k of parentKeys) {
    if (!node || typeof node !== 'object' || !(k in node)) return null;
    node = node[k];
  }
  if (!node || typeof node !== 'object' || !(removeKey in node)) return null;
  delete node[removeKey];
  fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
  return file;
}

function removeCodexBlock(file) {
  if (!fs.existsSync(file)) return null;
  const cur = fs.readFileSync(file, 'utf8');
  const lines = cur.split(/\r?\n/);
  const start = lines.findIndex((line) => line.trim() === '[mcp_servers.cielara]');
  if (start < 0) return null;
  let end = lines.length;
  for (let i = start + 1; i < lines.length; i += 1) {
    const t = lines[i].trim();
    if (t.startsWith('[') && t.endsWith(']') && !t.startsWith('[mcp_servers.cielara')) {
      end = i;
      break;
    }
  }
  // Also drop the [mcp_servers.cielara.env] subsection if it directly follows.
  const next = [...lines.slice(0, start), ...lines.slice(end)];
  // Trim leading/trailing blank lines we may have left.
  while (next.length && next[next.length - 1].trim() === '') next.pop();
  fs.writeFileSync(file, `${next.join('\n')}\n`);
  return file;
}

function removeDirIfEmpty(dir) {
  try {
    fs.rmdirSync(dir);
  } catch {
    /* not empty or missing — ignore */
  }
}

function uninstallCommands() {
  const srcDir = path.join(packageRoot(), 'commands');
  const destDir = path.join(os.homedir(), '.claude', 'commands');
  if (!fs.existsSync(srcDir) || !fs.existsSync(destDir)) return [];
  const removed = [];
  for (const file of fs.readdirSync(srcDir)) {
    if (!file.endsWith('.md')) continue;
    const dest = path.join(destDir, file);
    if (fs.existsSync(dest)) {
      fs.unlinkSync(dest);
      removed.push(dest);
    }
  }
  removeDirIfEmpty(destDir);
  return removed;
}

function uninstallCodexSkills(only) {
  const srcDir = path.join(packageRoot(), 'skills');
  const destDir = path.join(os.homedir(), '.codex', 'skills');
  if (!fs.existsSync(srcDir) || !fs.existsSync(destDir)) return [];
  const removed = [];
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue;
    if (only && !only.includes(entry.name)) continue;
    const skillDest = path.join(destDir, entry.name);
    if (fs.existsSync(skillDest)) {
      fs.rmSync(skillDest, { recursive: true, force: true });
      removed.push(skillDest);
    }
  }
  removeDirIfEmpty(destDir);
  return removed;
}

function uninstallClaudeSkills(only) {
  const srcDir = path.join(packageRoot(), 'skills');
  const destDir = path.join(os.homedir(), '.claude', 'skills');
  if (!fs.existsSync(srcDir) || !fs.existsSync(destDir)) return [];
  const removed = [];
  for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
    if (!entry.isDirectory()) continue;
    if (only && !only.includes(entry.name)) continue;
    const skillDest = path.join(destDir, entry.name);
    if (fs.existsSync(skillDest)) {
      fs.rmSync(skillDest, { recursive: true, force: true });
      removed.push(skillDest);
    }
  }
  removeDirIfEmpty(destDir);
  return removed;
}

function removeClaudeHooks() {
  const file = path.join(os.homedir(), '.claude', 'settings.json');
  if (!fs.existsSync(file)) return null;
  let data;
  try {
    data = JSON.parse(fs.readFileSync(file, 'utf8'));
  } catch {
    return null;
  }
  const postToolUse = data?.hooks?.PostToolUse;
  if (!Array.isArray(postToolUse)) return null;
  const filtered = postToolUse.filter(
    (entry) => !entry?.hooks?.some((h) => h?.command?.includes('cielara claude-hook')),
  );
  if (filtered.length === postToolUse.length) return null;
  data.hooks.PostToolUse = filtered;
  fs.writeFileSync(file, `${JSON.stringify(data, null, 2)}\n`);
  return file;
}

function uninstall(options = {}) {
  const removed = [];
  const home = os.homedir();

  // Free tier wrote the SessionStart hook + the cielara-usage skill. Remove
  // those (and, with --purge, the connector config dir) and stop — there are no
  // MCP entries, commands, or grep hooks to clean up.
  if (IS_FREE) {
    const sessionHookFile = removeClaudeSessionStartHook();
    if (sessionHookFile) removed.push({ target: 'claude-session-hook', file: sessionHookFile });
    for (const f of uninstallClaudeSkills(FREE_SKILLS)) removed.push({ target: 'claude-skill', file: f });
    for (const f of uninstallCodexSkills(FREE_SKILLS)) removed.push({ target: 'codex-skill', file: f });
    if (options.purge) {
      const dir = path.join(home, '.cielara');
      if (fs.existsSync(dir)) {
        fs.rmSync(dir, { recursive: true, force: true });
        removed.push({ target: 'config', file: dir });
      }
    }
    return removed;
  }

  // 1. MCP server entries — every client install() can write to.
  const jsonTargets = [
    { target: 'claude-code',    file: path.join(home, '.claude.json'),                                    parents: ['mcpServers'] },
    { target: 'claude-desktop', file: claudeDesktopConfigPath(),                                           parents: ['mcpServers'] },
    { target: 'cursor',         file: path.join(home, '.cursor', 'mcp.json'),                             parents: ['mcpServers'] },
    { target: 'antigravity',    file: path.join(home, '.gemini', 'antigravity', 'mcp_config.json'),       parents: ['mcpServers'] },
    { target: 'copilot',        file: path.join(home, '.vscode', 'mcp.json'),                             parents: ['servers'] },
    { target: 'openclaw',       file: path.join(home, '.openclaw', 'openclaw.json'),                      parents: ['mcp', 'servers'] },
    { target: 'windsurf',       file: path.join(home, '.codeium', 'windsurf', 'mcp_config.json'),         parents: ['mcpServers'] },
    { target: 'amazon-q',       file: path.join(home, '.aws', 'amazonq', 'mcp.json'),                     parents: ['mcpServers'] },
    { target: 'gemini-cli',     file: path.join(home, '.gemini', 'settings.json'),                        parents: ['mcpServers'] },
    { target: 'cline',          file: clineConfigPath(),                                                  parents: ['mcpServers'] },
    { target: 'kiro-cli',       file: path.join(home, '.kiro', 'settings', 'mcp.json'),                   parents: ['mcpServers'] },
  ];
  for (const t of jsonTargets) {
    const f = removeFromJSON(t.file, t.parents, 'cielara');
    if (f) removed.push({ target: t.target, file: f });
  }
  const codexFile = removeCodexBlock(path.join(home, '.codex', 'config.toml'));
  if (codexFile) removed.push({ target: 'codex', file: codexFile });

  // 2. Claude Code, Codex, and Cursor hooks.
  const hooksFile = removeClaudeHooks();
  if (hooksFile) removed.push({ target: 'claude-hooks', file: hooksFile });
  const sessionHookFile = removeClaudeSessionStartHook();
  if (sessionHookFile) removed.push({ target: 'claude-session-hook', file: sessionHookFile });
  const codexHooksFile = removeCodexHooks();
  if (codexHooksFile) removed.push({ target: 'codex-hooks', file: codexHooksFile });
  const cursorHooksFile = removeCursorHooks();
  if (cursorHooksFile) removed.push({ target: 'cursor-hooks', file: cursorHooksFile });
  const antigravityHooksFile = removeAntigravityHooks();
  if (antigravityHooksFile) removed.push({ target: 'antigravity-hooks', file: antigravityHooksFile });

  // 3. Copied commands + codex skills.
  for (const f of uninstallCommands()) removed.push({ target: 'command', file: f });
  for (const f of uninstallClaudeSkills()) removed.push({ target: 'claude-skill', file: f });
  for (const f of uninstallCodexSkills()) removed.push({ target: 'codex-skill', file: f });

  // 4. Connector config dir (connector.json, stats.json, tos-accepted.json,
  //    connector-python venv). Opt-in via --purge to avoid surprising users
  //    who reinstall and want their tokens preserved.
  if (options.purge) {
    const dir = path.join(home, '.cielara');
    if (fs.existsSync(dir)) {
      fs.rmSync(dir, { recursive: true, force: true });
      removed.push({ target: 'config', file: dir });
    }
  }

  return removed;
}

module.exports = {
  DEFAULT_PACKAGE_SPEC,
  install,
  uninstall,
  mcpCommand,
};
