/**
 * Single source of truth for the Claude Code hook entries Nexus Prime owns.
 * Both `nexus-prime install` (cli.ts) and the install-wizard call into here
 * so we cannot drift on hook spec, dedup logic, or marker detection.
 *
 * Foreign user hooks are preserved — only entries whose command starts with
 * `nexus-prime hook` are stripped before re-adding.
 */
import * as fs from 'fs';
import * as path from 'path';
/** Marker that identifies a hook entry as Nexus-owned. */
export const NEXUS_HOOK_COMMAND_MARKER = 'nexus-prime hook';
/**
 * Canonical Nexus hook spec. Edit here, not in callers.
 * Timeouts protect Claude Code from a wedged Nexus process — leave them set.
 */
export function getNexusHookSpec() {
    return {
        UserPromptSubmit: [
            { hooks: [{ type: 'command', command: 'nexus-prime hook bootstrap', timeout: 30 }] },
        ],
        PreToolUse: [
            {
                matcher: 'Edit|Write|MultiEdit',
                hooks: [{ type: 'command', command: 'nexus-prime hook mindkit', timeout: 10 }],
            },
        ],
        PostToolUse: [
            {
                matcher: 'Edit|Write|MultiEdit|Bash',
                hooks: [{ type: 'command', command: 'nexus-prime hook memory', timeout: 10 }],
            },
        ],
        Stop: [
            { hooks: [{ type: 'command', command: 'nexus-prime hook session-dna', timeout: 60 }] },
        ],
    };
}
function isNexusHookEntry(entry) {
    if (!entry || typeof entry !== 'object')
        return false;
    const hooksList = entry.hooks;
    if (!Array.isArray(hooksList))
        return false;
    return hooksList.some((h) => h && typeof h === 'object'
        && typeof h.command === 'string'
        && h.command.trimStart().startsWith(NEXUS_HOOK_COMMAND_MARKER));
}
/**
 * Idempotent writer for the Nexus hook block. Returns metadata so callers can
 * report status (`installed | unchanged | dry-run`) without re-implementing
 * the merge logic.
 */
export function writeNexusClaudeCodeHooks(settingsPath, options = {}) {
    const dryRun = options.dryRun ?? false;
    let settings = {};
    if (fs.existsSync(settingsPath)) {
        try {
            const raw = fs.readFileSync(settingsPath, 'utf8');
            const parsed = JSON.parse(raw);
            if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
                settings = parsed;
            }
        }
        catch {
            settings = {};
        }
    }
    const incomingHooks = getNexusHookSpec();
    const events = Object.keys(incomingHooks);
    const existing = (settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks))
        ? { ...settings.hooks }
        : {};
    let staleRemoved = 0;
    let changed = !fs.existsSync(settingsPath);
    for (const event of events) {
        const before = Array.isArray(existing[event]) ? existing[event] : [];
        const filtered = before.filter((entry) => {
            const isNexus = isNexusHookEntry(entry);
            if (isNexus)
                staleRemoved += 1;
            return !isNexus;
        });
        const next = [...filtered, ...incomingHooks[event]];
        if (filtered.length !== before.length || next.length !== before.length)
            changed = true;
        existing[event] = next;
    }
    settings.hooks = existing;
    if (dryRun || !changed) {
        return { changed, events, staleRemoved };
    }
    fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
    fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
    return { changed, events, staleRemoved };
}
