/**
 * Post-answer hook registry — 답변 완료 후 실행되는 부가 작업 모음.
 *
 * 새 hook 추가 = 1 객체 push. agent.ts 는 이 배열을 iterate 만 함.
 *
 * 현재 등록 순서 (v2.2.197):
 *  1. devilRebuttal — Devil Agent 반박 카드 (비활성 시 silent skip)
 *  2. postHocSelfCheck — 답변 검증 LLM 호출 (opt-in, 기본 OFF)
 *  3. termValidator — 결정론적 글로서리 forbidden 검사 (기본 ON)
 *  4. requirementCoverage — 업무 필수 요소 커버리지 결정론적 검사 (기본 ON)
 *  5. confidenceEscalation — 확신도 산출 + 인간 검토 요청 + Reflection 기록 (기본 ON)
 *  6. criticLoop — 결정론적 검사가 문제 신호한 업무 turn 만 LLM 검수 1회 (기본 ON)
 */

import type { PostAnswerHook, PostAnswerHookContext } from './types';
import { maybeEmitDevilRebuttal as maybeEmitDevilRebuttalFn } from '../llm/devilRebuttal';
import { postHocSelfCheck, formatSelfCheckFooter, DEFAULT_SELF_CHECK_OPTIONS } from '../postHocSelfCheck';
import { validateTermUsage, formatTermValidatorFooter } from '../termValidator';
import { checkRequirementCoverage, formatRequirementCoverageFooter, detectTaskType } from '../../intelligence/requirementGraph';
import { extractAnswerSignals, computeConfidence, formatConfidenceFooter } from '../../intelligence/confidenceEngine';
import { decideEscalation, formatEscalationFooter } from '../../intelligence/escalationEngine';
import { runCriticReview, formatCriticFooter } from '../../intelligence/criticAgent';
import { appendReflection } from '../../intelligence/reflectionStore';
import { detectGaps } from '../../intelligence/gapDetector';
import { appendSuccessPattern } from '../../intelligence/skillScore';
import { getConfig } from '../../config';
import { detectReimplementedProposals, formatReimplementationFooter } from '../../extension/featureConceptMap';
import { isSelfAssessRequest, isAboutSelf } from '../../lib/contextBuilders/selfAssessContext';

const devilRebuttalHook: PostAnswerHook = {
    id: 'devil-rebuttal',
    runAsync: true,
    async run(ctx: PostAnswerHookContext): Promise<void> {
        await maybeEmitDevilRebuttalFn(
            {
                getAbortSignal: ctx.getAbortSignal,
                callNonStreaming: ctx.callNonStreaming,
                // agent.ts 에서 vscode.Webview 를 통과시키므로 실런타임 호환. 타입 cast 로 hook 일반화.
                getWebview: ctx.getWebview as any,
            },
            {
                userPrompt: ctx.userPrompt,
                assistantAnswer: ctx.assistantAnswer,
                baseUrl: ctx.baseUrl,
                modelName: ctx.modelName,
                contextLength: ctx.contextLength,
                engine: ctx.engine,
            },
        );
    },
};

const postHocSelfCheckHook: PostAnswerHook = {
    id: 'self-check',
    runAsync: true,
    async run(ctx: PostAnswerHookContext): Promise<void> {
        const cfg = getConfig();
        if (!cfg.selfCheckEnabled) return;
        if (!ctx.userPrompt.trim() || !ctx.assistantAnswer.trim()) return;
        const model = (cfg.selfCheckModel || '').trim() || cfg.defaultModel;
        if (!model || !cfg.ollamaUrl) return;

        const result = await postHocSelfCheck(ctx.userPrompt, ctx.assistantAnswer, ctx.selfCheckSources, {
            ollamaUrl: cfg.ollamaUrl,
            model,
            timeoutMs: (cfg.selfCheckTimeoutSec ?? 6) * 1000,
            excerptLength: DEFAULT_SELF_CHECK_OPTIONS.excerptLength,
            maxSources: DEFAULT_SELF_CHECK_OPTIONS.maxSources,
        });
        const footer = formatSelfCheckFooter(result, model);
        ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });
    },
};

const termValidatorHook: PostAnswerHook = {
    id: 'term-validator',
    runAsync: false,
    run(ctx: PostAnswerHookContext): void {
        const cfg = getConfig();
        if (cfg.termValidatorEnabled === false) return;
        if (!ctx.assistantAnswer || !ctx.assistantAnswer.trim()) return;
        const result = validateTermUsage(ctx.assistantAnswer, cfg.glossaryPath || '.astra/glossary.md');
        if (!result.ran || result.dictionarySize === 0) return;
        const footer = formatTermValidatorFooter(result);
        if (footer) ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });
    },
};

const requirementCoverageHook: PostAnswerHook = {
    id: 'requirement-coverage',
    runAsync: false,
    run(ctx: PostAnswerHookContext): void {
        const cfg = getConfig();
        if (cfg.requirementCoverageEnabled === false) return;
        if (!ctx.userPrompt.trim() || !ctx.assistantAnswer.trim()) return;
        const result = checkRequirementCoverage(ctx.userPrompt, ctx.assistantAnswer);
        const footer = formatRequirementCoverageFooter(result);
        if (footer) ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });
    },
};

const confidenceEscalationHook: PostAnswerHook = {
    id: 'confidence-escalation',
    runAsync: false,
    run(ctx: PostAnswerHookContext): void {
        const cfg = getConfig();
        if (cfg.confidenceEngineEnabled === false) return;
        if (!ctx.userPrompt.trim() || !ctx.assistantAnswer.trim()) return;

        // 검색이 안 돈 turn (casual 등) 은 신호 null → 보수적 기본값 (근거 0건).
        const retrievalSignals = ctx.confidenceSignals ?? {
            chunkCount: 0, topScore: 0, conflictCount: 0, ambiguityDetected: false,
        };
        const coverage = checkRequirementCoverage(ctx.userPrompt, ctx.assistantAnswer);
        const answerSignals = extractAnswerSignals(
            ctx.assistantAnswer,
            coverage.ran ? coverage.missing.length : null,
        );
        const confidence = computeConfidence(retrievalSignals, answerSignals);

        // 업무 산출물 turn 에만 footer 표시 — 잡담까지 점수 붙이면 노이즈.
        // 단, 확신도 '매우 낮음' 은 업무 여부와 무관하게 표시 (T4).
        const isTask = coverage.ran || coverage.taskId !== undefined;
        if (!isTask && confidence.band !== 'very-low') return;

        let footer = formatConfidenceFooter(confidence);
        let escalated = false;
        if (cfg.escalationEnabled !== false) {
            const decision = decideEscalation({
                confidence, coverage, conflictCount: retrievalSignals.conflictCount,
            });
            escalated = decision.escalate;
            footer += formatEscalationFooter(decision);
        }
        if (footer) ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });

        // ── Reflection 기록 (Track 2-4 / 3-6) — 업무 turn 의 결정론적 회고를
        // <brain>/.astra/growth/reflections.jsonl 에 적재. 성장 추이·Failure Pattern 의 원천.
        if (cfg.reflectionEnabled !== false) {
            const task = detectTaskType(ctx.userPrompt);
            const brainPath = ctx.getBrainPath?.();
            if (task && brainPath) {
                // Gap Detector (Track 3-2) — Requirement − Knowledge. Need Engine 의 입력.
                const gap = detectGaps({ coverage, signals: retrievalSignals, taskId: task.id });
                const reflectionRecord = {
                    ts: new Date().toISOString(),
                    taskId: task.id,
                    taskLabel: task.label,
                    confidenceScore: confidence.score,
                    confidenceBand: confidence.band,
                    missing: coverage.ran ? coverage.missing : [],
                    escalated,
                    criticIssues: null, // Critic 은 비동기 별도 hook — v1 은 미집계
                    promptPreview: ctx.userPrompt.replace(/\s+/g, ' ').slice(0, 120),
                    // Decision Journal v1 (Track 3-7) — 판단 근거 역추적.
                    factors: confidence.factors.map((f) => `${f.label} (${f.delta > 0 ? '+' : ''}${f.delta})`),
                    usedSources: (ctx.selfCheckSources || []).map((s) => s.title).slice(0, 5),
                    // Gap 신호.
                    retrieval: { chunkCount: retrievalSignals.chunkCount, topScore: retrievalSignals.topScore },
                    weakGrounding: gap.weakGrounding,
                    gapSeverity: gap.severity,
                };
                appendReflection(brainPath, reflectionRecord);
                // Success Pattern DB (Track 7-4) — 전 요소 충족 + 확신도 90+ 만 적재.
                appendSuccessPattern(brainPath, reflectionRecord);
            }
        }
    },
};

const criticLoopHook: PostAnswerHook = {
    id: 'critic-loop',
    runAsync: true,
    async run(ctx: PostAnswerHookContext): Promise<void> {
        const cfg = getConfig();
        if (cfg.criticLoopEnabled === false) return;
        if (!ctx.userPrompt.trim() || !ctx.assistantAnswer.trim()) return;

        // 게이트 — 문제 신호가 있는 turn 에만 LLM 검수 1회 (로컬 latency 보호).
        // 트리거 2종:
        //  (a) 업무 turn: 결정론적 검사가 요소 누락 또는 저확신(<70)을 신호
        //  (b) 일반 turn: 두뇌 근거 약함(topScore<0.25)인데 답변이 수치·날짜·단정
        //      표현을 포함 — intrinsic self-correction 은 효과 없으므로(2310.01798)
        //      검수는 외부 신호(근거 약함이라는 검색 측정값)가 있을 때만 발동.
        const task = detectTaskType(ctx.userPrompt);
        const retrievalSignals = ctx.confidenceSignals ?? {
            chunkCount: 0, topScore: 0, conflictCount: 0, ambiguityDetected: false,
        };
        const ASSERTIVE_RE = /\d{4}년|\d+월\s*\d+일|\d+(\.\d+)?\s*(%|배|건|개|원|달러)|입니다|확실|반드시|분명히/;
        const weakAssertive = retrievalSignals.topScore < 0.25
            && ctx.assistantAnswer.length > 200
            && ASSERTIVE_RE.test(ctx.assistantAnswer);
        if (!task && !weakAssertive) return;
        const coverage = checkRequirementCoverage(ctx.userPrompt, ctx.assistantAnswer);
        const answerSignals = extractAnswerSignals(
            ctx.assistantAnswer,
            coverage.ran ? coverage.missing.length : null,
        );
        const confidence = computeConfidence(retrievalSignals, answerSignals);
        const needsReview = (coverage.ran && coverage.missing.length > 0) || confidence.score < 70 || weakAssertive;
        if (!needsReview) return;

        const critique = await runCriticReview({
            userPrompt: ctx.userPrompt,
            draft: ctx.assistantAnswer,
            requirement: task,
            missingLabels: coverage.ran ? coverage.missing : [],
            callLlm: async (system, user, maxTokens) => {
                const r = await ctx.callNonStreaming({
                    baseUrl: ctx.baseUrl,
                    modelName: ctx.modelName,
                    engine: ctx.engine,
                    messages: [
                        { role: 'system', content: system },
                        { role: 'user', content: user },
                    ],
                    temperature: 0.2,
                    maxTokens,
                    contextLength: ctx.contextLength,
                    signal: ctx.getAbortSignal(),
                });
                return r.text;
            },
        });
        if (!critique) return; // LLM/파싱 실패 — silent skip, main turn 영향 없음
        const footer = formatCriticFooter(critique);
        if (footer) ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });
    },
};

/**
 * 인벤토리 자동 대조 — 자기 평가/자기 분석 턴에서 답변이 *이미 구현된 기능*을
 * 신규 도입하라고 제안하면 결정론적으로(LLM 콜 0) 정정 푸터를 붙인다.
 *
 * 배경: 인벤토리를 프롬프트에 주입해도 작은 로컬 모델이 mid-prompt 컨텍스트를
 * 무시하고 "Reflection Layer 도입하라"(이미 Self-Reflector 3단계 구현) 같은
 * 제안을 반복하는 실패가 재현됐다. 주입은 1차 방어선 — 이 훅이 최종 방어선.
 */
const inventoryCrossCheckHook: PostAnswerHook = {
    id: 'inventory-cross-check',
    runAsync: false,
    run(ctx: PostAnswerHookContext): void {
        if (!ctx.assistantAnswer || !ctx.assistantAnswer.trim()) return;
        // 자기 대상 턴에만 — 일반 코딩/리서치 답변에 오탐 푸터를 붙이지 않게.
        if (!(isSelfAssessRequest(ctx.userPrompt) || isAboutSelf(ctx.userPrompt))) return;
        const hits = detectReimplementedProposals(ctx.assistantAnswer);
        if (hits.length === 0) return;
        const footer = formatReimplementationFooter(hits);
        if (footer) ctx.getWebview()?.postMessage({ type: 'streamChunk', value: footer });
    },
};

export const POST_ANSWER_HOOKS: PostAnswerHook[] = [
    devilRebuttalHook,
    postHocSelfCheckHook,
    termValidatorHook,
    requirementCoverageHook,
    confidenceEscalationHook,
    criticLoopHook,
    inventoryCrossCheckHook,
];

/** 모든 hook 을 안전하게 실행 — 한 hook 의 throw 가 다른 hook 막지 않음. */
export function runPostAnswerHooks(ctx: PostAnswerHookContext): void {
    for (const hook of POST_ANSWER_HOOKS) {
        try {
            if (hook.runAsync) {
                void Promise.resolve(hook.run(ctx)).catch(() => { /* swallow */ });
            } else {
                hook.run(ctx);
            }
        } catch { /* hook never breaks the turn */ }
    }
}

export type { PostAnswerHook, PostAnswerHookContext } from './types';
