import * as fs from 'fs';
import * as path from 'path';
import { getConfig } from '../config';

/**
 * 단계별 지식 스코프 라우터 — 소형 모델 정밀 검색의 핵심.
 *
 * 배경: 4b급 로컬 모델은 컨텍스트에 무관한 청크가 섞이면 크게 흔들린다.
 * 질문을 수학/코딩/팩트 세 도메인으로 분류해, 해당 도메인의 Specialty 지식
 * 폴더(+ 공통 General 폴더)로 RAG 검색 범위를 좁히면 같은 컨텍스트 예산에
 * 훨씬 밀도 높은 근거가 들어간다. 사용자는 도메인별 위키를 해당 폴더에서
 * 관리한다 (예: Topic_Programming 위키 → 코딩 Specialty).
 *
 * 설계 원칙:
 *  - 분류는 LLM 호출 없는 결정적 휴리스틱 — 매 턴 추가 지연/비용 0, 테스트 가능.
 *  - 확신이 없으면 'general' → 기존 전체 두뇌 검색으로 폴백 (안전한 opt-in).
 *  - 해당 도메인의 Specialty 폴더가 설정·존재할 때만 스코프 적용
 *    (General 폴더 하나만으로 좁히면 오히려 근거가 굶주린다).
 *  - 에이전트별 지식 매핑(agentKnowledgeMap)이 이미 있으면 그쪽이 우선 —
 *    기존 제한을 이 기능이 느슨하게 만들면 안 된다 (호출부에서 보장).
 */

export type KnowledgeDomain = 'math' | 'coding' | 'facts' | 'general';

// ── 분류 휴리스틱 ─────────────────────────────────────────────────────────
// 각 도메인의 신호 패턴. 히트 수 가중 합산 → 최고점이 임계치 이상이면 그 도메인.

const CODING_PATTERNS: RegExp[] = [
    /```/,                                                       // 코드 펜스
    /\b(error|exception|traceback|stack ?trace|undefined is not|cannot read|segfault)\b/i,
    /(에러|오류|버그|디버그|디버깅|컴파일|빌드 실패|함수|메서드|클래스|변수|리팩토링|구현해|코드 짜|코드를|스크립트)/,
    /\.(ts|tsx|js|jsx|py|java|go|rs|rb|php|cs|cpp|c|h|css|scss|html|sql|json|yaml|yml|sh|mjs)\b/i,
    /\b(python|typescript|javascript|java|kotlin|swift|rust|golang|node\.?js|react|vue|django|spring|sql|regex|api|endpoint)\b/i,
    /\b(npm|pip|git|docker|jest|pytest|eslint|tsc)\b/i,
    /\b[a-z]+[A-Z][a-zA-Z]*\(|[a-z_]+_[a-z_]+\(/,                // camelCase( / snake_case( 함수 호출꼴
];

const MATH_PATTERNS: RegExp[] = [
    /\d+\s*[+\-*/^×÷]\s*\d+/,                                    // 숫자 연산식
    /\d+(\.\d+)?\s*%/,                                            // 백분율
    /(계산해|계산기|얼마(야|인가|예요|입니까)|평균|합계|총합|확률|방정식|부등식|미분|적분|제곱|루트|소수점|반올림|나머지|몫)/,
    /(이자|복리|단리|환율|환산|할인율|수익률|퍼센트|평형|넓이|부피|둘레|각도|속도.*(거리|시간))/,
    /\b(solve|equation|probability|percentage|average|median|std ?dev|factorial|permutation|combination)\b/i,
    /(조합|순열|경우의 수|표준편차|중앙값|분산)/,
];

const FACTS_PATTERNS: RegExp[] = [
    /(뭐야|뭔가요|무엇인가|무엇입니까|누구(야|인가|예요)|언제(야|인가|였)|어디(야|인가|에 있)|왜 그런|정의(가|는|를)|뜻이|의미가)/,
    /(설명해|알려줘|소개해|정리해|차이(가|는|점)|역사|유래|어원|개념)/,
    /\b(what is|who is|when did|where is|why does|define|explain|difference between|history of)\b/i,
];

function countHits(text: string, patterns: RegExp[]): number {
    let n = 0;
    for (const re of patterns) { if (re.test(text)) n++; }
    return n;
}

/**
 * 질문을 지식 도메인으로 분류한다 (결정적 휴리스틱, LLM 호출 없음).
 * 코드 펜스가 있으면 coding 확정. 그 외엔 도메인별 패턴 히트 수로 판정하고,
 * 동점이거나 신호가 약하면 'general'(전체 두뇌 검색)로 보수적으로 폴백한다.
 */
export function classifyKnowledgeDomain(query: string): KnowledgeDomain {
    const q = (query || '').trim();
    if (!q) return 'general';
    if (/```/.test(q)) return 'coding'; // 코드 블록 동봉 = 코딩 확정

    const coding = countHits(q, CODING_PATTERNS);
    const math = countHits(q, MATH_PATTERNS);
    const facts = countHits(q, FACTS_PATTERNS);

    // 전문 도메인(coding/math)은 강한 신호라 1히트로 충분하며 facts 보다 우선한다 —
    // 정의형 질문("git rebase가 뭐야?")도 전문 지식 폴더가 더 정확한 근거를 갖는다.
    // facts 는 일반 대화와 경계가 흐려서 전문 신호가 전혀 없을 때만 인정.
    if (coding === 0 && math === 0 && facts === 0) return 'general';
    if (coding > 0 && coding >= math) return 'coding';
    if (math > 0) return 'math';
    return 'facts';
}

/**
 * [도구 라우팅] 도메인 분류 → 그 턴의 행동 지시. 분류 1회로 지식 스코프와 도구
 * 유도를 동시에 결정한다 — 소형 모델에게 "풀어라"가 아니라 "이 도구를 써라"를 준다.
 * dynamicBlocks 보호 구역에 주입되어 컨텍스트 truncation 에서도 살아남는다.
 */
export function buildDomainRoutingHint(domain: KnowledgeDomain): string {
    switch (domain) {
        case 'math':
            return '[도구 라우팅] 이 질문은 수학·계산 도메인으로 분류되었다. 수치·수학 계산은 절대 암산하지 말고 <calculate> 태그로 Python 에 위임한 뒤, 주입된 실행 결과만 해석해 답하라.';
        case 'coding':
            return '[도구 라우팅] 이 질문은 코딩·디버깅 도메인으로 분류되었다. 실행 가능한 코드를 작성·수정했으면 <run_code path="..."/> 로 실제 실행해 결과를 확인한 뒤 답하라. 문법 오류나 실행 에러가 주입되면 즉시 <edit_file> 로 수정하고 다시 실행해 확인하라.';
        case 'facts':
            return '[도구 라우팅] 이 질문은 사실 확인(팩트) 도메인으로 분류되었다. 컨텍스트에 근거가 없는 사실은 지어내지 말고 "확인 불가"라고 답하거나, 최신 정보가 필요하면 <fetch_url> 로 확인하라.';
        default:
            return '';
    }
}

// ── 도메인 → 지식 폴더 해석 ──────────────────────────────────────────────

/** 도메인별로 검색에 포함할 두뇌-상대 폴더 목록 (순수 함수 — 존재 검증 없음, 테스트 대상). */
export function combineDomainFolders(
    domain: KnowledgeDomain,
    cfg: { general: string; math: string; coding: string; facts: string },
): string[] {
    if (domain === 'general') return []; // 분류 안 됨 → 전체 두뇌 (레거시 동작)
    const specialty = (cfg[domain] || '').trim();
    if (!specialty) return []; // Specialty 미설정 → 스코프 적용 안 함 (General 만으로 좁히면 굶주림)
    const general = (cfg.general || '').trim();
    return general ? [general, specialty] : [specialty];
}

export interface DomainScope {
    domain: KnowledgeDomain;
    /** 검색에 넘길 절대 경로 폴더 목록. 빈 배열 = 스코프 미적용(전체 두뇌). */
    folders: string[];
    /** 로그/근거 추적용 라벨. 예: "coding → General + Specialty_Coding" */
    label: string;
}

/**
 * 설정을 읽어 도메인의 유효한(존재하는·두뇌 안의) 스코프 폴더 절대경로를 만든다.
 * 폴더가 두뇌 밖을 가리키거나 없으면 조용히 제외하지 않고 — 존재하는 것만 채택하되
 * Specialty 가 무효이면 스코프 전체를 포기한다(전체 두뇌 폴백).
 */
export function resolveDomainScope(query: string, brainRoot: string): DomainScope {
    const domain = classifyKnowledgeDomain(query);
    const none: DomainScope = { domain, folders: [], label: `${domain} → 전체 두뇌` };
    if (domain === 'general' || !brainRoot) return none;

    const config = getConfig();
    const relFolders = combineDomainFolders(domain, {
        general: config.domainKnowledgeGeneralFolder,
        math: config.domainKnowledgeMathFolder,
        coding: config.domainKnowledgeCodingFolder,
        facts: config.domainKnowledgeFactsFolder,
    });
    if (relFolders.length === 0) return none;

    const rootAbs = path.resolve(brainRoot);
    const valid: string[] = [];
    for (const rel of relFolders) {
        const abs = path.resolve(rootAbs, rel);
        // 두뇌 밖 탈출(../) 차단 + 실제 존재하는 디렉토리만
        if (!abs.startsWith(rootAbs + path.sep) && abs !== rootAbs) continue;
        try { if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) valid.push(abs); } catch { /* skip */ }
    }
    // Specialty(마지막 항목)가 살아남지 못했으면 스코프 포기 — General 만으로는 좁히지 않는다.
    const specialtyAbs = path.resolve(rootAbs, relFolders[relFolders.length - 1]);
    if (!valid.includes(specialtyAbs)) return none;

    return {
        domain,
        folders: valid,
        label: `${domain} → ${relFolders.join(' + ')}`,
    };
}
