/**
 * Filters Cursor UI noise from chat text (DOM + JSONL).
 * Keep: user messages, assistant prose, short meaningful status ("Исправляю.").
 */
const NOISE_LINE = /^(Editing|Edited|Ran|Thought\s+(briefly|for\s)|Explored|Listed|Grepped|Searched|Read|Loading|Planning|Generating|Running|Finished|Cancelled|Using image)\b/i;
const EDITED_DIFF = /^Edited\s+.+\s*\+\d+/i;
const FILE_DIFF = /\.[a-z0-9]+\s*[+-]\d+/i;
const TOOL_COMMAND = /^(cd |npm |npx |curl |git |node )/i;
/** Cursor pauses chat while background agent / shell runs */
export const BACKGROUND_WORK_STATUS = /will resume when|background work finishes|background shell|resuming when|paused while|waiting for background|waiting for \d+ command/i;
/** Cursor agent/runtime meta prompts that can leak into chat transcripts. */
const CURSOR_META_LEAK = /^(Briefly inform the user about the task result|Confirming server status|Usage limit reached)\b/i;
/**
 * Reflection / tool-trace leaks that should never be user-visible.
 * These appear as self-talk about debugging actions and tool calls.
 */
const SELF_TALK_PREFIX = /^(I need to|I want to|Now I should|I should|I'll |I will |I'm going to|I plan to|Checking for errors|Addressing user interruptions)\b/i;
const TOOL_TRACE_HINT = /\b(browser_[a-z_]+|Runtime\.evaluate|Performance\.getMetrics|Network\.(?:enable|emulateNetworkConditions)|CDP|DevTools|console logs|socket\.io|websocket|polling)\b/i;
export const AGENT_STATUS_BACKGROUND_SHELL = 'background_shell';
export const WORKING_STATUS_LINE = /^working\.?$/i;
/** Short DOM status line only — not a sentence inside a chat message. */
export function matchBackgroundWorkStatusText(text) {
    const t = text.trim().replace(/\s+/g, ' ');
    if (!t || t.length > 100)
        return undefined;
    if (BACKGROUND_WORK_STATUS.test(t) || WORKING_STATUS_LINE.test(t))
        return t;
    return undefined;
}
export function isBackgroundWorkStatusText(text) {
    return matchBackgroundWorkStatusText(text) !== undefined;
}
/** Paused for background shell — not active generation (no list spinner / agentWorking). */
export function isPassiveBackgroundShellState(state) {
    return (state.agentStatus === AGENT_STATUS_BACKGROUND_SHELL && state.agentWorking !== true);
}
/** Chat line that is only passive-work status (not prose mentioning it). */
export function isPassiveStatusChatLine(text) {
    const t = text.trim().replace(/\s+/g, ' ');
    if (!t || t.length > 80 || t.includes('?'))
        return false;
    if (BACKGROUND_WORK_STATUS.test(t) || WORKING_STATUS_LINE.test(t))
        return true;
    return false;
}
/** DOM/system lines that mean the agent is still busy */
export function textImpliesAgentWorking(text) {
    return isBackgroundWorkStatusText(text);
}
/** Cursor JSONL redacts tool/thinking tails as `[REDACTED]` — not shown in live DOM. */
export function stripJsonlRedactionArtifacts(text) {
    return text
        .replace(/(?:\n|\r\n)*\[REDACTED\]\s*$/g, '')
        .replace(/^\[REDACTED\]\s*$/g, '')
        .trim();
}
export function isNoiseChatText(text) {
    const t = stripJsonlRedactionArtifacts(text).trim().replace(/\s+/g, ' ');
    if (!t || t.length < 2)
        return true;
    if (CURSOR_META_LEAK.test(t))
        return true;
    if (NOISE_LINE.test(t))
        return true;
    if (EDITED_DIFF.test(t))
        return true;
    if (FILE_DIFF.test(t) && t.length < 120)
        return true;
    if (TOOL_COMMAND.test(t) && t.length < 160)
        return true;
    // "Thought briefly", "Explored2 files"
    if (/^Thought\s*for\s*\d/i.test(t) && t.length < 40)
        return true;
    if (/^Thought\s+\w+$/i.test(t) && t.length < 40)
        return true;
    if (/^Explored\d/i.test(t))
        return true;
    if (/^Read\s+\S+\s*·/i.test(t))
        return true;
    if (isBackgroundWorkStatusText(t))
        return true;
    // Lone tool label or file path only
    if (/^\[[\w.]+\]$/.test(t))
        return true;
    if (/^[\w./-]+\.(tsx?|jsx?|md|py|json|css)$/i.test(t))
        return true;
    return false;
}
/** Markdown / structured answer — must stay in chat lenta even if it mentions bridge symbols. */
export function isUserFacingAssistantProse(text) {
    const t = stripJsonlRedactionArtifacts(text).trim();
    if (!t)
        return false;
    if (/^##\s+\S/m.test(t))
        return true;
    if (/^\*\*[^*]{2,}\*\*/m.test(t) && t.length >= 60)
        return true;
    const tableRows = t.match(/^\|[^\n]+\|$/gm);
    if (tableRows && tableRows.length >= 2)
        return true;
    if (/^[-*]\s+\S/m.test(t) && t.length >= 100)
        return true;
    if (/^(?:\d+\.\s+\S|>\s)/m.test(t) && t.length >= 150)
        return true;
    return false;
}
/** Cursor agent internal outline (JSONL): "Evaluating …", "Analyzing …" title lines. */
const INTERNAL_REASONING_LINE = /^(?:Evaluating|Analyzing|Analysing|Examining|Investigating|Gathering|Considering|Reviewing|Checking|Assessing|Exploring|Looking at|Thinking about|Planning next|Outlining|Identifying|Determining|Clarifying|Noting|Comparing|Tracing|Preparing|Updating|Fixing|Implementing|Searching for|Reading|Reflecting on)\s+\S/i;
export function isInternalReasoningOutline(text) {
    const raw = stripJsonlRedactionArtifacts(text).trim();
    if (!raw)
        return false;
    const lines = raw
        .split(/\n+/)
        .map((l) => l.replace(/^\*\*|\*\*$/g, '').trim())
        .filter(Boolean);
    if (!lines.length)
        return false;
    const cot = lines.filter((l) => INTERNAL_REASONING_LINE.test(l) && l.length < 160);
    if (lines.length === 1 && cot.length === 1)
        return true;
    if (cot.length >= 2 && cot.length >= Math.ceil(lines.length * 0.55))
        return true;
    if (cot.length >= 3)
        return true;
    return false;
}
/** Chain-of-thought / tool trace rows — not user-facing answers (DOM + JSONL). */
export function isAssistantReflectionText(text) {
    const raw = stripJsonlRedactionArtifacts(text).trim();
    if (!raw)
        return true;
    // Hard drop self-talk that references tools/automation.
    if (SELF_TALK_PREFIX.test(raw) && TOOL_TRACE_HINT.test(raw))
        return true;
    if (isInternalReasoningOutline(raw))
        return true;
    const t = raw.replace(/\s+/g, ' ');
    if (!t || isNoiseChatText(t))
        return true;
    if (isUserFacingAssistantProse(t))
        return false;
    if (/^Thought\s*for\s*\d/i.test(t) && t.length < 160)
        return true;
    if (/^(Exploring|Grepped|Searched|Listed|Read |Ran |Edited |Loading|Planning|Using image)/i.test(t)) {
        return true;
    }
    const toolHits = t.match(/\b(Explored|Grepped|Searched|Listed|Read )\b/gi);
    if (toolHits && toolHits.length >= 2)
        return true;
    return false;
}
/** Short agent status worth showing (not tool log). */
export function isMeaningfulAssistantText(text) {
    const t = text.trim();
    if (!t || isNoiseChatText(t) || isAssistantReflectionText(t))
        return false;
    // Markdown prose / explanation
    if (t.length >= 20 && /\s/.test(t))
        return true;
    // Short status sentence: "Исправляю." "Fixing."
    if (t.length >= 6 && /[.!?…]$/.test(t) && !NOISE_LINE.test(t))
        return true;
    return t.length >= 50;
}
/** Paths from Cursor `<image_files>` blocks (JSONL + DOM text). */
export function parseUserImagePaths(text) {
    const paths = [];
    const scope = text.match(/<image_files>[\s\S]*?<\/image_files>/i)?.[0] ?? text;
    const re = /\d+\.\s+(\/?[^\s\n]+?\.(?:png|jpe?g|gif|webp))/gi;
    let m;
    while ((m = re.exec(scope))) {
        const p = m[1]?.trim();
        if (p && !paths.includes(p))
            paths.push(p);
    }
    return paths;
}
/** Cursor injects image metadata into user bubbles — hide it in the app. */
export function stripUserImageMetadata(text) {
    return text
        .replace(/^\[Image\]\s*$/gim, '')
        .replace(/<image_files>[\s\S]*?<\/image_files>/gi, '')
        .replace(/(?:^|\n)\s*The following images? (?:were |has been )?provid(?:ed|ied)[^\n]*(?:\n\s*\d+\.\s*[^\n]+)*/gi, '\n')
        .replace(/(?:^|\n)\s*These images can be copied for use in other locations\.?\s*/gi, '\n')
        .replace(/\n{3,}/g, '\n\n')
        .trim();
}
/** Keep in sync with `cleanUserText` in `extract-page.ts` (CDP runs in-page). */
export function cleanUserText(text) {
    return stripUserImageMetadata(text
        .replace(/<timestamp>[\s\S]*?<\/timestamp>/gi, '')
        .replace(/<user_query>\s*/gi, '')
        .replace(/<\/user_query>/gi, '')
        .replace(/([.!?…])(\d+\.\s*)/g, '$1\n$2')
        .replace(/([^\n\d\s])(\d+\.\s+)/g, '$1\n$2')
        .trim());
}
export function filterAssistantJsonlParts(parts) {
    return parts
        .map((p) => stripJsonlRedactionArtifacts(p))
        .filter((p) => isMeaningfulAssistantText(p));
}
