"use strict";
// src/test_context_composer.ts
// v1.14.0 — Composer behavior with deterministic source impls.
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 hit(kind, ref, score, tokens, text = 'body') {
    return { kind, ref, label: `${kind}-${ref}`, text, score, tokens };
}
async function main() {
    // 1. baseline — all three sources, no budget pressure
    const comp = new ContextComposer_1.ContextComposer();
    let r = await comp.compose('query', {
        storyHits: async () => [hit('story', 's1', 1.0, 50)],
        ragHits: async () => [hit('chunk', 'c1', 0.9, 100)],
        graphHits: async () => [hit('symbol', 'g1', 0.8, 30)],
        budgetTokens: 500,
    });
    assert(r.hits.length === 3, `expected 3 kept hits, got ${r.hits.length}`);
    assert(r.block.includes('Contexto do projeto'), 'block has heading');
    assert(r.usedTokens === 180, `expected 180 tokens, got ${r.usedTokens}`);
    console.log(`✓ scenario 1: three sources packed under budget (used=${r.usedTokens})`);
    // 2. budget enforcement
    r = await comp.compose('query', {
        storyHits: async () => [hit('story', 's1', 1.0, 200)],
        ragHits: async () => [hit('chunk', 'c1', 0.9, 200)],
        graphHits: async () => [hit('symbol', 'g1', 0.8, 200)],
        budgetTokens: 250,
    });
    assert(r.hits.length === 1, `budget should drop hits, got ${r.hits.length} hits`);
    assert(r.usedTokens <= 250, `tokens within budget (got ${r.usedTokens})`);
    console.log(`✓ scenario 2: budget enforced — kept ${r.hits.length} hit, dropped 2`);
    // 3. source weighting — story should beat lower-scored chunk after weighting
    r = await comp.compose('query', {
        storyHits: async () => [hit('story', 's1', 0.5, 50)],
        ragHits: async () => [hit('chunk', 'c1', 0.6, 50)],
        graphHits: async () => [],
        budgetTokens: 50,
    });
    // story weight 1.0 → 0.5; chunk weight 0.85 → 0.51 → chunk should rank first.
    assert(r.hits.length === 1, 'budget keeps only 1');
    assert(r.hits[0].kind === 'chunk', `expected chunk first after weighting, got ${r.hits[0].kind}`);
    console.log(`✓ scenario 3: cross-source weighting orders results predictably`);
    // 4. opt-out flag
    process.env.OPENLIFE_CONTEXT_COMPOSER = 'off';
    r = await comp.compose('query', {
        storyHits: async () => [hit('story', 's1', 1.0, 50)],
    });
    assert(r.block === '', 'block should be empty when disabled');
    assert(r.hits.length === 0, 'no hits when disabled');
    delete process.env.OPENLIFE_CONTEXT_COMPOSER;
    console.log(`✓ scenario 4: OPENLIFE_CONTEXT_COMPOSER=off disables the composer`);
    // 5. failure in one source does not break the composer
    r = await comp.compose('query', {
        storyHits: async () => { throw new Error('boom'); },
        ragHits: async () => [hit('chunk', 'c1', 0.9, 50)],
        graphHits: async () => [],
        budgetTokens: 1000,
    });
    assert(r.hits.length === 1, `composer should survive a failing source, got ${r.hits.length}`);
    assert(r.trace.some(t => t.source === 'story-error'), 'failure is recorded in trace');
    console.log(`✓ scenario 5: a failing source is isolated, others still contribute`);
    // 6. empty everything → empty block
    r = await comp.compose('query', {
        storyHits: async () => [],
        ragHits: async () => [],
        graphHits: async () => [],
    });
    assert(r.block === '', 'empty block when nothing matches');
    console.log(`✓ scenario 6: empty result yields empty block`);
    console.log('TEST_CONTEXT_COMPOSER_OK');
}
main().catch(err => {
    console.error(`TEST_CONTEXT_COMPOSER_FAIL: ${err instanceof Error ? err.message : String(err)}`);
    process.exit(1);
});
