"use strict";
/**
 * RED-GREEN tests for the replay player (W4.2 <-> W3.1 bridge).
 *
 * @connexum/ai-governance  F-NEW-PRODUCT-SANDBOX-REPLAY (player)
 */
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 node_test_1 = require("node:test");
const assert = __importStar(require("node:assert/strict"));
const replay_player_1 = require("./replay-player");
const sandbox_replay_1 = require("./sandbox-replay");
/** 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 = sandbox_replay_1.REPLAY_SEQUENCES[0];
(0, node_test_1.describe)('replay player', () => {
    (0, node_test_1.it)('emits one row per event in order via immediate scheduler', () => {
        const sched = immediateScheduler();
        const rows = [];
        const doneCalls = [];
        const player = (0, replay_player_1.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);
    });
    (0, node_test_1.it)('speedMultiplier scales delays', () => {
        const captured = [];
        const player = (0, replay_player_1.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);
    });
    (0, node_test_1.it)('stop() cancels pending events and fires done with ok=false', () => {
        const sched = manualScheduler();
        const rows = [];
        const doneCalls = [];
        const player = (0, replay_player_1.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);
    });
    (0, node_test_1.it)('start() is idempotent — second call is a no-op', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = (0, replay_player_1.createReplayPlayer)(firstSeq, { onRow: (r) => rows.push(r) }, { schedule: sched.schedule });
        player.start();
        const firstCount = rows.length;
        player.start();
        assert.equal(rows.length, firstCount);
    });
    (0, node_test_1.it)('isPlaying / isDone state transitions', () => {
        const sched = manualScheduler();
        const player = (0, replay_player_1.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);
    });
    (0, node_test_1.it)('empty sequence completes immediately with rowsEmitted=0', () => {
        const empty = { ...firstSeq, events: [] };
        const doneCalls = [];
        const player = (0, replay_player_1.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);
    });
    (0, node_test_1.it)('speedMultiplier <= 0 throws', () => {
        assert.throws(() => (0, replay_player_1.createReplayPlayer)(firstSeq, { onRow: () => { } }, { speedMultiplier: 0 }), /speedMultiplier must be > 0/);
        assert.throws(() => (0, replay_player_1.createReplayPlayer)(firstSeq, { onRow: () => { } }, { speedMultiplier: -1 }), /speedMultiplier must be > 0/);
    });
    (0, node_test_1.it)('realiseSequence produces the same rows as start() would emit', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = (0, replay_player_1.createReplayPlayer)(firstSeq, { onRow: (r) => rows.push(r) }, { schedule: sched.schedule, startIso: '2026-05-12T00:00:00.000Z' });
        player.start();
        const direct = (0, replay_player_1.realiseSequence)(firstSeq, '2026-05-12T00:00:00.000Z');
        assert.deepEqual(direct, rows);
    });
    (0, node_test_1.it)('emitted rows match runtime-event-renderer mapping', () => {
        const sched = immediateScheduler();
        const rows = [];
        const player = (0, replay_player_1.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