import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { assembleDebugLog, diagnosticsMessageKey } from './diagnostics';
// GENC-1351 §5.8: `assembleDebugLog` reassembles the forward-only diagnostics
// stream back into the `{ readme, timeline, meta }` debug-log shape — timeline
// sorted by timestamp, meta-snapshots pulled out with the latest winning.
const README = ['read me'];
const Suite = createLogicSuite('assembleDebugLog');
Suite('sorts the timeline by timestamp, tie-breaking event → turn → message', () => {
    const entries = [
        { kind: 'message', timestamp: '2026-01-01T00:00:02.000Z', content: 'later msg' },
        { kind: 'turn', timestamp: '2026-01-01T00:00:01.000Z', turnIndex: '0' },
        { kind: 'event', timestamp: '2026-01-01T00:00:01.000Z', type: 'turn.start' },
        { kind: 'message', timestamp: '2026-01-01T00:00:01.000Z', content: 'first msg' },
    ];
    const { timeline } = assembleDebugLog(entries, README);
    // Co-timestamped @ …01: event, then turn, then message; the …02 message last.
    assert.equal(timeline.map((e) => e.kind), ['event', 'turn', 'message', 'message']);
    assert.is(timeline[3].content, 'later msg');
});
Suite('pulls meta-snapshots out of the timeline and surfaces the latest as `meta`', () => {
    const entries = [
        { kind: 'meta-snapshot', timestamp: '2026-01-01T00:00:01.000Z', meta: { model: 'old' } },
        { kind: 'message', timestamp: '2026-01-01T00:00:02.000Z', content: 'hi' },
        { kind: 'meta-snapshot', timestamp: '2026-01-01T00:00:03.000Z', meta: { model: 'new' } },
    ];
    const { timeline, meta } = assembleDebugLog(entries, README);
    assert.equal(timeline.map((e) => e.kind), ['message'], 'meta-snapshots do not leak into the timeline');
    assert.equal(meta, { model: 'new' }, 'the newest meta-snapshot surfaces as top-level meta');
});
Suite('re-prepends the passed readme and tolerates an empty stream', () => {
    const empty = assembleDebugLog([], README);
    assert.is(empty.readme, README);
    assert.equal(empty.timeline, []);
    assert.is(empty.meta, undefined, 'no meta-snapshot → undefined meta');
});
Suite('reassembles two loads into one lifetime timeline (the stitch)', () => {
    // Load 1's appended entries + load 2's — as loadDiagnostics would return them,
    // in arbitrary order — stitch back chronologically.
    const load2 = [
        { kind: 'message', timestamp: '2026-01-01T00:10:00.000Z', content: 'reload msg' },
        { kind: 'meta-snapshot', timestamp: '2026-01-01T00:10:00.000Z', meta: { load: 2 } },
    ];
    const load1 = [
        { kind: 'message', timestamp: '2026-01-01T00:00:00.000Z', content: 'first msg' },
        { kind: 'meta-snapshot', timestamp: '2026-01-01T00:00:00.000Z', meta: { load: 1 } },
    ];
    const { timeline, meta } = assembleDebugLog([...load2, ...load1], README);
    assert.equal(timeline.filter((e) => e.kind === 'message').map((e) => e.content), ['first msg', 'reload msg'], 'messages ordered across loads regardless of input order');
    assert.equal(meta, { load: 2 }, 'newest load’s meta-snapshot surfaces');
});
Suite('collapses exact-duplicate entries but keeps genuinely-distinct ones', () => {
    // Two element instances (bubble + panel) can each append the same message, and a
    // reload's second instance can re-append it — byte-identical copies. Two real
    // `assistant.connected` (from two instances) differ by `index` and must be kept.
    const msg = {
        kind: 'message',
        timestamp: '2026-01-01T00:00:01.000Z',
        role: 'user',
        content: 'hi',
    };
    const connA = {
        kind: 'event',
        timestamp: '2026-01-01T00:00:00.000Z',
        type: 'assistant.connected',
        index: 0,
    };
    const connB = Object.assign(Object.assign({}, connA), { index: 1 });
    const { timeline } = assembleDebugLog([msg, connA, Object.assign({}, msg), connB, connA], README);
    assert.is(timeline.filter((e) => e.kind === 'message').length, 1, 'identical message collapsed');
    assert.is(timeline.filter((e) => e.type === 'assistant.connected').length, 2, 'two distinct connects (different index) kept; the repeated connA collapsed');
});
Suite.run();
// GENC-1461: the reasoning/narration/answer split (GENC-1411) emits several messages from ONE model
// response, so they share `timestamp` + `role` + `subAgentOf`. The message identity key must still
// tell them apart — otherwise the forward-capture dedup treats them as one and drops all but the
// first (a server generation's answer message vanished from the persisted download while its
// reasoning survived).
const KeySuite = createLogicSuite('diagnosticsMessageKey');
KeySuite('gives co-timestamped split messages (reasoning vs answer) distinct keys', () => {
    const base = {
        kind: 'message',
        role: 'assistant',
        timestamp: '2026-01-01T00:00:05.000Z',
    };
    const reasoning = Object.assign(Object.assign({}, base), { category: 'reasoning', content: 'thinking…' });
    const answer = Object.assign(Object.assign({}, base), { content: 'the generated code' }); // no category = the answer
    const kReasoning = diagnosticsMessageKey(reasoning);
    const kAnswer = diagnosticsMessageKey(answer);
    assert.ok(kReasoning, 'reasoning message has a key');
    assert.ok(kAnswer, 'answer message has a key');
    assert.not.equal(kReasoning, kAnswer, 'a reasoning and an answer split from one response must not collide');
});
KeySuite('still collapses a genuine re-append of the same message (same key)', () => {
    const m = {
        kind: 'message',
        role: 'assistant',
        timestamp: '2026-01-01T00:00:05.000Z',
        category: 'reasoning',
        content: 'thinking…',
    };
    // Two instances / a reload re-appending the SAME message must still share one key.
    assert.equal(diagnosticsMessageKey(m), diagnosticsMessageKey(Object.assign({}, m)));
});
KeySuite('returns null when the entry has no timestamp (treated as always-new)', () => {
    assert.is(diagnosticsMessageKey({ kind: 'message', role: 'assistant' }), null);
});
KeySuite.run();
