import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import { clearCostSessionHistory } from '../utils/cost-session-history';
import { emptyUsage } from '../utils/sum-usage';
import { FoundationAiAssistant } from './main';
// Hold a reference so the custom-element registration isn't tree-shaken.
FoundationAiAssistant;
// GENC-1475. Pins the banking behaviour behind the Usage tab's lifetime figures: a row must
// report a project's LIFETIME usage whether or not the transcript is restored on load, and it
// must never grow by re-counting spend it already holds.
//
// Two earlier attempts at this got it wrong in opposite directions, which is what these tests
// exist to catch:
//   - projecting the transcript straight onto the row → the row shrank on every refresh in
//     hosts where the transcript does not persist;
//   - re-deriving the banked figure whenever the rows reloaded → `chatConfig` rebinding
//     mid-session (hosts build it in a template expression) folded the live session into its
//     own baseline, compounding on every rebind.
//
// Like `popout-interaction-gate.test.ts` this deliberately does NOT mount the assistant:
// `document.createElement` upgrades the element (constructor only) without connecting, so
// `connectedCallback` never runs, nothing subscribes to `agenticActivityBus`, and its
// cross-tab `BroadcastChannel` — opened lazily on first use — is never created. No teardown
// needed, and the runner's event loop stays closed.
const Suite = createLogicSuite('FoundationAiAssistant cost session banking');
const PROJECT = 'proj-1';
const usage = (costUsd, outputTokens = 100) => (Object.assign(Object.assign({}, emptyUsage()), { costUsd,
    outputTokens }));
/**
 * An unconnected element with just enough session state wired for `finalizeCostSession`:
 * the live usage it projects, the transcript it titles from, and the key it upserts against.
 */
function element(options) {
    var _a, _b;
    const el = document.createElement('foundation-ai-assistant');
    const messages = [{ role: 'user', content: 'build me a blotter' }];
    const internals = el;
    internals._sessionRef = {
        store: {
            aiAssistant: {
                state: 'idle',
                messages,
                sessionUsage: options.live,
                // Redux-backed like `messages`, so it goes in the store rather than through the
                // element setter (which would dispatch through `actions` we don't fake). Present at
                // all to keep `collectSessionModels` from warning about an empty registry per write.
                providerStatuses: [
                    { name: 'anthropic', status: { provider: 'anthropic', model: 'sonnet' } },
                ],
            },
        },
    };
    internals._activeStateKey = PROJECT;
    el.chatConfig = {
        costHistory: { enabled: true, scope: options.scope, title: 'Trade Blotter' },
    };
    // `persistenceActive` is `enabled && provider`; a bare object is provider enough here since
    // no cost-history methods are on it, so the localStorage path is used for writes.
    el.persistence = options.persistenceActive
        ? { enabled: true, provider: {} }
        : { enabled: false };
    el.costSessionHistory = (_a = options.rows) !== null && _a !== void 0 ? _a : [];
    // LAST, deliberately: assigning `chatConfig` above fires `chatConfigChanged` →
    // `reloadCostSessionHistory`, which sets this flag itself. Setting it any earlier would be
    // silently overwritten — which is exactly how this helper was wrong first time round.
    internals._costHistoryLoaded = (_b = options.loaded) !== null && _b !== void 0 ? _b : true;
    return el;
}
const row = (over = {}) => (Object.assign({ id: PROJECT, projectKey: PROJECT, title: 'Trade Blotter', updatedAt: '2026-08-01T00:00:00.000Z', usage: usage(10), banked: usage(4), models: [] }, over));
Suite('writes the live session as lifetime for a project with no prior row', () => {
    const scope = 'bank-fresh';
    clearCostSessionHistory(scope);
    const el = element({ scope, live: usage(3) });
    el.finalizeCostSession();
    assert.is(el.costSessionHistory.length, 1);
    assert.is(el.costSessionHistory[0].usage.costUsd, 3);
    assert.equal(el.costSessionHistory[0].banked, emptyUsage(), 'nothing came before');
    clearCostSessionHistory(scope);
});
Suite('restored transcript: repeat writes are stable, not compounding', () => {
    // The transcript is authoritative (persistence on), so it re-proves the 6 sitting on top
    // of the 4 already banked. The row must land on 10 no matter how many times we write —
    // this is the `chatConfig`-rebind double-count.
    const scope = 'bank-stable';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(6),
        rows: [row({ usage: usage(10), banked: usage(4) })],
        persistenceActive: true,
    });
    for (let write = 0; write < 3; write += 1) {
        el.finalizeCostSession();
        assert.is(el.costSessionHistory.length, 1, `write ${write}: still one row`);
        assert.is(el.costSessionHistory[0].usage.costUsd, 10, `write ${write}: total held`);
    }
    clearCostSessionHistory(scope);
});
Suite('a reload mid-session does not re-bank what the row already holds', () => {
    // The regression: `chatConfig` rebinds (a new object from a host template expression),
    // reloadCostSessionHistory runs, and the rows it re-reads now contain our own upsert. The
    // banked memo must survive that, or the live session is added to its own baseline.
    const scope = 'bank-rebind';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(6),
        rows: [row({ usage: usage(10), banked: usage(4) })],
        persistenceActive: true,
    });
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 10);
    // Rebind chatConfig exactly as a host template would — a fresh object, same values.
    el.chatConfig = {
        costHistory: { enabled: true, scope, title: 'Trade Blotter' },
    };
    el.chatConfigChanged();
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 10, 'rebind must not inflate the row');
    clearCostSessionHistory(scope);
});
Suite('no restore: the prior total is absorbed so the row never shrinks', () => {
    var _a;
    // The mirror failure. The transcript starts empty every refresh, so this page-load's 2 must
    // be ADDED to the 10 already recorded rather than replacing it.
    const scope = 'bank-absorb';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(2),
        rows: [row({ usage: usage(10), banked: usage(4) })],
        persistenceActive: false,
    });
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 12);
    assert.is((_a = el.costSessionHistory[0].banked) === null || _a === void 0 ? void 0 : _a.costUsd, 10, 'whole prior total banked');
    clearCostSessionHistory(scope);
});
Suite('skips the write until the ledger has loaded', () => {
    // A provider read is async. Banking from a ledger that simply has not arrived would read as
    // "nothing came before" and rewrite the row down to this turn.
    const scope = 'bank-unloaded';
    clearCostSessionHistory(scope);
    const el = element({ scope, live: usage(3), loaded: false });
    el.finalizeCostSession();
    assert.is(el.costSessionHistory.length, 0, 'no row written while unloaded');
    el._costHistoryLoaded = true;
    el.finalizeCostSession();
    assert.is(el.costSessionHistory.length, 1, 'writes once loaded');
    clearCostSessionHistory(scope);
});
Suite('clearing history drops the banked figure so it cannot be resurrected', () => {
    const scope = 'bank-cleared';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(2),
        rows: [row({ usage: usage(10), banked: usage(4) })],
        persistenceActive: false,
    });
    el.clearCostSessionHistory();
    assert.is(el.costSessionHistory.length, 0);
    // The next turn must start from zero, not resurrect the 10 the user just discarded.
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 2);
    clearCostSessionHistory(scope);
});
Suite('deleting this project row drops its banked figure too', () => {
    const scope = 'bank-removed';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(2),
        rows: [row({ usage: usage(10), banked: usage(4) })],
        persistenceActive: false,
    });
    // Bank first, so the memo is populated before the removal — the ordering that matters.
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 12);
    el.removeCostSessionFromHistory(PROJECT);
    assert.is(el.costSessionHistory.length, 0);
    el.finalizeCostSession();
    assert.is(el.costSessionHistory[0].usage.costUsd, 2, 'removed spend must not return');
    clearCostSessionHistory(scope);
});
Suite('lifetime usage sums every row, per bucket', () => {
    const scope = 'bank-lifetime';
    clearCostSessionHistory(scope);
    const el = element({
        scope,
        live: usage(1),
        rows: [
            row({ id: 'a', projectKey: 'a', usage: usage(2, 30) }),
            row({ id: 'b', projectKey: 'b', usage: usage(3, 40) }),
        ],
    });
    assert.is(el.cumulativeUsage.costUsd, 5);
    assert.is(el.cumulativeUsage.outputTokens, 70);
    assert.is(el.cumulativeCostUsd, 5);
    // Rows only — adding the live session would count the open project twice, since its own
    // row is kept current on every transcript change.
    assert.is(el.cumulativeCostUsd, 5, 'live session is not added on top');
    clearCostSessionHistory(scope);
});
Suite('lifetime usage falls back to the live session when history is off', () => {
    const el = element({ scope: 'bank-off', live: usage(4) });
    el.chatConfig = { costHistory: { enabled: false } };
    assert.is(el.cumulativeUsage.costUsd, 4, 'no rows exist to sum');
    assert.is(el.settingsCostLifetimeVisible, false, 'and the section is hidden');
});
Suite('labels the live figures by whether the transcript is authoritative', () => {
    const withPersistence = element({ scope: 'bank-label', live: usage(1), persistenceActive: true });
    assert.is(withPersistence.liveUsageIsProjectLifetime, true, 'restored → project lifetime');
    const without = element({ scope: 'bank-label', live: usage(1), persistenceActive: false });
    assert.is(without.liveUsageIsProjectLifetime, false, 'not restored → this session only');
});
Suite.run();
