import { archiveCoversOverlay, composeDisplayLenta, messageOrderKey } from './chat-display.js';
export function formatLentaIssue(issue) {
    const dom = issue.dom
        ? ` dom=${issue.dom.role ?? '?'}:${(issue.dom.head ?? '').slice(0, 48)}`
        : '';
    return `${issue.type}${dom}${issue.detail ? ` (${issue.detail})` : ''}`;
}
export function normPreviewText(t) {
    return String(t ?? '')
        .replace(/\s+/g, ' ')
        .trim()
        .slice(0, 400);
}
export function previewRow(m, source) {
    const text = normPreviewText(m.text);
    return {
        source,
        role: m.role ?? '?',
        flat: m.flatIndex ?? null,
        domSeq: m.domSeq ?? null,
        id: (m.id ?? '').slice(0, 28),
        len: text.length,
        head: text.slice(0, 72),
    };
}
function rowFingerprint(m) {
    const text = normPreviewText(m.text);
    return `${m.role}|${m.id ?? ''}|${text.length}|${text.slice(0, 64)}`;
}
function rowsFingerprint(rows) {
    return rows.map(rowFingerprint);
}
/** `messages` must equal `[...historyMessages, ...liveMessages]` (socket/HTTP payload). */
export function checkPayloadCompose(messages, historyMessages, liveMessages) {
    const issues = [];
    const expectedLen = historyMessages.length + liveMessages.length;
    if (messages.length !== expectedLen) {
        issues.push({
            type: 'PAYLOAD_COMPOSE_LEN',
            detail: `messages=${messages.length} history+live=${expectedLen} (${historyMessages.length}+${liveMessages.length})`,
        });
    }
    const composed = [...historyMessages, ...liveMessages];
    const a = rowsFingerprint(messages);
    const b = rowsFingerprint(composed);
    const n = Math.min(a.length, b.length);
    for (let i = 0; i < n; i++) {
        if (a[i] !== b[i]) {
            issues.push({
                type: 'PAYLOAD_COMPOSE_ROW',
                index: i,
                detail: `messages[${i}] !== concat(history,live)[${i}]`,
            });
            break;
        }
    }
    if (a.length !== b.length && !issues.some((x) => x.type === 'PAYLOAD_COMPOSE_LEN')) {
        issues.push({
            type: 'PAYLOAD_COMPOSE_ROW',
            detail: `fingerprint len ${a.length} vs ${b.length}`,
        });
    }
    return issues;
}
/** In-memory store: `messages` = composeDisplayLenta(jsonl, overlay). */
export function checkStoreCompose(jsonlHistory, domOverlay, messages) {
    const issues = [];
    const composed = composeDisplayLenta(jsonlHistory, domOverlay);
    if (messages.length !== composed.length) {
        issues.push({
            type: 'STORE_COMPOSE_LEN',
            detail: `messages=${messages.length} composed=${composed.length} (jsonl=${jsonlHistory.length} overlay=${domOverlay.length})`,
        });
    }
    const a = rowsFingerprint(messages);
    const b = rowsFingerprint(composed);
    for (let i = 0; i < Math.min(a.length, b.length); i++) {
        if (a[i] !== b[i]) {
            issues.push({
                type: 'STORE_COMPOSE_ROW',
                index: i,
                detail: `messages[${i}] !== jsonl+overlay[${i}]`,
            });
            break;
        }
    }
    return issues;
}
export function checkMonotonicOrder(messages, label) {
    const issues = [];
    for (let i = 1; i < messages.length; i++) {
        const prev = messageOrderKey(messages[i - 1]);
        const cur = messageOrderKey(messages[i]);
        if (cur < prev) {
            issues.push({
                type: 'LENTA_ORDER_INVERSION',
                index: i,
                detail: `${label} i=${i} ${prev}>${cur}`,
            });
            break;
        }
    }
    return issues;
}
/** DOM overlay must not repeat a turn already in JSONL archive. */
export function checkOverlayDuplicatesArchive(jsonlHistory, domOverlay) {
    const issues = [];
    for (const dom of domOverlay) {
        const hit = jsonlHistory.find((h) => archiveCoversOverlay(h, dom));
        if (hit) {
            issues.push({
                type: 'OVERLAY_DUPLICATES_ARCHIVE',
                detail: `dom flat=${dom.flatIndex ?? '?'} covered by jsonl flat=${hit.flatIndex ?? '?'}`,
                dom: previewRow(dom, 'domOverlay'),
                bridge: previewRow(hit, 'jsonlHistory'),
            });
        }
    }
    return issues;
}
export function checkOverlayAfterArchive(jsonlHistory, domOverlay) {
    if (!jsonlHistory.length || !domOverlay.length)
        return [];
    let floor = 0;
    for (const m of jsonlHistory) {
        floor = Math.max(floor, messageOrderKey(m));
    }
    const minOverlay = Math.min(...domOverlay.map((m) => messageOrderKey(m)));
    if (minOverlay <= floor) {
        return [
            {
                type: 'OVERLAY_NOT_AFTER_ARCHIVE',
                detail: `archive floor=${floor} overlay min key=${minOverlay}`,
            },
        ];
    }
    return [];
}
/** When not synced with Cursor, DOM overlay for subscribed chat should stay empty. */
export function checkSyncGate(synced, domOverlay, domRawCount) {
    if (synced)
        return [];
    if (domOverlay.length > 0) {
        return [
            {
                type: 'OVERLAY_WHILE_UNSYNCED',
                detail: `overlay=${domOverlay.length} domRaw=${domRawCount}`,
            },
        ];
    }
    return [];
}
export function rowSimilar(a, b) {
    if (a.role === b.role) {
        const ah = a.head.toLowerCase();
        const bh = b.head.toLowerCase();
        if (!ah || !bh)
            return ah === bh;
        if (ah === bh)
            return true;
        const short = ah.length <= bh.length ? ah : bh;
        const long = ah.length <= bh.length ? bh : ah;
        if (short.length >= 8 && long.includes(short))
            return true;
        return long.includes(short) && short.length >= 16;
    }
    return domViewportRowCoveredByLenta(a, b) || domViewportRowCoveredByLenta(b, a);
}
/**
 * Cursor viewport may label a bubble `assistant` while it still shows the queued user line
 * (e.g. `LIVE-…-QUEUE` + partial reply). Match that to the JSONL `user` row.
 */
const LIVE_QUEUE_MARKER_RE = /LIVE-\d+-QUEUE/i;
function extractLiveQueueMarker(head) {
    const m = head.match(LIVE_QUEUE_MARKER_RE);
    return m ? m[0] : null;
}
export function domViewportRowCoveredByLenta(dom, lenta) {
    const d = dom.head.toLowerCase();
    const l = lenta.head.toLowerCase();
    if (!d || !l)
        return false;
    const marker = extractLiveQueueMarker(dom.head);
    if (marker) {
        const mk = marker.toLowerCase();
        if (l.includes(mk) || d.includes(mk))
            return true;
    }
    if (dom.role === 'assistant' && lenta.role === 'user') {
        if (l.length >= 6 && d.includes(l))
            return true;
        if (d.startsWith(l))
            return true;
    }
    if (dom.role === 'user' && lenta.role === 'assistant') {
        if (d.length >= 12 && l.includes(d))
            return true;
    }
    if (dom.role === 'user' && lenta.role === 'user') {
        const short = d.length <= l.length ? d : l;
        const long = d.length <= l.length ? l : d;
        if (short.length >= 16 && long.includes(short))
            return true;
    }
    return false;
}
/** Viewport bubble already represented somewhere in composed lenta (JSONL + overlay). */
export function lentaContainsDomViewportRow(dom, lentaRows) {
    return lentaRows.some((row) => rowSimilar(dom, row) || domViewportRowCoveredByLenta(dom, row));
}
/** DOM viewport vs full lenta — match each viewport row to lenta from the end (avoids duplicate-text false gaps). */
export function compareDomTailToLenta(domRows, lentaRows, tail, opts) {
    const issues = [];
    const domTailN = opts?.hasJsonlArchive ? Math.min(tail, 4) : tail;
    const d = domRows.slice(-domTailN);
    const full = lentaRows;
    const lentaTail = full.slice(-Math.max(tail, 16));
    if (!d.length || !full.length) {
        return { issues, domTail: d, lentaTail };
    }
    const searchWindow = Math.min(full.length, Math.max(tail * 10, full.length > 80 ? 120 : 48));
    let searchEnd = full.length;
    for (let di = d.length - 1; di >= 0; di--) {
        const dom = d[di];
        let found = -1;
        const from = Math.max(0, searchEnd - searchWindow);
        for (let j = searchEnd - 1; j >= from; j--) {
            if (rowSimilar(dom, full[j])) {
                found = j;
                break;
            }
        }
        if (found < 0) {
            const domFlat = dom.flat ?? 0;
            const maxLentaFlat = full.reduce((m, r) => Math.max(m, r.flat ?? 0), 0);
            if (lentaContainsDomViewportRow(dom, full)) {
                searchEnd = full.length;
                continue;
            }
            // Tail-scoped search can miss rows still visible in viewport (scrolled DOM, flat index drift).
            const anywhere = full.findIndex((row) => rowSimilar(dom, row));
            if (anywhere >= 0) {
                searchEnd = anywhere;
                continue;
            }
            const busy = Boolean(opts?.agentWorking || opts?.agentGenerating);
            const aheadOfArchive = domFlat > maxLentaFlat + 5;
            if (busy) {
                // Viewport can run ahead of JSONL / overlay handoff while agent is active.
                continue;
            }
            const type = !opts?.hasDomOverlay && aheadOfArchive ? 'NEEDS_DOM_OVERLAY' : 'DOM_MISSING_IN_LENTA';
            issues.push({ type, dom, detail: `domFlat=${domFlat} lentaMax=${maxLentaFlat}` });
            continue;
        }
        searchEnd = found;
    }
    return { issues, domTail: d, lentaTail };
}
export function checkApiMatchesStore(apiMessages, storeMessages) {
    const a = rowsFingerprint(apiMessages);
    const b = rowsFingerprint(storeMessages);
    if (a.length !== b.length) {
        return [
            {
                type: 'API_STORE_LEN',
                detail: `api=${a.length} store=${b.length}`,
            },
        ];
    }
    for (let i = Math.max(0, a.length - 8); i < a.length; i++) {
        if (a[i] !== b[i]) {
            return [
                {
                    type: 'API_STORE_TAIL',
                    index: i,
                    detail: `tail mismatch at ${i}`,
                },
            ];
        }
    }
    return [];
}
