"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports._testing = void 0;
exports.runQuickstart = runQuickstart;
exports.registerQuickstartCommand = registerQuickstartCommand;
const path = __importStar(require("path"));
const fs = __importStar(require("fs"));
const os = __importStar(require("os"));
const daemon_discovery_js_1 = require("../../lib/daemon-discovery.js");
const ui_js_1 = require("../ui.js");
const QUICKSTART_TEMPLATE_ID = 'quickstart-self-test';
const SAMPLE_ARTIFACT = `// Quickstart self-test artifact — a tiny snippet with a real bug.
// Reviewers should flag the off-by-one in the loop bound.

function average(numbers) {
  let sum = 0;
  for (let i = 0; i <= numbers.length; i++) {
    sum += numbers[i];
  }
  return sum / numbers.length;
}
`;
const SAMPLE_WORK = 'Quickstart self-test for chorus — does the reviewer catch the off-by-one in the average() loop?';
/** Build the smallest valid review-only YAML that uses one detected lineage. */
function buildQuickstartYaml(lineage, model) {
    // Keep crossLineage off — a user with only one CLI installed must
    // still be able to run quickstart. The 'require: 1' threshold means
    // a single reviewer's verdict is enough to settle the chat.
    return `id: ${QUICKSTART_TEMPLATE_ID}
name: Quickstart Self-Test
description: |
  Auto-generated by \`chorus quickstart\` — single reviewer slot routed
  to whichever CLI the user has on PATH. Replaced on every quickstart
  invocation to match the latest detected lineage.
author: chorus
agreementThreshold: 1.0
onThresholdMet: ask
maxRounds: 1
yoloDefault: false
estimatedBaselineTokens: 300
ship:
  enabled: false
phases:
  - id: review
    kind: review_only
    title: Quickstart Review
    description: Single reviewer critiques the supplied artifact.
    reviewer:
      require: 1
      crossLineage: false
      candidates:
        - lineage: ${lineage}${model ? `\n          models:\n            - ${model}` : ''}
    inputs:
      include: []
      exclude: []
    artifact:
      label: Code snippet
      hint: Quickstart self-test artifact (auto-supplied).
      maxBytes: 16384
`;
}
async function pollChat(baseUrl, chatId, signal) {
    let lastStatus = '';
    while (!signal.aborted) {
        const r = await fetch(`${baseUrl}/chats/${chatId}`);
        if (!r.ok)
            throw new Error(`status fetch failed: ${r.status}`);
        const env = (await r.json());
        const data = env.data;
        if (!data)
            throw new Error('chat status missing data');
        if (data.status !== lastStatus) {
            process.stdout.write(`  ${ui_js_1.c.gray('·')} status: ${ui_js_1.c.cyan(data.status)}\n`);
            lastStatus = data.status;
        }
        if (data.status === 'approved' ||
            data.status === 'merged' ||
            data.status === 'blocked' ||
            data.status === 'cancelled' ||
            data.status === 'failed' ||
            data.status === 'no_review') {
            return data;
        }
        await new Promise((resolve) => setTimeout(resolve, 1500));
    }
    throw new Error('aborted');
}
function readReviewerAnswer(chatDir) {
    // Round 1 is the only round for review-only; one reviewer dir lives
    // inside it (we built the template that way). Walk to find it.
    const round1 = path.join(chatDir, 'round-1');
    if (!fs.existsSync(round1))
        return null;
    const reviewerDirs = fs
        .readdirSync(round1)
        .filter((n) => n.startsWith('reviewer-'));
    if (reviewerDirs.length === 0)
        return null;
    const answerFile = path.join(round1, reviewerDirs[0], 'answer.md');
    if (!fs.existsSync(answerFile))
        return null;
    const body = fs.readFileSync(answerFile, 'utf-8');
    if (body.startsWith('## REVIEWER FAILED')) {
        return { kind: 'failed', body };
    }
    return { kind: 'ok', body };
}
async function runQuickstart(opts = {}) {
    console.log('');
    console.log(`  ${ui_js_1.sym.rocket} ${ui_js_1.c.bold('Chorus quickstart')} ${ui_js_1.c.dim('— 30-second self-test')}`);
    console.log('');
    // 1. Daemon up?
    const info = (0, daemon_discovery_js_1.readDaemonInfo)();
    if (!info) {
        console.log(`  ${ui_js_1.c.red('✗')} daemon not running`);
        console.log(`     run ${ui_js_1.c.bold('chorus start')} first, then re-run quickstart`);
        process.exitCode = 1;
        return;
    }
    const healthy = await (0, daemon_discovery_js_1.isDaemonHealthy)(info.daemonPort, 1500);
    if (!healthy) {
        console.log(`  ${ui_js_1.c.red('✗')} daemon not responding on :${info.daemonPort}`);
        console.log(`     run ${ui_js_1.c.bold('chorus stop && chorus start')} to recycle`);
        process.exitCode = 1;
        return;
    }
    const baseUrl = opts.daemonUrl ?? `http://127.0.0.1:${info.daemonPort}`;
    console.log(`  ${ui_js_1.c.green('✓')} daemon healthy on :${info.daemonPort}`);
    // 2. Pick a CLI lineage.
    const { detectAllClis } = await import('../../lib/cli-detect.js');
    // Map cli-detect ids to lineage strings the template-schema accepts.
    // Models intentionally omitted — the daemon's voices seed picks the
    // canonical default for each lineage, which keeps quickstart in sync
    // with whatever the user has configured / whatever ships as the
    // current default. Hardcoding model strings here proved brittle in
    // self-review (4 reviewers flagged drift risk for kimi-k2.6, gpt-5.5,
    // opencode/claude-sonnet-4-6).
    //
    // grok-cli is included now that we have a shim. It still needs a
    // SuperGrok Heavy subscription at dispatch time — quickstart's
    // single-reviewer slot will surface a quota_exhausted error cleanly
    // if the user is on a free tier. That's preferable to silently
    // skipping the only detected CLI.
    const cliToLineage = {
        'claude-code': 'anthropic',
        'codex-cli': 'openai',
        'gemini-cli': 'google',
        'opencode-cli': 'opencode',
        'kimi-cli': 'moonshot',
        'grok-cli': 'grok',
        'antigravity-cli': 'antigravity',
    };
    const detected = detectAllClis(true)
        .filter((d) => d.found)
        .filter((d) => cliToLineage[d.id] !== undefined);
    if (detected.length === 0) {
        console.log('');
        console.log(`  ${ui_js_1.c.red('✗')} no dispatchable CLIs detected on PATH`);
        console.log(`     install Claude Code, Codex, Gemini CLI, OpenCode, or Kimi CLI`);
        console.log(`     then run ${ui_js_1.c.bold('chorus connect')} to wire MCP`);
        console.log(`     ${ui_js_1.c.gray('details:')} ${ui_js_1.c.bold('chorus diagnose')}`);
        process.exitCode = 1;
        return;
    }
    const first = detected[0];
    const lineage = cliToLineage[first.id];
    if (!lineage) {
        // Unreachable — filter above guarantees lineage exists.
        console.log(`  ${ui_js_1.c.red('✗')} detected CLI '${first.id}' has no quickstart mapping`);
        process.exitCode = 1;
        return;
    }
    console.log(`  ${ui_js_1.c.green('✓')} reviewer: ${ui_js_1.c.bold(first.id)} ${ui_js_1.c.gray('(lineage:')} ${lineage}${ui_js_1.c.gray(')')}`);
    // 3. Upsert the quickstart template (no model — defaults from the seed).
    const yaml = buildQuickstartYaml(lineage);
    const upsert = await fetch(`${baseUrl}/templates`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ id: QUICKSTART_TEMPLATE_ID, yaml }),
    });
    if (!upsert.ok) {
        const text = await upsert.text();
        console.log(`  ${ui_js_1.c.red('✗')} template upsert failed: ${upsert.status} ${text.slice(0, 200)}`);
        process.exitCode = 1;
        return;
    }
    console.log(`  ${ui_js_1.c.green('✓')} template seeded`);
    // 4. Fire the chat.
    const chatRes = await fetch(`${baseUrl}/chats`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
            work: SAMPLE_WORK,
            templateId: QUICKSTART_TEMPLATE_ID,
            artifact: SAMPLE_ARTIFACT,
        }),
    });
    if (!chatRes.ok) {
        const text = await chatRes.text();
        console.log(`  ${ui_js_1.c.red('✗')} chat create failed: ${chatRes.status} ${text.slice(0, 200)}`);
        process.exitCode = 1;
        return;
    }
    const chatEnv = (await chatRes.json());
    const chatId = chatEnv.data?.id;
    if (!chatId) {
        console.log(`  ${ui_js_1.c.red('✗')} chat create returned no id`);
        process.exitCode = 1;
        return;
    }
    const cockpitUrl = await resolveCockpitUrlSafe();
    console.log(`  ${ui_js_1.c.green('✓')} chat fired ${ui_js_1.c.gray('(id: ' + chatId + ')')}`);
    if (cockpitUrl) {
        console.log(`     watch live: ${ui_js_1.c.cyan(`${cockpitUrl}/runs/${chatId}`)}`);
    }
    console.log('');
    // 5. Poll until terminal. Cap at 4 minutes — if a reviewer hasn't
    // finished by then, something's wrong with the CLI itself, not chorus.
    // Ctrl+C handler is registered BEFORE polling so a user who bails
    // mid-chat doesn't leave the daemon running an orphan reviewer in
    // the background. Self-review (4 reviewers) flagged this as the
    // primary blocker — without cancel-on-SIGINT, every quickstart that
    // gets interrupted leaves a chat consuming subscription quota.
    const ac = new AbortController();
    const timeout = setTimeout(() => ac.abort(), 4 * 60_000);
    let cancelled = false;
    const onSigint = () => {
        if (cancelled)
            return;
        cancelled = true;
        ac.abort();
        // Best-effort daemon cancel. Synchronous-ish — we don't await
        // because the SIGINT handler should return quickly so Node can
        // exit cleanly. The daemon's /chats/:id/cancel route is idempotent
        // so a double-cancel is harmless.
        void fetch(`${baseUrl}/chats/${chatId}/cancel`, { method: 'POST' }).catch(() => {
            /* daemon may already be tearing down — best effort */
        });
        console.log('');
        console.log(`  ${ui_js_1.c.gray('Ctrl-C — cancelling chat ' + chatId + '...')}`);
    };
    process.on('SIGINT', onSigint);
    let final;
    try {
        final = await pollChat(baseUrl, chatId, ac.signal);
    }
    catch (err) {
        console.log(`  ${ui_js_1.c.red('✗')} ${err instanceof Error ? err.message : String(err)}`);
        process.exitCode = 1;
        return;
    }
    finally {
        clearTimeout(timeout);
        process.off('SIGINT', onSigint);
    }
    console.log('');
    console.log(`  ${ui_js_1.sym.pointer} ${ui_js_1.c.bold('Result')} ${ui_js_1.c.gray('— status: ' + final.status + (final.verdict ? ', verdict: ' + final.verdict : ''))}`);
    console.log('');
    // 6. Render the reviewer's output (or its failure summary).
    const chatDir = path.join(os.homedir(), '.chorus', 'chats', chatId);
    const answer = readReviewerAnswer(chatDir);
    if (!answer) {
        console.log(`  ${ui_js_1.c.gray('(no reviewer output on disk yet — refresh the run page)')}`);
    }
    else if (answer.kind === 'failed') {
        console.log(ui_js_1.c.red(answer.body.slice(0, 1500)));
        console.log('');
        console.log(`  ${ui_js_1.c.gray('see')} ${ui_js_1.c.bold('chorus diagnose')} ${ui_js_1.c.gray('for failure context')}`);
        process.exitCode = 1;
        return;
    }
    else {
        // Trim long responses to ~80 lines so terminal isn't flooded.
        const lines = answer.body.split('\n');
        const display = lines.slice(0, 80).join('\n');
        console.log(display);
        if (lines.length > 80) {
            console.log(`  ${ui_js_1.c.gray(`(${lines.length - 80} more lines on disk)`)}`);
        }
    }
    console.log('');
    if (cockpitUrl) {
        console.log(`  ${ui_js_1.c.gray('full run:')} ${ui_js_1.c.cyan(`${cockpitUrl}/runs/${chatId}`)}`);
    }
    console.log('');
}
async function resolveCockpitUrlSafe() {
    // Read the cockpit port out of `~/.chorus/daemon.json` rather than
    // string-substituting the daemon URL (the substitution broke on any
    // non-default port — `:17707` → `:15050`, and any user with a
    // custom cockpit port saw a wrong link). The daemon writes both
    // ports to daemon.json on startup; falling back to null means the
    // terminal output simply omits the link rather than printing one
    // that 404s.
    try {
        const info = (0, daemon_discovery_js_1.readDaemonInfo)();
        if (!info)
            return null;
        return `http://127.0.0.1:${info.cockpitPort}`;
    }
    catch {
        return null;
    }
}
function registerQuickstartCommand(program) {
    program
        .command('quickstart')
        .description('Fire a 30-second sample chat against your first-detected CLI to confirm chorus works end-to-end')
        .action(async () => {
        try {
            await runQuickstart();
        }
        catch (err) {
            console.error('quickstart failed:', err instanceof Error ? err.message : err);
            process.exit(1);
        }
    });
}
exports._testing = {
    buildQuickstartYaml,
    QUICKSTART_TEMPLATE_ID,
    SAMPLE_ARTIFACT,
    SAMPLE_WORK,
};
//# sourceMappingURL=quickstart.js.map