import { __awaiter } from "tslib";
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
// Side-effect import — MUST precede the driver import so it subclasses jsdom's
// EventTarget (CustomEvent dispatch then works in node).
import '../../components/chat-driver/align-event-globals';
import { OrchestratingDriver } from '../../components/orchestrating-driver/orchestrating-driver';
import { clearDiagnosticsCursorRegistry } from './diagnostics-cursors';
import { clearPersisterRegistry } from './persister-registry';
import { SessionPersister } from './session-persister';
// GENC-1351 — end-to-end save→restore→route→re-activate proof. The persister unit
// tests exercise `persistSession`/`restore` against a FAKE driver; the driver pin/
// flow-owner tests exercise routing against a real driver. NOTHING wired the two
// together — the exact integration that broke once (bug #2: the live agent's
// `serialize()` was read via `driver.getActiveAgent()` — a host minting fresh
// configs handed back a never-activated closure whose `serialize()` returned
// undefined, so the machine reset on reload). This test closes that gap with a REAL
// `OrchestratingDriver` + a REAL `SessionPersister` + an in-memory provider:
//   persist (capture the LIVE agent's state) → restore (prime + re-lock routing) →
//   send (route back to the flow-owner without re-classifying) → re-activate aligned.
// `Planner` is `excludeFromClassifier` AND never pinned, so a turn landing on it after
// restore proves the RESTORED flow-owner lock (not the pin, not the classifier) routed
// it — the `setFlowOwner`-on-restore half of the fix.
const KEY = 'e2e-session';
const makeRegistry = (provider) => ({
    get: () => provider,
    default: () => provider,
    defaultName: () => 'test',
    names: () => ['test'],
    getStatus: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
    listStatuses: () => __awaiter(void 0, void 0, void 0, function* () { return []; }),
});
/** A provider that ends every turn immediately with a plain-text reply (no tools). */
const noToolProvider = () => ({
    chat: (_history, _userMessage, _options) => __awaiter(void 0, void 0, void 0, function* () { return ({ role: 'assistant', content: 'ok' }); }),
});
const agentCfg = (overrides) => (Object.assign({ description: 'test agent' }, overrides));
/** In-memory persistence provider (single session), enough for one save + one load. */
function makeMemoryProvider() {
    let stored = null;
    return {
        saveDebounceMs: 0,
        save: (_k, snap) => __awaiter(this, void 0, void 0, function* () {
            stored = snap;
        }),
        load: () => __awaiter(this, void 0, void 0, function* () { return stored; }),
        clear: () => __awaiter(this, void 0, void 0, function* () {
            stored = null;
        }),
        get current() {
            return stored;
        },
    };
}
/** The functional store shape the persister reads scalars from / hydrates into. */
function makeStoreFake(over = {}) {
    return {
        store: {
            aiAssistant: Object.assign({ messages: [], flowOwnerAgentName: null, flowActivationPrompt: null, activeAgent: undefined, pinnedAgentName: null, 
                // The persister reads `sessionUsage.costUsd` when building a snapshot, so this
                // has to be the object rather than a bare cost scalar.
                sessionUsage: {
                    costUsd: 0,
                    uncachedInputTokens: 0,
                    cacheReadTokens: 0,
                    cacheWriteTokens: 0,
                    outputTokens: 0,
                }, contextTokens: 0, contextLimit: 0, activeModel: undefined, activeProviderName: undefined, restoring: false }, over),
        },
        actions: {
            aiAssistant: {
                loadSession: () => { },
                setInputValue: () => { },
            },
        },
    };
}
function makePersister(driver, storeFake, provider) {
    const deps = {
        stateKey: KEY,
        getPersistence: () => ({ enabled: true, provider: provider }),
        getAgentNames: () => ['Genesis Assistant', 'Planner'],
        getDriver: () => driver,
        getStore: () => storeFake,
        getDiagnosticEntries: () => [],
    };
    return new SessionPersister(deps);
}
const tick = () => new Promise((r) => {
    setTimeout(r, 0);
});
const Suite = createLogicSuite('stateful-restore-e2e');
Suite.before.each(() => {
    clearPersisterRegistry();
    clearDiagnosticsCursorRegistry();
});
Suite('a flow-owned stateful agent survives persist → restore → resume, routing back without re-classifying', () => __awaiter(void 0, void 0, void 0, function* () {
    var _a, _b;
    const provider = makeMemoryProvider();
    // ── Persist side ────────────────────────────────────────────────────────────
    // `stateA` is the Planner's live state bag; `serialize` closes over it, exactly
    // like a real stateful agent's machine snapshot.
    let stateA = { cursor: 0 };
    const planner1 = agentCfg({
        name: 'Planner',
        excludeFromClassifier: true,
        onActivate: (_a) => __awaiter(void 0, [_a], void 0, function* ({ restored }) {
            if (restored !== undefined)
                stateA = restored;
        }),
        serialize: () => stateA,
        systemPrompt: () => 'planner',
    });
    const driver1 = new OrchestratingDriver(makeRegistry(noToolProvider()), [agentCfg({ name: 'Genesis Assistant' }), planner1], { sessionKey: KEY });
    // Flow-owned but NOT pinned, then run a turn: the flow-owner lock routes the
    // Planner (it's excludeFromClassifier, so the classifier could never pick it).
    driver1.setFlowOwner('Planner');
    yield driver1.sendMessage('go');
    assert.is((_a = driver1.getActiveAgent()) === null || _a === void 0 ? void 0 : _a.name, 'Planner', 'flow-owner routed the Planner on persist side');
    // The flow advances — mutate the SAME object `serialize()` returns.
    stateA.cursor = 3;
    // The store mirrors what main.ts keeps: the flow-owner lock lives in the store
    // (that's the scalar `persistSession` reads).
    const storeFake1 = makeStoreFake({ flowOwnerAgentName: 'Planner' });
    const persister1 = makePersister(driver1, storeFake1, provider);
    persister1.scheduleSave();
    yield tick();
    yield tick();
    const saved = provider.current;
    assert.ok(saved, 'a snapshot was persisted');
    assert.is(saved.flowOwnerAgentName, 'Planner', 'flow-owner lock persisted');
    // Bug-#2 assertion: the LIVE (activated) agent's serialize was captured via
    // driver.getActiveAgent(), so the advanced state is in the snapshot.
    assert.equal(saved.agentSnapshots.Planner, { cursor: 3 }, "live agent's advanced state captured (not a never-activated closure)");
    // ── Restore side (fresh instances share the same in-memory provider) ──────────
    let stateB = { cursor: -1 }; // sentinel; restore must overwrite
    const planner2 = agentCfg({
        name: 'Planner',
        excludeFromClassifier: true,
        onActivate: (_a) => __awaiter(void 0, [_a], void 0, function* ({ restored }) {
            if (restored !== undefined)
                stateB = restored;
        }),
        serialize: () => stateB,
        systemPrompt: () => 'planner',
    });
    const driver2 = new OrchestratingDriver(makeRegistry(noToolProvider()), [agentCfg({ name: 'Genesis Assistant' }), planner2], { sessionKey: KEY });
    const storeFake2 = makeStoreFake();
    const persister2 = makePersister(driver2, storeFake2, provider);
    yield persister2.restore();
    // The restored flow-owner lock (setFlowOwner-on-restore) routes the next turn
    // back to the Planner without re-classifying.
    yield driver2.sendMessage('again');
    assert.is((_b = driver2.getActiveAgent()) === null || _b === void 0 ? void 0 : _b.name, 'Planner', 'restored flow-owner routed the Planner (excludeFromClassifier + unpinned ⇒ only the restored lock could have)');
    // primeRestoredAgentState → onActivate({ restored }) fed the snapshot back, so
    // the fresh Planner resumed exactly where the persisted one left off.
    assert.equal(stateB, { cursor: 3 }, 'restored agent re-activated aligned to the persisted state');
}));
Suite.run();
