/**
 * `olam hermes bootstrap` — one-shot wiring of a fresh Hermes install to olam.
 *
 * Performs:
 *   1. Validates ~/.hermes/config.yaml exists (refuses with actionable message if not).
 *   2. Adds mcp_servers.olam (resolved via `which olam` or argv[1] fallback).
 *   3. Optionally adds mcp_servers.agentmemory-atlas when bridge URL + secret
 *      are available (env vars or --bridge-url / --bridge-secret flags).
 *   4. Installs kg-first.sh hook via installKgFirstHookForHermes.
 *   5. Mirrors ~/.claude/skills/* symlinks into ~/.hermes/skills/.
 *   6. Sets terminal.backend = 'local' (unless --keep-docker is given).
 *   7. Prints a summary of added / skipped / next-steps.
 *
 * All mutations are idempotent: re-running is safe.
 * --dry-run prints the planned diff without writing anything.
 */
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import * as child_process from 'node:child_process';
import { parse as yamlParse, stringify as yamlStringify } from 'yaml';
import { printError, printInfo, printSuccess, printWarning } from '../output.js';
import { installKgFirstHookForHermes, uninstallKgFirstHookForHermes, HERMES_KG_HOOK_SENTINEL, } from './hermes-kg-hook.js';
// ── Paths ─────────────────────────────────────────────────────────────────────
export function hermesConfigPath() {
    return path.join(os.homedir(), '.hermes', 'config.yaml');
}
export function hermesHooksDir() {
    return path.join(os.homedir(), '.hermes', 'hooks');
}
export function hermesSkillsDir() {
    return path.join(os.homedir(), '.hermes', 'skills');
}
export function claudeSkillsDir() {
    return path.join(os.homedir(), '.claude', 'skills');
}
// ── YAML helpers ──────────────────────────────────────────────────────────────
function readHermesConfig(configPath) {
    const raw = fs.readFileSync(configPath, 'utf-8');
    const parsed = yamlParse(raw);
    if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error(`${configPath} is not a YAML mapping`);
    }
    return parsed;
}
function writeHermesConfig(configPath, config) {
    fs.writeFileSync(configPath, yamlStringify(config), 'utf-8');
}
// ── Olam binary resolution ────────────────────────────────────────────────────
function resolveOlamBinary() {
    // Prefer `which olam` from PATH.
    try {
        const result = child_process.spawnSync('which', ['olam'], { encoding: 'utf-8' });
        const found = result.stdout.trim();
        if (found && fs.existsSync(found))
            return found;
    }
    catch {
        // fall through
    }
    // Fallback to process.argv[1] (the running CLI binary).
    return process.argv[1] ?? 'olam';
}
// ── Workspace dir resolution ──────────────────────────────────────────────────
function resolveWorkspaceDir() {
    // Try `olam config get workspace.dir`; fall back to cwd.
    try {
        const result = child_process.spawnSync(process.argv[0] ?? 'node', [process.argv[1] ?? 'olam', 'config', 'get', 'workspace.dir'], { encoding: 'utf-8' });
        const out = result.stdout.trim();
        if (out && fs.existsSync(out))
            return out;
    }
    catch {
        // fall through
    }
    return process.cwd();
}
// ── Sub-operations ────────────────────────────────────────────────────────────
function ensureMcpOlam(config, olamBin, workspaceDir, dryRun, summary) {
    const mcpServers = (config['mcp_servers'] ?? {});
    if (mcpServers['olam'] !== undefined) {
        summary.skipped.push('mcp_servers.olam (already present)');
        return config;
    }
    if (dryRun) {
        summary.added.push(`mcp_servers.olam → { command: "${olamBin}", args: ["mcp","serve"], cwd: "${workspaceDir}" }`);
        return config;
    }
    const updated = {
        ...config,
        mcp_servers: {
            ...mcpServers,
            olam: {
                command: olamBin,
                args: ['mcp', 'serve'],
                cwd: workspaceDir,
            },
        },
    };
    summary.added.push('mcp_servers.olam');
    return updated;
}
function ensureMcpAgentMemory(config, bridgeUrl, bridgeSecret, dryRun, summary) {
    const mcpServers = (config['mcp_servers'] ?? {});
    const key = 'agentmemory-atlas';
    if (mcpServers[key] !== undefined) {
        summary.skipped.push(`mcp_servers.${key} (already present)`);
        return config;
    }
    if (dryRun) {
        summary.added.push(`mcp_servers.${key} → { bridge_url, bridge_secret }`);
        return config;
    }
    const updated = {
        ...config,
        mcp_servers: {
            ...mcpServers,
            [key]: {
                bridge_url: bridgeUrl,
                bridge_secret: bridgeSecret,
            },
        },
    };
    summary.added.push(`mcp_servers.${key}`);
    return updated;
}
function ensureTerminalBackendLocal(config, dryRun, summary) {
    const terminal = (config['terminal'] ?? {});
    if (terminal['backend'] === 'local') {
        summary.skipped.push('terminal.backend (already local)');
        return config;
    }
    if (dryRun) {
        summary.added.push('terminal.backend = local');
        return config;
    }
    return {
        ...config,
        terminal: { ...terminal, backend: 'local' },
    };
}
function mirrorSkillSymlinks(dryRun, summary) {
    const srcDir = claudeSkillsDir();
    if (!fs.existsSync(srcDir)) {
        summary.skipped.push('skills mirror (no ~/.claude/skills/ found)');
        return;
    }
    const destDir = hermesSkillsDir();
    if (!dryRun) {
        fs.mkdirSync(destDir, { recursive: true });
    }
    const entries = fs.readdirSync(srcDir);
    let mirrored = 0;
    let collisions = 0;
    for (const entry of entries) {
        const srcPath = path.join(srcDir, entry);
        const destPath = path.join(destDir, entry);
        if (fs.existsSync(destPath) || fs.lstatSync(srcPath).isFile()) {
            // Skip collisions and non-directory entries (skills are dirs)
            if (fs.existsSync(destPath))
                collisions++;
            continue;
        }
        if (dryRun) {
            mirrored++;
            continue;
        }
        try {
            fs.symlinkSync(srcPath, destPath);
            mirrored++;
        }
        catch (err) {
            printWarning(`skills mirror: skipping ${entry} — ${err instanceof Error ? err.message : String(err)}`);
        }
    }
    if (mirrored > 0)
        summary.added.push(`${mirrored} skill symlink(s) → ~/.hermes/skills/`);
    if (collisions > 0)
        summary.skipped.push(`${collisions} skill symlink collision(s)`);
}
// ── Bootstrap action ──────────────────────────────────────────────────────────
export async function runHermesBootstrap(opts, deps = {}) {
    const dryRun = opts.dryRun ?? false;
    const keepDocker = opts.keepDocker ?? false;
    const configPath = deps.configPath ?? hermesConfigPath();
    const olamBin = deps.olamBin ?? resolveOlamBinary();
    const workspaceDir = deps.workspaceDir ?? resolveWorkspaceDir();
    const readConfig = deps.readConfig ?? readHermesConfig;
    const writeConfig = deps.writeConfig ?? writeHermesConfig;
    const mirrorSkillsFn = deps.mirrorSkills ?? mirrorSkillSymlinks;
    const installHookFn = deps.installHook ?? installKgFirstHookForHermes;
    // Guard: ~/.hermes/config.yaml must exist
    if (!fs.existsSync(configPath)) {
        printError(`~/.hermes/config.yaml not found at ${configPath}`);
        printInfo('remedy', 'Install Hermes first: https://hermes.ai/docs/install — then re-run `olam hermes bootstrap`');
        process.exitCode = 1;
        return;
    }
    const summary = { added: [], skipped: [], nextSteps: [] };
    // Load current config
    let config;
    try {
        config = readConfig(configPath);
    }
    catch (err) {
        printError(`Could not parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
        return;
    }
    // Step 2: mcp_servers.olam
    config = ensureMcpOlam(config, olamBin, workspaceDir, dryRun, summary);
    // Step 3: mcp_servers.agentmemory-atlas (optional)
    const bridgeUrl = opts.bridgeUrl ?? process.env['AGENTMEMORY_BRIDGE_URL'];
    const bridgeSecret = opts.bridgeSecret ?? process.env['AGENTMEMORY_BRIDGE_SECRET'];
    if (bridgeUrl && bridgeSecret) {
        config = ensureMcpAgentMemory(config, bridgeUrl, bridgeSecret, dryRun, summary);
    }
    else {
        summary.skipped.push('mcp_servers.agentmemory-atlas (AGENTMEMORY_BRIDGE_URL/SECRET not set; use --bridge-url/--bridge-secret to add)');
    }
    // Step 4: kg-first hook
    const hooksDir = path.join(path.dirname(configPath), 'hooks');
    const hookResult = installHookFn(hooksDir, dryRun);
    switch (hookResult.status) {
        case 'installed':
            summary.added.push('kg-first.sh hook');
            break;
        case 'already-present':
            summary.skipped.push('kg-first.sh hook (already installed)');
            break;
        case 'dry-run':
            summary.added.push('kg-first.sh hook (dry-run)');
            break;
    }
    // Ensure hooks.pre_tool_call entry in config
    config = ensureHermesHookEntry(config, hooksDir, dryRun, summary);
    // Step 5: mirror skill symlinks
    mirrorSkillsFn(dryRun, summary);
    // Step 6: terminal.backend = local
    if (!keepDocker) {
        config = ensureTerminalBackendLocal(config, dryRun, summary);
    }
    else {
        summary.skipped.push('terminal.backend (--keep-docker)');
    }
    // Write updated config
    if (!dryRun) {
        try {
            writeConfig(configPath, config);
        }
        catch (err) {
            printError(`Could not write ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
            process.exitCode = 1;
            return;
        }
    }
    // Step 7: print summary
    if (dryRun) {
        printInfo('dry-run', 'No changes written. Planned additions:');
    }
    else {
        printSuccess('Hermes bootstrap complete');
    }
    for (const item of summary.added) {
        printInfo(dryRun ? 'would add' : 'added', item);
    }
    for (const item of summary.skipped) {
        printInfo('skipped', item);
    }
    summary.nextSteps.push('hermes gateway restart');
    for (const step of summary.nextSteps) {
        printInfo('next', step);
    }
}
// ── Hook config entry ─────────────────────────────────────────────────────────
const HOOK_MATCHERS = ['terminal', 'bash', 'shell', 'search_files', 'grep', 'ripgrep'];
function ensureHermesHookEntry(config, hooksDir, dryRun, summary) {
    const hookScriptPath = path.join(hooksDir, 'kg-first.sh');
    const hooks = (config['hooks'] ?? {});
    const preToolCall = Array.isArray(hooks['pre_tool_call'])
        ? hooks['pre_tool_call']
        : [];
    // Idempotency: check if our sentinel command is already present
    const alreadyPresent = preToolCall.some((entry) => typeof entry['command'] === 'string' && entry['command'].includes(HERMES_KG_HOOK_SENTINEL));
    if (alreadyPresent) {
        summary.skipped.push('hooks.pre_tool_call entry (already present)');
        return config;
    }
    const newEntry = {
        matcher: HOOK_MATCHERS.join('|'),
        command: `${hookScriptPath} # ${HERMES_KG_HOOK_SENTINEL}`,
    };
    if (dryRun) {
        summary.added.push(`hooks.pre_tool_call entry (matcher: ${HOOK_MATCHERS.join('|')})`);
        return config;
    }
    const updatedPreToolCall = [...preToolCall, newEntry];
    return {
        ...config,
        hooks: { ...hooks, pre_tool_call: updatedPreToolCall },
        hooks_auto_accept: true,
    };
}
// ── Command registration ──────────────────────────────────────────────────────
export function registerHermes(program) {
    const hermes = program
        .command('hermes')
        .description('Hermes integration commands');
    hermes
        .command('bootstrap')
        .description('One-shot wiring of a fresh Hermes install to olam (MCP + KG hook + skill mirror)')
        .option('--dry-run', 'Print planned changes without writing anything')
        .option('--keep-docker', 'Skip setting terminal.backend = local')
        .option('--bridge-url <url>', 'agentmemory bridge URL (overrides AGENTMEMORY_BRIDGE_URL env)')
        .option('--bridge-secret <secret>', 'agentmemory bridge secret (overrides AGENTMEMORY_BRIDGE_SECRET env)')
        .action(async (opts) => {
        await runHermesBootstrap(opts);
    });
}
//# sourceMappingURL=hermes.js.map