"use strict";
// src/test_context_composer_cache.ts
// v1.14.1 — Issue #12 regression.
//
// Asserts the compose() cache contract:
//   1. Warm calls (cache hit) return the SAME ComposeResult instance.
//   2. Different queries produce different cache entries.
//   3. OPENLIFE_CONTEXT_COMPOSER_CACHE=off bypasses the cache.
//   4. OPENLIFE_CONTEXT_COMPOSER=off still short-circuits before cache.
//   5. Test stubs (storyHits/ragHits/graphHits) are NOT cached, so they
//      stay deterministic across calls.
Object.defineProperty(exports, "__esModule", { value: true });
const ContextComposer_1 = require("./orchestrator/context/ContextComposer");
function assert(cond, msg) {
    if (!cond)
        throw new Error(`assertion failed: ${msg}`);
}
function setEnv(key, value) {
    const prev = process.env[key];
    if (value === undefined)
        delete process.env[key];
    else
        process.env[key] = value;
    return () => { if (prev === undefined)
        delete process.env[key];
    else
        process.env[key] = prev; };
}
function stub(label, score) {
    return async (_q) => [{
            kind: 'story',
            ref: `ref:${label}`,
            label,
            text: `body for ${label}`,
            score,
            tokens: 10,
        }];
}
async function scenarioCacheHit() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    // Two real (non-stubbed) calls with same query — second must hit cache.
    const first = await c.compose('cache test');
    const second = await c.compose('cache test');
    assert(first === second, 'cache hit must return identical result reference');
    console.log('✅ scenario 1: identical query returns cached ComposeResult instance');
}
async function scenarioDifferentQueries() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    const a = await c.compose('alpha query');
    const b = await c.compose('beta query');
    assert(a !== b, 'different queries must produce different result references');
    const a2 = await c.compose('alpha query');
    assert(a === a2, 'first query still cached after second query');
    console.log('✅ scenario 2: cache keyed on query — distinct queries get distinct entries');
}
async function scenarioCacheOff() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    const restore = setEnv('OPENLIFE_CONTEXT_COMPOSER_CACHE', 'off');
    try {
        const a = await c.compose('cache-off query');
        const b = await c.compose('cache-off query');
        assert(a !== b, 'cache-off flag must produce fresh result every call');
    }
    finally {
        restore();
    }
    console.log('✅ scenario 3: OPENLIFE_CONTEXT_COMPOSER_CACHE=off bypasses cache');
}
async function scenarioComposerOffShortCircuit() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    const restore = setEnv('OPENLIFE_CONTEXT_COMPOSER', 'off');
    try {
        const r = await c.compose('whatever');
        assert(r.block === '', 'composer off → empty block');
        assert(r.trace.length === 1 && r.trace[0].source === 'disabled', 'trace says disabled');
    }
    finally {
        restore();
    }
    console.log('✅ scenario 4: OPENLIFE_CONTEXT_COMPOSER=off short-circuits before cache');
}
async function scenarioStubsBypassCache() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    // Stub returns one hit. Cache should NOT remember stubbed results because
    // stubs are by definition test-injection points — caching them would
    // poison subsequent calls.
    let callCount = 0;
    const trackedStub = async (_q) => {
        callCount++;
        return [{ kind: 'story', ref: 'ref:stub', label: 'Stub', text: 'stub body', score: 1, tokens: 5 }];
    };
    await c.compose('stub query', { storyHits: trackedStub, sources: ['story'] });
    await c.compose('stub query', { storyHits: trackedStub, sources: ['story'] });
    assert(callCount === 2, `stub must be invoked twice, got ${callCount} call(s) — cache leaked`);
    console.log('✅ scenario 5: stubbed sources bypass cache (deterministic test isolation)');
}
async function scenarioLatency() {
    (0, ContextComposer_1._resetComposerCache)();
    const c = new ContextComposer_1.ContextComposer();
    // Real compose, no stub. Warm latency target ≤100ms.
    await c.compose('warm-up query'); // cold
    const start = Date.now();
    for (let i = 0; i < 10; i++) {
        await c.compose('warm-up query'); // all cache hits
    }
    const avg = (Date.now() - start) / 10;
    assert(avg <= 50, `warm cache hit avg should be ≤50ms, got ${avg}ms`);
    console.log(`✅ scenario 6: warm cache hit avg ${avg.toFixed(1)}ms across 10 calls (target ≤50ms, issue #12 spec ≤100ms)`);
}
(async () => {
    try {
        await scenarioCacheHit();
        await scenarioDifferentQueries();
        await scenarioCacheOff();
        await scenarioComposerOffShortCircuit();
        await scenarioStubsBypassCache();
        await scenarioLatency();
        (0, ContextComposer_1._resetComposerCache)(); // clean up for downstream tests in the same process
        console.log('TEST_CONTEXT_COMPOSER_CACHE_OK');
    }
    catch (err) {
        console.error('TEST_CONTEXT_COMPOSER_CACHE_FAIL', err);
        process.exit(1);
    }
})();
