import { archiveOrderFloor, composeDisplayLenta, filterClientDisplayList, historyRowsToChat, mergeHistoryTail, prepareChatMessagesForDisplay, prepareDomOverlayForDisplay, selectDomOverlayFromViewport, stampOverlayAfterArchive, } from './chat-display.js';
import { DomTranscriptStore } from './dom-transcript-store.js';
/**
 * Chat UI store: JSONL baseline + DOM overlay tail.
 *
 * Invariant:
 * - JSONL baseline = everything already in `.jsonl` (monotonic archive order).
 * - DOM overlay = viewport rows **not covered** by JSONL (`archiveCoversOverlay`).
 * - While a row streams in DOM but JSONL is shorter → overlay keeps it; when JSONL catches up → overlay drops (no duplicate).
 * - App lenta = `[...jsonlHistory, ...domOverlay]` — never sort both by DOM flatIndex.
 */
export class ChatDisplayStore {
    domTranscript = new DomTranscriptStore();
    /** Last CDP viewport extract per agent — sole source for live overlay. */
    latestDomViewport = new Map();
    jsonlBaseline = new Map();
    /** Raw JSONL row count per agent — incremental display merge during streaming. */
    jsonlRowCount = new Map();
    /** While true, bridge pushes lenta on DOM ingest (emit path); overlay filter unchanged. */
    generatingByAgent = new Map();
    /** Latest composer todo statuses — enriches JSONL `CreatePlan` cards in lenta. */
    liveTodosByAgent = new Map();
    setLiveTodos(agentId, todos) {
        if (!agentId)
            return;
        if (!todos.length)
            this.liveTodosByAgent.delete(agentId);
        else
            this.liveTodosByAgent.set(agentId, todos);
    }
    clearAgent(agentId) {
        this.domTranscript.clear(agentId);
        this.latestDomViewport.delete(agentId);
        this.liveTodosByAgent.delete(agentId);
        this.jsonlBaseline.delete(agentId);
        this.jsonlRowCount.delete(agentId);
        this.generatingByAgent.delete(agentId);
    }
    setAgentGenerating(agentId, generating) {
        if (generating)
            this.generatingByAgent.set(agentId, true);
        else {
            this.generatingByAgent.delete(agentId);
            this.reconcileDomViewportToArchive(agentId);
            this.pruneDomOverlay(agentId);
        }
    }
    isAgentGenerating(agentId) {
        return this.generatingByAgent.get(agentId) ?? false;
    }
    getJsonlHistory(agentId) {
        return filterClientDisplayList(this.jsonlBaseline.get(agentId) ?? []);
    }
    getDomLive(agentId) {
        const archive = this.archiveForOverlayMatch(agentId);
        const dom = selectDomOverlayFromViewport(this.latestDomViewport.get(agentId) ?? [], archive);
        if (!dom.length)
            return [];
        if (!archive.length) {
            return filterClientDisplayList(prepareChatMessagesForDisplay(dom));
        }
        const floor = archiveOrderFloor(archive, this.jsonlRowCount.get(agentId) ?? 0);
        const stamped = stampOverlayAfterArchive(floor, dom);
        return filterClientDisplayList(prepareDomOverlayForDisplay(stamped));
    }
    /** Reload JSONL; drop viewport rows now covered by archive. */
    setJsonlBaseline(agentId, rows) {
        const prevRows = this.jsonlRowCount.get(agentId) ?? 0;
        if (rows.length > 0 && prevRows > 24 && rows.length < prevRows - 12) {
            return;
        }
        const prev = this.jsonlBaseline.get(agentId) ?? [];
        let prepared;
        if (rows.length > prevRows && prevRows > 0 && prev.length && rows.length - prevRows <= 128) {
            const delta = rows.slice(prevRows);
            const deltaChat = prepareChatMessagesForDisplay(historyRowsToChat(delta));
            prepared = mergeHistoryTail(prev, deltaChat);
        }
        else {
            prepared = rows.length
                ? prepareChatMessagesForDisplay(historyRowsToChat(rows))
                : [];
        }
        this.jsonlRowCount.set(agentId, rows.length);
        this.jsonlBaseline.set(agentId, prepared);
        this.reconcileDomViewportToArchive(agentId);
        this.pruneDomOverlay(agentId);
    }
    /** Append JSONL rows during live stream; returns new/updated bubbles for socket `append` emit. */
    appendJsonlRows(agentId, deltaRows) {
        if (!deltaRows.length)
            return [];
        const prev = this.jsonlBaseline.get(agentId) ?? [];
        const prevLen = prev.length;
        const deltaChat = prepareChatMessagesForDisplay(historyRowsToChat(deltaRows));
        const prepared = mergeHistoryTail(prev, deltaChat);
        this.jsonlRowCount.set(agentId, (this.jsonlRowCount.get(agentId) ?? 0) + deltaRows.length);
        this.jsonlBaseline.set(agentId, prepared);
        this.reconcileDomViewportToArchive(agentId);
        this.pruneDomOverlay(agentId);
        if (prepared.length > prevLen)
            return prepared.slice(prevLen);
        if (prepared.length && prevLen) {
            const last = prepared[prepared.length - 1];
            const prevLast = prev[prevLen - 1];
            if (prevLast && last.id === prevLast.id && last.text !== prevLast.text)
                return [last];
            if (prevLast &&
                last.role === prevLast.role &&
                (last.text?.length ?? 0) > (prevLast.text?.length ?? 0) &&
                last.text?.includes((prevLast.text ?? '').slice(0, 24))) {
                return [last];
            }
        }
        if (!prevLen && prepared.length)
            return prepared;
        return deltaChat.length ? deltaChat : [];
    }
    /** Ingest DOM extract; overlay = viewport minus JSONL-covered rows. */
    mergeLiveForAgent(agentId, rawDom) {
        const live = prepareChatMessagesForDisplay(rawDom);
        if (live.length) {
            this.latestDomViewport.set(agentId, live);
            this.domTranscript.ingest(agentId, live);
            this.domTranscript.reconcileToViewport(agentId, live);
        }
        else {
            this.latestDomViewport.delete(agentId);
            this.domTranscript.reconcileToViewport(agentId, []);
        }
        this.reconcileDomViewportToArchive(agentId);
        this.pruneDomOverlay(agentId);
    }
    /** Canonical UI lenta: JSONL archive + DOM overlay (compose dedupes cross-layer users). */
    getDisplayMessages(agentId) {
        return composeDisplayLenta(this.getJsonlHistory(agentId), this.getDomLive(agentId), this.liveTodosByAgent.get(agentId));
    }
    /** Raw DOM ingest before JSONL filter / stamp (debug only). */
    getDomTranscriptRaw(agentId) {
        return this.domTranscript.list(agentId);
    }
    /** Snapshot for `/debug/lenta` and lenta:watch. */
    getLentaDebug(agentId) {
        const jsonlHistory = this.getJsonlHistory(agentId);
        const domOverlay = this.getDomLive(agentId);
        return {
            jsonlRowCount: this.jsonlRowCount.get(agentId) ?? 0,
            jsonlHistory,
            domOverlay,
            domRaw: this.getDomTranscriptRaw(agentId),
            messages: this.getDisplayMessages(agentId),
            source: domOverlay.length ? 'hybrid' : 'jsonl',
            agentGenerating: this.isAgentGenerating(agentId),
        };
    }
    /** JSONL rows used to suppress DOM overlay (baseline + displayed archive). */
    archiveForOverlayMatch(agentId) {
        const baseline = this.jsonlBaseline.get(agentId) ?? [];
        const displayed = this.getJsonlHistory(agentId);
        const seen = new Set();
        const out = [];
        for (const m of [...baseline, ...displayed]) {
            const key = `${m.role}:${m.id ?? ''}:${m.flatIndex ?? ''}:${(m.text ?? '').slice(0, 48)}`;
            if (seen.has(key))
                continue;
            seen.add(key);
            out.push(m);
        }
        return out;
    }
    /** Shrink cached viewport to rows still missing from JSONL (handoff DOM → JSONL). */
    reconcileDomViewportToArchive(agentId) {
        const dom = this.latestDomViewport.get(agentId);
        if (!dom?.length)
            return;
        const archive = this.archiveForOverlayMatch(agentId);
        const kept = selectDomOverlayFromViewport(dom, archive);
        if (kept.length)
            this.latestDomViewport.set(agentId, kept);
        else
            this.latestDomViewport.delete(agentId);
    }
    pruneDomOverlay(agentId) {
        const archive = this.archiveForOverlayMatch(agentId);
        if (!archive.length)
            return;
        this.domTranscript.pruneCoveredBy(agentId, archive);
    }
}
