#!/usr/bin/env node
// neruva-mcp-install -- wire the bundled Rust hook into ~/.claude/settings.json
// in FULL-TAKEOVER mode (no Python, no daemon). Run via:
//
//     npx -p @neruva/mcp neruva-mcp-install
//     npx -p @neruva/mcp neruva-mcp-install --uninstall
//
// Topology (mirrors the daemon+async-recorder split, all-Rust):
//   PreToolUse[*]                      sync   -> context (never records)
//   UserPromptSubmit                   sync   -> context + record prompt
//   PostToolUse[records_ingest]        sync   -> extract/consistency (--context-only)
//   PostToolUse[*]                     async  -> record tool_call    (--record-only)
//   Stop / SessionEnd / Subagent* / Task*  async -> record (--record-only)
//   SessionStart                       sync   -> boot recall + record
//
// The async record entries keep recording OFF the latency path; --context-only
// on the records_ingest entry prevents double-recording. Idempotent: re-running
// strips our prior entries first. Backs settings up before writing.

import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, copyFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { homedir, platform, arch } from 'node:os';

const __dirname = dirname(fileURLToPath(import.meta.url));
const SETTINGS = join(homedir(), '.claude', 'settings.json');

function pickBinary() {
  const p = platform();
  const platSeg = p === 'win32' ? 'win' : p === 'darwin' ? 'mac' : 'linux';
  const archSeg = arch() === 'arm64' ? 'arm64' : 'x64';
  const ext = p === 'win32' ? '.exe' : '';
  return join(__dirname, '..', 'binaries', `neruva-record-hook-${platSeg}-${archSeg}${ext}`);
}

// Default auto-pilot env. Forced: NERUVA_RUST_FULL_TAKEOVER. Others are set only
// when absent so we never clobber a user's existing tuning.
const ENV_FORCE = { NERUVA_RUST_FULL_TAKEOVER: '1' };
const ENV_DEFAULTS = {
  NERUVA_AUTO_RECORD: 'claude_code',
  NERUVA_AUTO_RECALL: '10',
  NERUVA_PER_TURN_RECALL: '3',
  NERUVA_AUTO_EXTRACT: '1',
  NERUVA_PRE_ACTION_RECALL: '1',
  NERUVA_PRE_ACTION_SOLUTION: '1',
  NERUVA_PRE_ACTION_THRESHOLD: '0.3',
  NERUVA_PRE_ACTION_DEADLINE_MS: '1500',
  NERUVA_AUTO_ROUTE: '0',
  // canon v3 (0.43): LEAN hook = mistake-recall + enforce-deny, loop/stall-break,
  // solution-reuse, record-for-replay. CUT from defaults: code-graph (commodity),
  // domain-KG-on-prompt + post-ingest consistency (noise), swarm layers
  // (resource-claim/consensus/task-coord = opt-in, multi-agent only), and
  // primitive routing (AUTO_ROUTE=0, primitives are internal-only). Opt back in
  // per-flag for a real swarm or to experiment.
};

const isOurEntry = (entry) =>
  (entry?.hooks || []).some((h) => typeof h?.command === 'string' && h.command.includes('neruva-record-hook'));

function buildHooks(cmd) {
  const sync = (extra) => ({ hooks: [{ type: 'command', command: extra ? `${cmd} ${extra}` : cmd }] });
  const async_ = (extra, matcher) => {
    const e = { hooks: [{ type: 'command', command: extra ? `${cmd} ${extra}` : cmd, async: true }] };
    if (matcher) e.matcher = matcher;
    return e;
  };
  const syncM = (matcher, extra) => ({ matcher, hooks: [{ type: 'command', command: extra ? `${cmd} ${extra}` : cmd }] });
  return {
    PreToolUse: [syncM('*')],
    UserPromptSubmit: [sync()],
    PostToolUse: [syncM('mcp__neruva__records_ingest', '--context-only'), async_('--record-only', '*')],
    // Capture failures (kind=tool_failure) so the reliability loop self-feeds:
    // a failed action is auto-recorded and surfaces before a similar action next time.
    PostToolUseFailure: [async_('--record-only')],
    Stop: [async_('--record-only')],
    SessionStart: [sync()],
    SessionEnd: [async_('--record-only')],
    SubagentStart: [async_('--record-only')],
    SubagentStop: [async_('--record-only')],
    TaskCreated: [async_('--record-only')],
    TaskCompleted: [async_('--record-only')],
  };
}

function readSettings() {
  if (!existsSync(SETTINGS)) return {};
  let raw;
  try { raw = readFileSync(SETTINGS, 'utf8'); }
  catch (e) { console.error(`[neruva] cannot read ${SETTINGS}: ${e.message}`); process.exit(1); }
  if (!raw.trim()) return {};
  try { return JSON.parse(raw); }
  catch (e) {
    // CRITICAL: never overwrite a settings file we can't parse -- that would
    // destroy the user's mcpServers / hooks / env on a stray comma. Abort.
    console.error(
      `[neruva] ${SETTINGS} is not valid JSON (${e.message}).\n` +
      `         Refusing to overwrite it. Fix or remove the file, then re-run.`);
    process.exit(1);
  }
}

function stripOurs(hooks) {
  const out = {};
  for (const [evt, entries] of Object.entries(hooks || {})) {
    const kept = (entries || []).filter((e) => !isOurEntry(e));
    if (kept.length) out[evt] = kept;
  }
  return out;
}

function main() {
  const uninstall = process.argv.includes('--uninstall');
  const binary = pickBinary();

  if (!uninstall && !existsSync(binary)) {
    console.error(`[neruva] bundled binary missing: ${binary}\n` +
      `Reinstall @neruva/mcp so the platform binary is present.`);
    process.exit(1);
  }
  if (!uninstall && platform() !== 'win32') {
    try { chmodSync(binary, 0o755); } catch {}
  }

  const settings = readSettings();
  if (existsSync(SETTINGS)) {
    try { copyFileSync(SETTINGS, `${SETTINGS}.bak`); } catch {}
  } else {
    mkdirSync(dirname(SETTINGS), { recursive: true });
  }

  // Always start from a clean slate of OUR entries (idempotent re-runs).
  settings.hooks = stripOurs(settings.hooks);

  if (uninstall) {
    if (settings.env) delete settings.env.NERUVA_RUST_FULL_TAKEOVER;
    writeFileSync(SETTINGS, JSON.stringify(settings, null, 2));
    console.log('[neruva] full-takeover hooks removed from', SETTINGS);
    return;
  }

  const cmd = `"${binary.replace(/\\/g, '/')}"`;
  const ours = buildHooks(cmd);
  for (const [evt, entries] of Object.entries(ours)) {
    settings.hooks[evt] = [...(settings.hooks[evt] || []), ...entries];
  }

  settings.env = settings.env || {};
  Object.assign(settings.env, ENV_FORCE);
  for (const [k, v] of Object.entries(ENV_DEFAULTS)) {
    if (settings.env[k] === undefined) settings.env[k] = v;
  }

  writeFileSync(SETTINGS, JSON.stringify(settings, null, 2));
  console.log('[neruva] full-takeover auto-pilot wired into', SETTINGS);
  console.log('[neruva] binary:', binary);
  if (!settings.env.NERUVA_API_KEY) {
    console.log('\n  NEXT: set your API key (free at https://neruva.io):');
    console.log('    add  "NERUVA_API_KEY": "nv_..."  to the "env" block in', SETTINGS);
  }
  console.log('  Then restart Claude Code. Memory + reasoning auto-pilot is now active with zero Python.');
}

main();
