/**
 * RED-GREEN tests for the replay player (W4.2 <-> W3.1 bridge).
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-SANDBOX-REPLAY (player)
 */
import { describe, it } from 'node:test';
import * as assert from 'node:assert/strict';
import { createReplayPlayer, realiseSequence } from './replay-player.js';
import { REPLAY_SEQUENCES } from './sandbox-replay.js';
/** A deterministic scheduler that executes immediately, ignoring delays. */
function immediateScheduler() {
    const scheduled = [];
    return {
        scheduled: scheduled,
        schedule: (cb, delayMs) => {
            const entry = { delay: delayMs, ran: false, cb };
            scheduled.push(entry);
            // Run synchronously so tests can assert the full timeline immediately.
            try {
                cb();
                entry.ran = true;
            }
            catch {
                // surface in normal flow
            }
            return () => {
                /* noop — already ran */
            };
        },
    };
}
/** A deferred scheduler that captures callbacks for manual ticking. */
function manualScheduler() {
    const queue = [];
    return {
        get pending() {
            return queue.filter((q) => !q.cancelled).length;
        },
        schedule: (cb, _delayMs) => {
            const entry = { cb, cancelled: false };
            queue.push(entry);
            return () => {
                entry.cancelled = true;
            };
        },
        tickAll() {
            while (queue.some((q) => !q.cancelled)) {
                const next = queue.find((q) => !q.cancelled);
                if (!next)
                    break;
                next.cancelled = true;
                next.cb();
            }
        },
    };
}
const firstSeq = REPLAY_SEQUENCES[0];
describe('replay player', () => {
    it('emits one row per event in order via immediate scheduler', () => {
        const sched = immediateScheduler();
        const rows = [];
        const doneCalls = [];
        const player = createReplayPlayer(firstSeq, {
            onRow: (r) => rows.push(r),
            onDone: (d) => doneCalls.push({ ok: d.ok, rowsEmitted: d.rowsEmitted }),
        }, { schedule: sched.schedule, startIso: '2026-05-12T00:00:00.000Z' });
        player.start();
        assert.equal(rows.length, firstSeq.events.length);
        assert.equal(doneCalls.length, 1);
        assert.equal(doneCalls[0].ok, true);
        assert.equal(doneCalls[0].rowsEmitted, firstSeq.events.length);
        assert.equal(player.isDone(), true);
    });
    it('speedMultiplier scales delays', () => {
        const captured = [];
        const player = createReplayPlayer(firstSeq, { onRow: () => { } }, {
            speedMultiplier: 5,
            schedule: (cb, delayMs) => {
                captured.push(delayMs);
                // Execute synchronously so the test can assert all captured delays
                cb();
                return () => { };
            },
        });
        player.start();
        const eventDelays = firstSeq.events.map((e) => e.tMs / 5);
        assert.deepEqual(captured, eventDelays);
    });
    it('stop() cancels pending events and fires done with ok=false', () => {
        const sched = manualScheduler();
        const rows = [];
        const doneCalls = [];
        const player = createReplayPlayer(firstSeq, {
            onRow: (r) => rows.push(r),
            onDone: (d) => doneCalls.push({ ok: d.ok }),
        }, { schedule: sched.schedule });
        player.start();
        assert.equal(sched.pending, firstSeq.events.length);
        player.stop();
        assert.equal(doneCalls.length, 1);
        assert.equal(doneCalls[0].ok, false);
        // After stop, ticking the scheduler must NOT emit additional rows.
        sched.tickAll();
        assert.equal(rows.length, 0);
    });
    it('start() is idempotent — second call is a no-op', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = createReplayPlayer(firstSeq, { onRow: (r) => rows.push(r) }, { schedule: sched.schedule });
        player.start();
        const firstCount = rows.length;
        player.start();
        assert.equal(rows.length, firstCount);
    });
    it('isPlaying / isDone state transitions', () => {
        const sched = manualScheduler();
        const player = createReplayPlayer(firstSeq, { onRow: () => { } }, { schedule: sched.schedule });
        assert.equal(player.isPlaying(), false);
        assert.equal(player.isDone(), false);
        player.start();
        assert.equal(player.isPlaying(), true);
        sched.tickAll();
        assert.equal(player.isDone(), true);
        assert.equal(player.isPlaying(), false);
    });
    it('empty sequence completes immediately with rowsEmitted=0', () => {
        const empty = { ...firstSeq, events: [] };
        const doneCalls = [];
        const player = createReplayPlayer(empty, { onRow: () => { }, onDone: (d) => doneCalls.push({ ok: d.ok, rowsEmitted: d.rowsEmitted }) }, { schedule: () => () => { } });
        player.start();
        assert.equal(doneCalls.length, 1);
        assert.equal(doneCalls[0].rowsEmitted, 0);
    });
    it('speedMultiplier <= 0 throws', () => {
        assert.throws(() => createReplayPlayer(firstSeq, { onRow: () => { } }, { speedMultiplier: 0 }), /speedMultiplier must be > 0/);
        assert.throws(() => createReplayPlayer(firstSeq, { onRow: () => { } }, { speedMultiplier: -1 }), /speedMultiplier must be > 0/);
    });
    it('realiseSequence produces the same rows as start() would emit', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = createReplayPlayer(firstSeq, { onRow: (r) => rows.push(r) }, { schedule: sched.schedule, startIso: '2026-05-12T00:00:00.000Z' });
        player.start();
        const direct = realiseSequence(firstSeq, '2026-05-12T00:00:00.000Z');
        assert.deepEqual(direct, rows);
    });
    it('emitted rows match runtime-event-renderer mapping', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = createReplayPlayer(firstSeq, { onRow: (r) => rows.push(r) }, { schedule: sched.schedule, startIso: '2026-05-12T00:00:00.000Z' });
        player.start();
        // First emitted row should match the first event's kind.
        assert.equal(rows[0].kind, 'tool-call', 'all sequences start with TOOL_CALL_REQUESTED');
        // Last emitted row should have tMs <= sequence.durationMs.
        assert.ok((rows[rows.length - 1].tMs ?? 0) <= firstSeq.durationMs);
    });
});
//# sourceMappingURL=replay-player.test.js.map