/**
 * 모델 출력 후처리 (post-stream sanitization). 모두 stateless pure transform.
 *
 *   1) sanitizeAssistantContent — 모델이 답변에 흘려보낸 내부 마커 (rationale,
 *      reasoning channels, Harmony/GPT-OSS 채널 마커, [PROBLEM]/[GOAL]/[REASONING]
 *      메타 섹션) 를 제거한 *깨끗한 본문* 만 남긴다.
 *   2) cleanDegeneratedOutput — 작은 모델이 long context 에서 degenerate 됐을 때
 *      (문자 벽, 영문 메타 코멘트, 중복 paragraph stutter 등) 가독성 복구.
 *      모델 실패를 *고치는* 게 아니라 "나쁜 답변을 노이즈 벽이 아니라 읽을 수 있는
 *      형태로 유지" 하는 정도의 책임.
 *   3) isRestartedAnswer — continuation round 가 답변을 "이어붙이지" 않고 *처음부터*
 *      다시 시작했는지 detect. 작은 모델이 흔히 "continue from here" 지시를 무시.
 *
 * Why one module: 셋 다 *모델 출력을 읽을 만한 텍스트로 정리* 라는 단일 책임을
 * 공유. 호출 위치도 같은 stream loop 안에 모여 있어 응집도 높음.
 */

/**
 * 모델 출력에서 *내부* 마커 / 메타 섹션을 제거. Harmony 스타일 채널 마커는
 * `final` 채널만 남기고 나머지 (thought, analysis, commentary, reasoning) 는 통째로
 * 제거 — 모델별 closing 형태가 달라 (`<channel|>`, `<|channel|>`, `<|end|>`,
 * `<|return|>`) 보수적으로 매칭.
 */
export function sanitizeAssistantContent(text: string): string {
    const stripped = text
        .replace(/<rationale>[\s\S]*?<\/rationale>/gi, '')
        .replace(/^\s*\[PROBLEM\][\s\S]*?\[GOAL\][\s\S]*?\[REASONING\][^\n]*(?:\n+|$)/i, '')
        .replace(/^\s*\[PROBLEM\][\s\S]*?(?:\n\s*\n|$)/i, '')
        .replace(/(?:<think(?:ing)?>|<analysis>)[\s\S]*?(?:<\/think(?:ing)?>|<\/analysis>)/gi, '')
        .replace(/<\|?channel\|?>\s*(?:thought|analysis|commentary|reasoning)\b[\s\S]*?<\|?channel\|?>/gi, '')
        .replace(/<\|?channel\|?>\s*(?:thought|analysis|commentary|reasoning)\b[\s\S]*?(?=<\|?channel\|?>\s*final\b)/gi, '')
        .replace(/<\|?channel\|?>\s*final\b\s*(?:<\|?message\|?>)?/gi, '')
        .replace(/<\|?(?:end|return|start|message)\|?>/gi, '')
        .trim();
    return cleanDegeneratedOutput(stripped);
}

/**
 * Long context 에서 small 모델이 degenerate 됐을 때의 blast radius 봉쇄:
 *   - 문자 벽 (`_______…`, `~~~~~~`) 8+ 반복은 모두 제거. markdown rule 은 3.
 *   - 영문 (Note: …) 류 자기 narration 제거.
 *   - leaked Chronicle / follow-up scaffolding 제거.
 *   - 중복 paragraph (모델 stutter) 합치기.
 *
 * 모델 실패를 *고치는* 게 아니라 "노이즈 벽" 을 "읽을 수 있는 텍스트" 로 유지.
 */
export function cleanDegeneratedOutput(text: string): string {
    let s = text;
    s = s.replace(/([_=~.*])\1{7,}/g, '');
    s = s.replace(/\(Note:[^)]*\)/gi, '');
    s = s.replace(/\n?Candidate records for this discussion[^\n]*/gi, '');
    s = s.replace(/\(\s*질문\s*의도\s*[:：][^)]*\)/g, '');
    s = s.replace(/\[\s*핵심\s*확인\s*질문\s*\]\s*/g, '');
    const paras = s.split(/\n{2,}/);
    const deduped: string[] = [];
    for (const p of paras) {
        const norm = p.trim().replace(/\s+/g, ' ');
        const prevNorm = deduped.length ? deduped[deduped.length - 1].trim().replace(/\s+/g, ' ') : '';
        if (norm && norm === prevNorm) { continue; }
        deduped.push(p);
    }
    return deduped.join('\n\n').replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
}

/**
 * Continuation round 가 "이어붙이기" 가 아니라 *처음부터 다시* 답변을 시작했는지
 * detect — 공유 leading prefix 12자 이상 + 양쪽 다 40자 이상이면 restart 로 판정.
 * 작은 모델이 "continue from here" 지시를 무시하는 흔한 회귀 대응.
 */
/**
 * 모델 답변에 `<rationale>…</rationale>` 블록이 포함됐을 때 그 안의
 * [PROBLEM] / [GOAL] / [REASONING] 세 섹션을 파싱해 구조화된 객체로 반환.
 * 블록 자체가 없으면 undefined — 호출자가 옵셔널로 처리.
 *
 * 섹션 정규식은 `[PROBLEM]…[GOAL]…[REASONING]…` 순서 가정. 어느 한쪽이 빠지면
 * 해당 필드는 빈 문자열 / fallback (reasoning 은 raw 전체) 로.
 */
export function parseRationale(text: string): { problem: string; goal: string; reasoning: string } | undefined {
    const match = text.match(/<rationale>([\s\S]*?)<\/rationale>/);
    if (!match) return undefined;

    const raw = match[1];
    const problem = raw.match(/\[PROBLEM\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || '';
    const goal = raw.match(/\[GOAL\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || '';
    const reasoning = raw.match(/\[REASONING\]([\s\S]*?)(?=\[|$)/)?.[1]?.trim() || raw.trim();

    return { problem, goal, reasoning };
}

export function isRestartedAnswer(soFar: string, cont: string): boolean {
    const norm = (x: string) => x.trim().replace(/\s+/g, ' ').toLowerCase();
    const a = norm(soFar);
    const b = norm(cont);
    if (a.length < 40 || b.length < 40) { return false; }
    let i = 0;
    const max = Math.min(a.length, b.length, 80);
    while (i < max && a[i] === b[i]) { i++; }
    return i >= 12;
}
