/**
 * Knowledge Validation + Belief Revision — 지식 저장 전 검증 (설계서 10장).
 *
 * Self-Evolving OS 마스터 플랜 Phase 4 / Track 4-1 + 4-2. 새 지식 후보를
 * 기존 지식과 비교해 수용/검토/거부를 판정하고, 충돌 시 Add/Update/Retire
 * 권고를 만든다.
 *
 * Constitution 준수: 이 모듈은 *판정과 권고만* 한다 — 실제 저장·폐기는 승인
 * 흐름(Learning Queue / 사용자)을 거친다 (Permission Based Learning).
 *
 * v1 은 결정론적 (LLM 없음):
 *   - 중복: 토큰 Jaccard 유사도 ≥ 0.85 → reject
 *   - 충돌/관련: 0.35 ≤ 유사도 < 0.85 → review + Belief Revision 권고
 *     (후보가 더 최신 → update / 기존이 더 최신·불명 → 병존 add 후 사람 판단)
 *   - 출처 없음 → 자동 수용 금지 (최대 review) — Provenance 원칙
 *   - 수집일이 오래됨 → review (낡은 지식 유입 방지)
 *
 * 사용처: Research Agent (P6) 가 수집한 지식의 저장 게이트. 지금은 라이브러리 +
 * 테스트로 준비 — Research Agent 배선 시 그대로 연결.
 */

export interface KnowledgeCandidate {
    title: string;
    content: string;
    /** 출처 (URL/문서명). 없으면 자동 수용 불가. */
    source?: string;
    /** 수집 시각 ISO. */
    collectedAt?: string;
}

export interface ExistingKnowledgeRef {
    title: string;
    content: string;
    /** epoch ms. */
    lastUpdated?: number;
    filePath?: string;
}

export type ValidationVerdict = 'accept' | 'review' | 'reject';
export type BeliefRevisionAction = 'add' | 'update' | 'retire-old';

export interface ValidationResult {
    verdict: ValidationVerdict;
    checks: {
        hasSource: boolean;
        freshness: 'fresh' | 'stale' | 'unknown';
        /** 중복 판정된 기존 지식 title. */
        duplicateOf: string | null;
        /** 충돌/관련 판정된 기존 지식 title. */
        conflictsWith: string | null;
        similarity: number;
    };
    /** 충돌 시 권고 (검토자에게 표시). 충돌 없으면 'add'. */
    beliefRevision: BeliefRevisionAction;
    reasons: string[];
}

export interface ValidationOptions {
    /** 이 일수보다 오래 전 수집된 후보는 stale. 기본 365. */
    staleAfterDays: number;
    /** 중복 임계 Jaccard. 기본 0.85. */
    duplicateThreshold: number;
    /**
     * 관련/충돌 임계 Jaccard. 기본 0.25 — 한국어는 조사 변형(계산은/계산이) 때문에
     * 같은 주제 문서도 토큰 Jaccard 가 낮게 나온다. 영어 위주 지식이면 0.35 권장.
     */
    conflictThreshold: number;
    /** 테스트 주입용 현재 시각. */
    nowMs?: number;
}

export const DEFAULT_VALIDATION_OPTIONS: ValidationOptions = {
    staleAfterDays: 365,
    duplicateThreshold: 0.85,
    conflictThreshold: 0.25,
};

/** 공백/문장부호 기준 토큰화 — 한글·영문 공용 v1. */
function tokenize(text: string): Set<string> {
    return new Set(
        (text || '')
            .toLowerCase()
            .replace(/[^\w가-힣\s]/gu, ' ')
            .split(/\s+/)
            .filter((t) => t.length >= 2),
    );
}

export function jaccardSimilarity(a: string, b: string): number {
    const ta = tokenize(a);
    const tb = tokenize(b);
    if (ta.size === 0 || tb.size === 0) return 0;
    let inter = 0;
    for (const t of ta) if (tb.has(t)) inter++;
    return inter / (ta.size + tb.size - inter);
}

export function validateKnowledgeCandidate(
    candidate: KnowledgeCandidate,
    existing: ExistingKnowledgeRef[],
    options: Partial<ValidationOptions> = {},
): ValidationResult {
    const opts: ValidationOptions = { ...DEFAULT_VALIDATION_OPTIONS, ...options };
    const now = opts.nowMs ?? Date.now();
    const reasons: string[] = [];

    // ─── 출처 (Provenance) ───
    const hasSource = !!(candidate.source && candidate.source.trim());
    if (!hasSource) reasons.push('출처 없음 — 자동 수용 불가');

    // ─── 최신성 ───
    let freshness: ValidationResult['checks']['freshness'] = 'unknown';
    if (candidate.collectedAt) {
        const t = Date.parse(candidate.collectedAt);
        if (!isNaN(t)) {
            const ageDays = (now - t) / 86400000;
            freshness = ageDays > opts.staleAfterDays ? 'stale' : 'fresh';
            if (freshness === 'stale') reasons.push(`수집일이 ${opts.staleAfterDays}일 이상 경과`);
        }
    }

    // ─── 중복·충돌 — 가장 유사한 기존 지식 1건 기준 ───
    let bestSim = 0;
    let bestRef: ExistingKnowledgeRef | null = null;
    for (const ref of existing) {
        const sim = jaccardSimilarity(candidate.content, ref.content);
        if (sim > bestSim) { bestSim = sim; bestRef = ref; }
    }

    let duplicateOf: string | null = null;
    let conflictsWith: string | null = null;
    let beliefRevision: BeliefRevisionAction = 'add';

    if (bestRef && bestSim >= opts.duplicateThreshold) {
        duplicateOf = bestRef.title;
        reasons.push(`기존 지식과 중복 (유사도 ${bestSim.toFixed(2)}: ${bestRef.title})`);
    } else if (bestRef && bestSim >= opts.conflictThreshold) {
        conflictsWith = bestRef.title;
        // Belief Revision (Track 4-2) — 어느 쪽을 믿을 것인가.
        const candTime = candidate.collectedAt ? Date.parse(candidate.collectedAt) : NaN;
        const existTime = bestRef.lastUpdated ?? NaN;
        if (!isNaN(candTime) && !isNaN(existTime) && candTime > existTime) {
            beliefRevision = 'update';
            reasons.push(`기존 지식과 관련/충돌 — 후보가 더 최신 → 갱신(update) 권고, 기존은 폐기(retire) 검토 (${bestRef.title})`);
        } else {
            beliefRevision = 'add';
            reasons.push(`기존 지식과 관련/충돌 — 신선도 우위 불명 → 병존(add) 후 사람 판단 (${bestRef.title})`);
        }
    }

    // ─── 종합 판정 ───
    let verdict: ValidationVerdict;
    if (duplicateOf) {
        verdict = 'reject';
    } else if (!hasSource || freshness === 'stale' || conflictsWith) {
        verdict = 'review';
    } else {
        verdict = 'accept';
        reasons.push('출처 있음 · 중복/충돌 없음');
    }

    return {
        verdict,
        checks: { hasSource, freshness, duplicateOf, conflictsWith, similarity: bestSim },
        beliefRevision,
        reasons,
    };
}
