/**
 * ============================================================
 * Context Budget Manager (컨텍스트 예산 관리)
 * 
 * 시스템 프롬프트의 토큰 예산을 관리하여
 * 로컬 모델의 context window를 효율적으로 활용합니다.
 * ============================================================
 */

import { RetrievalChunk, ContextBudgetConfig } from './types';

const DEFAULT_BUDGET: ContextBudgetConfig = {
    totalBudget: 8000,     // ~32K context 중 retrieval에 할당
    retrievalRatio: 0.4,   // 40%
    minChunks: 2,
    maxChunks: 12
};

/**
 * 토큰 수를 대략 추정합니다 (문자 수 / 4).
 * 한국어는 글자당 토큰이 더 많으므로 보정합니다.
 */
export function estimateTokens(text: string): number {
    // 한국어 비율 추정
    const koreanChars = (text.match(/[가-힣]/g) || []).length;
    const totalChars = text.length;
    const koreanRatio = totalChars > 0 ? koreanChars / totalChars : 0;

    // 한국어는 글자당 ~1.5 토큰, 영어는 ~0.25 토큰
    const koreanTokens = koreanChars * 1.5;
    const otherTokens = (totalChars - koreanChars) * 0.25;

    return Math.ceil(koreanTokens + otherTokens);
}

/**
 * 검색 결과 청크들을 토큰 예산 내에서 선택합니다.
 * 
 * 선택 전략:
 * 1. 스코어 내림차순 정렬
 * 2. 중복 제거 (같은 filePath를 가진 청크)
 * 3. 토큰 예산 내에서 순서대로 선택
 * 4. 최소 청크 수 보장
 */
export function selectWithinBudget(
    chunks: RetrievalChunk[],
    config: Partial<ContextBudgetConfig> = {}
): { selected: RetrievalChunk[]; dropped: RetrievalChunk[]; tokensUsed: number } {
    const cfg = { ...DEFAULT_BUDGET, ...config };
    const budget = Math.floor(cfg.totalBudget * cfg.retrievalRatio);

    // 1. Sort by score descending
    const sorted = [...chunks].sort((a, b) => b.score - a.score);

    // 2. [Structural Fix] 파일당 청크 수 제한 완화 (Deduplication -> Multi-context)
    const fileChunkCounts = new Map<string, number>();
    const filtered = sorted.filter((chunk) => {
        const key = chunk.metadata.filePath || chunk.id;
        const count = fileChunkCounts.get(key) || 0;
        
        // 파일당 최대 3개까지의 주요 맥락 허용 (정보 유실 방지)
        if (count >= 3) return false;
        
        fileChunkCounts.set(key, count + 1);
        return true;
    });

    // 3. Select within budget
    const selected: RetrievalChunk[] = [];
    const dropped: RetrievalChunk[] = [];
    let tokensUsed = 0;

    for (const chunk of filtered) {
        const chunkTokens = chunk.tokenEstimate || estimateTokens(chunk.content);

        if (selected.length >= cfg.maxChunks) {
            dropped.push(chunk);
            continue;
        }

        if (tokensUsed + chunkTokens > budget && selected.length >= cfg.minChunks) {
            dropped.push(chunk);
            continue;
        }

        selected.push(chunk);
        tokensUsed += chunkTokens;
    }

    return { selected, dropped, tokensUsed };
}

/**
 * 청크의 '주제(Subject)' 태그를 도출한다 — 서로 다른 프로젝트/주제의 정보가 한
 * 컨텍스트에 섞일 때 모델이 경계를 인지하도록(무성 교차오염 방지). category 가 있으면
 * 그걸, 없으면 title/filePath 의 최상위 폴더 세그먼트를 주제로 본다. 파일명만 있으면 ''.
 */
function deriveSubject(chunk: RetrievalChunk): string {
    const cat = (chunk.metadata.category || '').trim();
    if (cat) return cat;
    const ref = (chunk.title || chunk.metadata.filePath || '').replace(/\\/g, '/');
    const seg = ref.split('/').filter(Boolean);
    return seg.length >= 2 ? seg[0] : '';
}

/**
 * 선택된 청크들을 하나의 컨텍스트 문자열로 조립합니다.
 * 소스별로 그룹화하여 가독성을 높입니다.
 */
export function assembleContext(chunks: RetrievalChunk[]): string {
    if (chunks.length === 0) return '';

    const sourceLabels: Record<string, string> = {
        'brain-trace': '📚 Second Brain Knowledge',
        'brain-memory': '📚 Brain Knowledge',
        'long-term-memory': '🧠 Long-Term Memory (사용자 규칙/결정)',
        'medium-term-memory': '🗂️ Medium-Term Memory (최근 세션 요약)',
        'project-memory': '📂 Project Memory (프로젝트 컨텍스트)',
        'procedural-memory': '📋 Procedural Memory (반복 절차)',
        'episodic-memory': '📖 Episodic Memory (과거 대화 흐름)',
        'project-scan': '🔍 Project Scan',
        'recent-knowledge': '📄 Recent Project Knowledge'
    };

    // Group by source
    const groups = new Map<string, RetrievalChunk[]>();
    for (const chunk of chunks) {
        const key = chunk.source;
        if (!groups.has(key)) groups.set(key, []);
        groups.get(key)!.push(chunk);
    }

    const sections: string[] = [];
    for (const [source, groupChunks] of groups) {
        const label = sourceLabels[source] || source;
        const items = groupChunks
            .map((c) => {
                const metadata = c.metadata;
                const subject = deriveSubject(c);
                const subjectTag = subject ? `[${subject}] ` : '';
                const conflictTag = metadata.conflictDetected ? ` [⚠️ CONFLICT: ${metadata.conflictSeverity}]` : '';
                const coverageTag = metadata.queryCoverage !== undefined ? ` (Coverage: ${metadata.queryCoverage.toFixed(2)})` : '';
                return `- ${subjectTag}${c.title}${conflictTag}${coverageTag}: ${c.content}`;
            })
            .join('\n');
        sections.push(`### ${label}\n${items}`);
    }

    return [
        '[MEMORY CONTEXT]',
        'Review this layered memory before preparing the answer. Use it only when relevant, and prefer the current user request when there is conflict.',
        '각 항목 앞의 [주제] 태그와 섹션 출처를 확인하라. **현재 요청과 다른 프로젝트·주제의 항목은 사용하지 마라** — 서로 다른 프로젝트의 규칙·결정·수치·고유명사를 섞지 말 것. 어느 항목이 현재 작업과 관련 있는지 불확실하면 그 항목에 의존하지 마라.',
        '',
        sections.join('\n\n')
    ].join('\n');
}
