"use strict";
/**
 * test_performance_latency.ts — Story 12.4
 *
 * Lightweight latency benchmark for OpenLife hot paths. Measures P50/P95/P99
 * for a handful of deterministic operations and persists a baseline at
 * `.artifacts/perf-baseline.json`. Fails if any P95 regresses more than
 * the configured threshold (default 30%) vs the saved baseline.
 *
 * Design constraints:
 *   • runs inside `test:all` → must complete in <5s end-to-end
 *   • no CLI spawn — measures library code directly to keep variance low
 *   • baseline auto-seeds on first run; later runs compare
 *   • configurable threshold via PERF_REGRESSION_THRESHOLD_PCT (default 30)
 */
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 });
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const IntentClassifier_1 = require("./orchestrator/IntentClassifier");
const ToolsetGuard_1 = require("./orchestrator/toolset/ToolsetGuard");
const ProfileManager_1 = require("./cli/ProfileManager");
const ConditionParser_1 = require("./orchestrator/workflow/ConditionParser");
const WorkflowParser_1 = require("./orchestrator/workflow/WorkflowParser");
function nowNs() { return process.hrtime.bigint(); }
function nsToMs(ns) { return Number(ns) / 1_000_000; }
function percentile(sorted, p) {
    if (sorted.length === 0)
        return 0;
    const idx = Math.min(sorted.length - 1, Math.floor((sorted.length - 1) * p));
    return sorted[idx];
}
function bench(name, iterations, fn) {
    // Warm-up — discard first 5 runs to avoid JIT noise.
    for (let i = 0; i < 5; i++)
        fn();
    const samples = [];
    for (let i = 0; i < iterations; i++) {
        const start = nowNs();
        fn();
        samples.push(nsToMs(nowNs() - start));
    }
    samples.sort((a, b) => a - b);
    return {
        name,
        iterations,
        p50ms: percentile(samples, 0.5),
        p95ms: percentile(samples, 0.95),
        p99ms: percentile(samples, 0.99),
        meanMs: samples.reduce((s, v) => s + v, 0) / samples.length,
    };
}
function main() {
    const repoRoot = path.resolve(__dirname, '..');
    const artifactsDir = path.join(repoRoot, '.artifacts');
    fs.mkdirSync(artifactsDir, { recursive: true });
    // Story 13.4 (v1.5) — baseline path is now env-overridable so CI can
    // point at a checked-in `ci/perf-baseline.json` while local dev keeps
    // the gitignored `.artifacts/perf-baseline.json`.
    const baselineFile = process.env.OPENLIFE_PERF_BASELINE_FILE
        ? path.resolve(repoRoot, process.env.OPENLIFE_PERF_BASELINE_FILE)
        : path.join(artifactsDir, 'perf-baseline.json');
    fs.mkdirSync(path.dirname(baselineFile), { recursive: true });
    // ── Benchmark 1: IntentClassifier.classify ──────────────────────
    const classifier = new IntentClassifier_1.IntentClassifier();
    const b1 = bench('intent_classify_cold', 200, () => {
        classifier.classify('plan a small refactor of the auth flow');
    });
    // ── Benchmark 2: ToolsetGuard isAllowed (off path) ──────────────
    (0, ToolsetGuard_1._resetToolsetGuardCache)();
    const b2 = bench('toolset_guard_off', 500, () => {
        (0, ToolsetGuard_1.isToolsetAllowed)('terminal');
    });
    // ── Benchmark 3: ProfileManager list ────────────────────────────
    const tmpStateDir = path.join(artifactsDir, '_perf-bench-state');
    fs.mkdirSync(tmpStateDir, { recursive: true });
    const prevStateDir = process.env.OPENLIFE_STATE_DIR;
    process.env.OPENLIFE_STATE_DIR = tmpStateDir;
    const pm = new ProfileManager_1.ProfileManager();
    // Seed 5 profiles for realistic listing.
    for (const id of ['a', 'b', 'c', 'd', 'e']) {
        try {
            pm.create({ id, name: id, toolsetAllowed: ['*'] });
        }
        catch { /* ignore re-create */ }
    }
    const b3 = bench('profile_manager_list', 200, () => {
        pm.list();
    });
    if (prevStateDir === undefined)
        delete process.env.OPENLIFE_STATE_DIR;
    else
        process.env.OPENLIFE_STATE_DIR = prevStateDir;
    fs.rmSync(tmpStateDir, { recursive: true, force: true });
    // ── Benchmark 4 (Story 16.2): condition parser hot path ──────────
    const parseResult = (0, ConditionParser_1.parseCondition)('(foo == "bar" AND baz) OR NOT qux');
    const conditionAst = parseResult.ok ? parseResult.ast : null;
    const conditionCtx = { foo: 'bar', baz: true, qux: false };
    const b4 = bench('condition_parse_and_evaluate', 500, () => {
        const r = (0, ConditionParser_1.parseCondition)('(foo == "bar" AND baz) OR NOT qux');
        if (r.ok && r.ast)
            (0, ConditionParser_1.evaluate)(r.ast, conditionCtx);
        else if (conditionAst)
            (0, ConditionParser_1.evaluate)(conditionAst, conditionCtx);
    });
    // ── Benchmark 5 (Story 16.2): workflow parse hot path ──────────────
    const sampleYaml = [
        'id: bench-wf',
        'name: Bench Workflow',
        'version: "1.0"',
        'description: tiny',
        'mode: task',
        'steps:',
        '  - id: greet',
        '    name: greet',
        '    agent: cli',
        '    action: { kind: log, message: hi }',
        '  - id: verify',
        '    name: verify',
        '    agent: cli',
        '    action: { kind: log, message: ok }',
        '    condition: greet.success',
        '',
    ].join('\n');
    const b5 = bench('workflow_parse', 200, () => {
        (0, WorkflowParser_1.parseWorkflow)(sampleYaml);
    });
    // ── Aggregate & persist ────────────────────────────────────────
    const results = [b1, b2, b3, b4, b5];
    const current = {
        capturedAt: new Date().toISOString(),
        benchmarks: results.reduce((acc, r) => {
            acc[r.name] = { p50ms: r.p50ms, p95ms: r.p95ms, p99ms: r.p99ms, meanMs: r.meanMs };
            return acc;
        }, {}),
    };
    const threshold = Number(process.env.PERF_REGRESSION_THRESHOLD_PCT || 30);
    if (!fs.existsSync(baselineFile)) {
        fs.writeFileSync(baselineFile, JSON.stringify(current, null, 2), 'utf-8');
        console.log('[perf] no baseline found — seeded fresh baseline.');
        for (const r of results) {
            console.log(`[perf] ${r.name.padEnd(28)} P50=${r.p50ms.toFixed(3)}ms  P95=${r.p95ms.toFixed(3)}ms  P99=${r.p99ms.toFixed(3)}ms  mean=${r.meanMs.toFixed(3)}ms`);
        }
        console.log('TEST_PERFORMANCE_LATENCY_OK');
        return;
    }
    const baseline = JSON.parse(fs.readFileSync(baselineFile, 'utf-8'));
    // Story 13.4 (v1.5) — absolute P95 floor below which the % gate is skipped.
    // Sub-millisecond benchmarks are dominated by timer noise; a +30% delta on
    // 0.001ms is meaningless. Configurable via PERF_NOISE_FLOOR_MS (default 0.5).
    const noiseFloorMs = Number(process.env.PERF_NOISE_FLOOR_MS || 0.5);
    const regressions = [];
    for (const r of results) {
        const prev = baseline.benchmarks[r.name];
        if (!prev) {
            console.log(`[perf] ${r.name} new — no baseline.`);
            continue;
        }
        if (prev.p95ms < noiseFloorMs && r.p95ms < noiseFloorMs) {
            console.log(`[perf] · ${r.name.padEnd(28)} P95 now=${r.p95ms.toFixed(3)}ms (baseline=${prev.p95ms.toFixed(3)}ms) — below noise floor ${noiseFloorMs}ms, gate skipped`);
            continue;
        }
        const pctP95 = prev.p95ms > 0 ? ((r.p95ms - prev.p95ms) / prev.p95ms) * 100 : 0;
        const mark = pctP95 > threshold ? '❌' : '✓';
        console.log(`[perf] ${mark} ${r.name.padEnd(28)} P95 now=${r.p95ms.toFixed(3)}ms vs baseline=${prev.p95ms.toFixed(3)}ms (${pctP95 >= 0 ? '+' : ''}${pctP95.toFixed(1)}%)`);
        if (pctP95 > threshold)
            regressions.push(`${r.name}: P95 +${pctP95.toFixed(1)}% > threshold ${threshold}%`);
    }
    if (regressions.length > 0) {
        console.error('[perf] REGRESSION DETECTED:');
        for (const r of regressions)
            console.error(`  • ${r}`);
        process.exitCode = 1;
        return;
    }
    // Optionally refresh the baseline if PERF_REFRESH_BASELINE=1.
    if (process.env.PERF_REFRESH_BASELINE === '1') {
        fs.writeFileSync(baselineFile, JSON.stringify(current, null, 2), 'utf-8');
        console.log('[perf] baseline refreshed.');
    }
    console.log('TEST_PERFORMANCE_LATENCY_OK');
}
main();
