/**
 * `olam kg doctor` — diagnostic table for the kg-service container.
 *
 * Probes (in order):
 *   1. container  — Container state (running / stopped / missing)
 *   2. health     — /health responds with ready=true (+ version + uptime)
 *   3. workspaces — /status lists at least one workspace (advisory; warn if empty)
 *   4. classify   — /classify round-trip works end-to-end (synthetic probe)
 *   5. savings     — cumulative KG-hit tally (advisory; "is the hook firing?")
 *
 * Exit 0 on all-green; non-zero on the first hard failure with a remedy hint.
 * Advisory warnings (empty workspaces, zero hits) never flip the exit code.
 * --json emits a machine-readable structure for monitoring pipelines.
 *
 * The probe walk is a pure, injectable function (`runKgDoctor`) so it can be
 * unit-tested without a live container — mirrors the `runKgStatus` /
 * `runDoctor` testability pattern used elsewhere in the CLI.
 */
import pc from 'picocolors';
import { KgServiceContainerController } from './kg-service-container.js';
import { classify as classifyImpl, health as healthImpl, KgServiceUnreachableError, savings as savingsImpl, status as statusImpl, } from '@olam/core/src/kg/kg-service-client.js';
import { printError, printHeader, printInfo, printSuccess, printWarning } from '../output.js';
/** Compact human-readable uptime (e.g. "3m", "2h 5m", "1d 4h"). */
function formatUptime(seconds) {
    if (!Number.isFinite(seconds) || seconds < 0)
        return '?';
    const total = Math.floor(seconds);
    if (total < 60)
        return `${total}s`;
    const minutes = Math.floor(total / 60);
    if (minutes < 60)
        return `${minutes}m`;
    const hours = Math.floor(minutes / 60);
    if (hours < 24)
        return `${hours}h ${minutes % 60}m`;
    const days = Math.floor(hours / 24);
    return `${days}d ${hours % 24}h`;
}
/** Compact token-count formatting that mirrors `kg savings`' `~`-prefix convention. */
function formatSavedTokens(n) {
    if (n >= 1_000_000)
        return `~${(n / 1_000_000).toFixed(1)}M`;
    if (n >= 1_000)
        return `~${Math.round(n / 1000)}k`;
    return `~${n}`;
}
async function runProbes(deps) {
    const results = [];
    const controller = deps.controller ?? new KgServiceContainerController();
    const health = deps.health ?? healthImpl;
    const status = deps.status ?? statusImpl;
    const classify = deps.classify ?? classifyImpl;
    const savings = deps.savings ?? savingsImpl;
    // Probe 1: container state
    const state = controller.status();
    if (state.state === 'running') {
        results.push({ name: 'container', status: 'ok', detail: `running on :${state.port}` });
    }
    else if (state.state === 'stopped') {
        results.push({
            name: 'container',
            status: 'fail',
            detail: 'stopped',
            remedy: '`olam services up`',
        });
        return results; // Subsequent probes will all fail; short-circuit.
    }
    else {
        if (controller.imageMissingEverywhere()) {
            results.push({
                name: 'container',
                status: 'warn',
                detail: 'image not pulled (opt-out)',
                remedy: '`olam bootstrap` (without --skip-kg-service) to pull olam-kg-service',
            });
        }
        else {
            results.push({
                name: 'container',
                status: 'fail',
                detail: 'missing',
                remedy: '`olam services up`',
            });
        }
        return results;
    }
    // Probe 2: /health
    try {
        const h = await health({ timeoutMs: 2_000 });
        if (h.ready) {
            // Surface uptime when the server reports it (added by /health in the
            // current server.py; older builds omit uptime_s — degrade gracefully).
            const uptime = h.uptime_s != null ? ` · up ${formatUptime(h.uptime_s)}` : '';
            results.push({ name: 'health', status: 'ok', detail: `ready · v${h.version}${uptime}` });
        }
        else {
            results.push({
                name: 'health',
                status: 'warn',
                detail: `service up but not ready (still warming up?)`,
                remedy: 'Wait a few seconds and re-run',
            });
        }
    }
    catch (err) {
        results.push({
            name: 'health',
            status: 'fail',
            detail: err instanceof Error ? err.message : String(err),
            remedy: 'Check `docker logs olam-kg-service`',
        });
        return results;
    }
    // Probe 3: /status (workspace enumeration)
    try {
        const s = await status({ timeoutMs: 2_000 });
        if (s.workspaces.length === 0) {
            results.push({
                name: 'workspaces',
                status: 'warn',
                detail: 'no workspaces built yet',
                remedy: '`olam kg build` to index a workspace',
            });
        }
        else {
            results.push({
                name: 'workspaces',
                status: 'ok',
                detail: `${s.workspaces.length} indexed: ${s.workspaces.map((w) => w.name).join(', ')}`,
            });
        }
    }
    catch (err) {
        results.push({
            name: 'workspaces',
            status: 'fail',
            detail: err instanceof Error ? err.message : String(err),
        });
    }
    // Probe 4: /classify round-trip
    try {
        const r = await classify({ q: 'doctor probe' }, { timeoutMs: 3_000 });
        if (r.route && r.took_ms != null) {
            results.push({
                name: 'classify',
                status: 'ok',
                detail: `round-trip ok · ${r.took_ms}ms · layer ${r.layer}`,
            });
        }
        else {
            results.push({
                name: 'classify',
                status: 'warn',
                detail: 'unexpected response shape',
            });
        }
    }
    catch (err) {
        if (err instanceof KgServiceUnreachableError) {
            results.push({
                name: 'classify',
                status: 'fail',
                detail: 'kg-service unreachable',
                remedy: '`olam services up`',
            });
        }
        else {
            results.push({
                name: 'classify',
                status: 'fail',
                detail: err instanceof Error ? err.message : String(err),
            });
        }
    }
    // Probe 5: /savings (cumulative KG-hit tally).
    //
    // Advisory-only — answers the operator's real question "is the KG-first
    // hook actually firing on my grep/find calls?". Zero hits is a soft WARN
    // (not a failure): a freshly-booted service legitimately has no tally yet.
    // A non-zero tally is the felt signal that the substrate is doing work.
    try {
        const sv = await savings({ timeoutMs: 2_000 });
        if (sv.total_hits === 0) {
            results.push({
                name: 'savings',
                status: 'warn',
                detail: 'no KG hits logged yet',
                remedy: 'Run a few grep/find Bash calls in a world to exercise the KG-first hook',
            });
        }
        else {
            results.push({
                name: 'savings',
                status: 'ok',
                detail: `${sv.total_hits} hits · ${formatSavedTokens(sv.total_saved_tokens_est)} tokens saved (est)`,
            });
        }
    }
    catch {
        // Older kg-service builds predate the /savings endpoint. Treat an
        // unreachable/absent endpoint as advisory — never fail doctor on it.
        results.push({
            name: 'savings',
            status: 'warn',
            detail: 'savings tally unavailable (older kg-service build?)',
        });
    }
    return results;
}
/**
 * Pure, injectable probe walk. Renders to the configured output when not in
 * JSON mode and returns the structured result + exit code. Tests drive this
 * directly with fake deps; the commander action is a thin wrapper.
 */
export async function runKgDoctor(opts, deps = {}) {
    const probes = await runProbes(deps);
    if (opts.json) {
        process.stdout.write(JSON.stringify({ probes }, null, 2) + '\n');
    }
    else {
        printHeader('kg-service doctor');
        for (const p of probes) {
            const badge = p.status === 'ok'
                ? pc.green('✓')
                : p.status === 'warn'
                    ? pc.yellow('⊘')
                    : pc.red('✗');
            const label = `${badge}  ${p.name}`;
            const detail = p.detail ?? '';
            printInfo(label, detail);
            if (p.remedy) {
                process.stderr.write(`      ${pc.dim('remedy:')} ${p.remedy}\n`);
            }
        }
    }
    // Exit code: non-zero if any probe FAILED; warn alone is acceptable.
    const hasFail = probes.some((p) => p.status === 'fail');
    if (hasFail) {
        if (!opts.json)
            printError('one or more probes failed');
        return { exitCode: 1, probes };
    }
    if (probes.every((p) => p.status === 'ok')) {
        if (!opts.json)
            printSuccess('all probes green');
    }
    else if (!opts.json) {
        printWarning('mostly green, with advisory warnings');
    }
    return { exitCode: 0, probes };
}
export function registerKgDoctorCommand(kg) {
    kg.command('doctor')
        .description('Diagnostic table for kg-service (container + /health + workspaces + classify round-trip + savings tally)')
        .option('--json', 'Emit machine-readable JSON instead of the formatted table')
        .action(async (opts) => {
        const r = await runKgDoctor(opts);
        if (r.exitCode !== 0)
            process.exitCode = r.exitCode;
    });
}
//# sourceMappingURL=kg-doctor.js.map