import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { DOM } from '@genesislcap/web-core';
import { FoundationAiAssistant } from './main';
// Hold a reference so the custom-element registration isn't tree-shaken.
FoundationAiAssistant;
// GENC-1511 — the blocking "not saving" modal has MORE THAN ONE reporter: this element's own
// session persister (`sessions/<key>`), and a host reporting its own keyspaces through the public
// API (Create's `projects/<id>.json`, which holds the app rather than the transcript).
//
// It was first built on a single `persistenceBroken` boolean, and review caught what that costs:
// whichever reporter recovered FIRST closed the modal for both, and the host's own re-entry guard
// then kept it from ever being raised again. This suite pins the per-source behaviour so that
// collision cannot come back.
//
// Like `blocked-state.test.ts`, this exercises the METHODS the template bindings read rather than
// the rendered DOM: `document.createElement` upgrades the element (constructor) without connecting
// it, so `connectedCallback` never runs and nothing subscribes to `agenticActivityBus` — which
// would otherwise hold the test runner's event loop open.
const Suite = createLogicSuite('FoundationAiAssistant persistence-broken sources');
/** A fresh, unconnected element. Nothing here needs a session store. */
const element = () => document.createElement('foundation-ai-assistant');
/** The modal's visible copy, as the template bindings read it. */
const copy = (el) => ({
    broken: el.persistenceBroken,
    message: el.persistenceBrokenMessage,
    detail: el.persistenceBrokenDetail,
});
const SESSION = 'session';
const PROJECT = 'project';
Suite('a single reporter raises and withdraws its own warning', () => {
    const el = element();
    assert.is(el.persistenceBroken, false);
    el.reportPersistenceBroken({ detail: '2 messages have not been saved.', source: SESSION });
    assert.is(el.persistenceBroken, true);
    assert.is(el.persistenceBrokenDetail, '2 messages have not been saved.');
    el.reportPersistenceRecovered(SESSION);
    assert.is(el.persistenceBroken, false);
    assert.is(el.persistenceBrokenMessage, undefined);
    assert.is(el.persistenceBrokenDetail, undefined);
});
Suite('one source recovering does NOT withdraw another still-broken source', () => {
    const el = element();
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    el.reportPersistenceBroken({ message: 'project copy', source: PROJECT });
    // The session's own save lands. Its warning goes; the project's must not.
    el.reportPersistenceRecovered(SESSION);
    assert.is(el.persistenceBroken, true, 'modal must stay up while the project store is failing');
    assert.is(el.persistenceBrokenMessage, 'project copy', 'and must still describe THAT failure');
    el.reportPersistenceRecovered(PROJECT);
    assert.is(el.persistenceBroken, false, 'only once nothing is broken');
});
Suite('a later report does not overwrite an earlier source with its own copy', () => {
    const el = element();
    el.reportPersistenceBroken({
        message: 'project copy',
        detail: 'project detail',
        source: PROJECT,
    });
    // The session reports with NO message override — it wants the template's session default.
    // Before per-source tracking this assigned `undefined` over the project wording, so the modal
    // claimed "this session has stopped saving" about a failed PROJECT write.
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    assert.is(el.persistenceBrokenMessage, undefined, 'newest source wins while it is broken');
    assert.is(el.persistenceBrokenDetail, 'session detail');
    // ...and when the newest recovers, the copy falls BACK to the survivor rather than staying
    // on the recovered source's wording (or reverting to the default).
    el.reportPersistenceRecovered(SESSION);
    assert.is(el.persistenceBrokenMessage, 'project copy');
    assert.is(el.persistenceBrokenDetail, 'project detail');
    assert.is(el.persistenceBroken, true);
});
Suite('recovering an unknown or already-recovered source is a no-op', () => {
    const el = element();
    el.reportPersistenceBroken({ message: 'project copy', source: PROJECT });
    el.reportPersistenceRecovered(SESSION); // never reported
    assert.equal(copy(el), { broken: true, message: 'project copy', detail: undefined });
    el.reportPersistenceRecovered(PROJECT);
    el.reportPersistenceRecovered(PROJECT); // twice
    assert.equal(copy(el), { broken: false, message: undefined, detail: undefined });
});
Suite('re-reporting the same source refreshes its copy without duplicating it', () => {
    const el = element();
    el.reportPersistenceBroken({ detail: '2 messages have not been saved.', source: SESSION });
    el.reportPersistenceBroken({ detail: '5 messages have not been saved.', source: SESSION });
    assert.is(el.persistenceBrokenDetail, '5 messages have not been saved.');
    // One recovery must clear it — a second entry for the same source would leave it stuck on.
    el.reportPersistenceRecovered(SESSION);
    assert.is(el.persistenceBroken, false);
});
Suite('a host that omits the source gets a default it can still recover', () => {
    const el = element();
    el.reportPersistenceBroken({ message: 'host copy' });
    assert.is(el.persistenceBroken, true);
    el.reportPersistenceRecovered();
    assert.is(el.persistenceBroken, false);
});
Suite("a host's default source is independent of the session's", () => {
    const el = element();
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    el.reportPersistenceBroken({ message: 'host copy' });
    el.reportPersistenceRecovered(); // the host's
    assert.is(el.persistenceBroken, true, 'the session failure is still live');
    assert.is(el.persistenceBrokenDetail, 'session detail');
});
// GENC-1511 (PR #2472 review) — the deferred show is a separate race from the source tracking.
// `persistenceBrokenChanged` queues the show via `DOM.queueUpdate`, and the queued callback used
// not to re-read the flag. A `true → false` inside the same batch runs the `!broken` branch against
// a modal that was never shown (both statements no-op), and the callback then raised a blocking,
// NON-DISMISSIBLE modal for a problem that had already fixed itself — with no further change event
// left to close it, so the only way out was a reload nobody needed.
//
// Reachable because `maybeReportBroken` fires before `provider.save` in `persistSession`, so a
// report and the write that withdraws it can share a tick.
/** Minimal stand-in for the design-system modal, recording what the callback did to it. */
function modalStub() {
    const calls = { show: 0, close: 0 };
    const dialog = document.createElement('dialog');
    return {
        calls,
        modal: {
            open: false,
            mainElement: dialog,
            show() {
                calls.show += 1;
                this.open = true;
            },
            close() {
                calls.close += 1;
                this.open = false;
            },
        },
    };
}
/**
 * Drain the FAST update queue SYNCHRONOUSLY.
 *
 * `DOM.processUpdates()` and not an `await setTimeout(0)`: the queue lands on
 * `requestAnimationFrame`, which a zero-delay timer does not reliably cover. A timer-based flush
 * made these tests pass or fail on scheduling luck — the "does not show" assertion passed for the
 * wrong reason, and its positive control passed only because earlier tests had pumped the queue.
 */
const flush = () => DOM.processUpdates();
const withModal = (el, modal) => {
    el.persistenceModal = modal;
};
// POSITIVE CONTROL for the test below. Without it, a queue that never drains in this environment
// would make the "does not show" assertion pass for entirely the wrong reason.
Suite('the deferred show DOES raise the modal when the failure is still live', () => {
    const el = element();
    const { calls, modal } = modalStub();
    withModal(el, modal);
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    flush();
    assert.is(calls.show, 1, 'precondition: the queued callback runs in this environment');
    assert.is(modal.open, true);
});
Suite('a recovery inside the same batch cancels the deferred show', () => {
    const el = element();
    const { calls, modal } = modalStub();
    withModal(el, modal);
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    el.reportPersistenceRecovered(SESSION); // lands before the queue flushes
    flush();
    assert.is(el.persistenceBroken, false);
    assert.is(calls.show, 0, 'must not raise a blocking modal for a resolved failure');
    assert.is(modal.open, false);
});
Suite('a recovery of ONE source still shows for the source left broken', () => {
    const el = element();
    const { calls, modal } = modalStub();
    withModal(el, modal);
    el.reportPersistenceBroken({ detail: 'session detail', source: SESSION });
    el.reportPersistenceBroken({ message: 'project copy', source: PROJECT });
    el.reportPersistenceRecovered(SESSION);
    flush();
    assert.is(el.persistenceBroken, true);
    assert.is(calls.show, 1, 'the project failure still warrants the modal');
    assert.is(el.persistenceBrokenMessage, 'project copy');
});
Suite.run();
