/**
 * Knowledge Mix — model 지식 vs Second Brain 지식의 *상대 비율*을 LLM에게
 * 전달하는 정책 레이어.
 *
 * ── 정책 v2 (상대값+상대값=상대값) ──────────────────────────────────────
 * weight는 0~100 정수이지만 *상대 비율*로만 해석한다. 즉 "70"은 "100% 중
 * 70%"라는 상대 표현이고, 시스템이 도중에 *절대 정수*(예: brain 파일 N개)
 * 로 변환하지 않는다. 절대 변환은 v1의 핵심 약점이었다 — 사용자가 입력한
 * 상대적 의미가 brain 파일 개수라는 절대 정수로 펴지면서 LLM이 받는
 * 비율 정보와 retrieve된 실제 양이 따로 놀았다.
 *
 * v2에서 weight가 *실제로* 영향을 미치는 곳은 단 한 군데:
 *   - `buildKnowledgeMixPolicy` — LLM 시스템 프롬프트에 "model X% / brain Y%"
 *     자연어 정책을 삽입. 비율 그 자체만 모델에게 전달.
 *
 * 절대값 측면(brain 파일 개수, context 예산 비율)은 사용자 설정
 * (`memoryLongTermFiles`)을 그대로 사용. 두 극단값(weight=0, weight=100)만
 * 안전 차원에서 절대 의미를 유지 — 0이면 0개·5% 예산, 100이면 50% 예산.
 *
 * 우선순위: per-agent override → global config → DEFAULT_WEIGHT(50).
 */
import { getConfig } from '../config';
import { getOrCreateAgentEntry } from '../skills/agentKnowledgeMap';

export const DEFAULT_WEIGHT = 50;

/** Where the resolved weight came from — surfaced in `_lastRetrievalInfo` for UX. */
export type KnowledgeMixSource = 'agent' | 'global' | 'default';

export interface ResolvedKnowledgeMix {
    /** Integer in [0, 100]. */
    weight: number;
    source: KnowledgeMixSource;
    /** Agent name when `source === 'agent'`, else undefined. */
    agent?: string;
}

/**
 * Resolve the effective weight for the active turn.
 *
 * Precedence: per-agent → global config → default. Out-of-range values from
 * either source are clamped, never silently zeroed (a typo in JSON should not
 * disable retrieval entirely).
 */
export function resolveKnowledgeMix(agentFileOrName?: string): ResolvedKnowledgeMix {
    if (agentFileOrName && agentFileOrName !== 'none') {
        try {
            const entry = getOrCreateAgentEntry(agentFileOrName);
            if (typeof entry.secondBrainWeight === 'number' && Number.isFinite(entry.secondBrainWeight)) {
                return {
                    weight: _clamp(entry.secondBrainWeight),
                    source: 'agent',
                    agent: entry.name,
                };
            }
        } catch {
            // Map missing or unreadable — fall through to global.
        }
    }
    try {
        const cfg = getConfig();
        if (typeof cfg.knowledgeMixSecondBrainWeight === 'number') {
            return { weight: _clamp(cfg.knowledgeMixSecondBrainWeight), source: 'global' };
        }
    } catch {
        // getConfig should never throw in practice, but keep this safe in tests.
    }
    return { weight: DEFAULT_WEIGHT, source: 'default' };
}

function _clamp(n: number): number {
    if (!Number.isFinite(n)) return DEFAULT_WEIGHT;
    return Math.max(0, Math.min(100, Math.round(n)));
}

/**
 * Brain 파일 *최대 개수*를 결정.
 *
 * Knowledge Mix v2 정책 (상대값+상대값=상대값):
 * weight는 LLM에게 *얼마나 신뢰할지*를 전달하는 상대 비율일 뿐, brain 파일
 * *개수 자체*를 좌우하지 않는다. 사용자가 명시적으로 설정한
 * `memoryLongTermFiles`(=`configuredLimit`)를 그대로 사용.
 *
 * weight가 의미를 가지는 극단값 두 가지만 절대 의미를 유지한다:
 *   - weight=0   → 0개  (사용자가 명시적으로 "brain 사용 안 함" 선언)
 *   - 그 외      → configuredLimit 그대로
 *
 * 이전 v1은 weight=70이면 8개, weight=30이면 4개 식으로 절대 정수 변환을 했고
 * 이게 "사용자가 의도한 70%라는 상대 비율"의 의미를 도중에 잃게 만드는
 * 원인이었다. 비율 표현은 `buildKnowledgeMixPolicy`가 LLM에게 자연어로
 * 전달하는 역할 하나만 맡는다.
 */
export function mapWeightToBrainFileLimit(weight: number, configuredLimit: number): number {
    const w = _clamp(weight);
    if (w === 0) return 0;
    const baseline = Math.max(1, configuredLimit || 6);
    // 안전 상한 12는 그대로 — context budget 폭주 방지. 그 외엔 사용자 설정 그대로.
    return Math.max(0, Math.min(12, baseline));
}

/**
 * Brain retrieval에 할당할 context-budget 비율.
 *
 * Knowledge Mix v2 정책: weight와 *분리*. budget 분배는 시스템 안정성에 관한
 * 절대 결정이지 사용자가 입력한 상대 비율이 직접 좌우할 일이 아니다. 다만
 * 두 극단값에서만 의미를 유지:
 *   - weight=0   → 0.05  (검색 자체가 비활성화되어도 다른 컨텍스트는 살림)
 *   - weight=100 → 0.50  (brain이 거의 유일한 근거일 때 더 큰 슬라이스)
 *   - 그 외      → 0.40  (균형 baseline)
 */
export function mapWeightToRetrievalRatio(weight: number): number {
    const w = _clamp(weight);
    if (w === 0) return 0.05;
    if (w === 100) return 0.5;
    return 0.4;
}

/**
 * Build the natural-language policy block injected into the system prompt.
 * Returns `''` when the weight is exactly the default (50) — at the midpoint
 * there's nothing useful to tell the model, and quieter prompts behave better.
 */
export function buildKnowledgeMixPolicy(mix: ResolvedKnowledgeMix): string {
    const w = _clamp(mix.weight);
    if (w === DEFAULT_WEIGHT) return '';
    const header = `[KNOWLEDGE MIX POLICY]\nSecond Brain reliance: ${w}% (model knowledge: ${100 - w}%).`;
    let body: string;
    if (w === 0) {
        body = [
            'Second Brain retrieval is disabled for this turn.',
            'Answer from your general knowledge and the conversation history alone.',
            'Do not invent file citations.',
        ].join('\n');
    } else if (w < 25) {
        body = [
            'Rely primarily on your own general knowledge.',
            'Treat any Second Brain notes shown below as light reference material only.',
            'Brainstorming, broad explanations and creative synthesis are encouraged.',
        ].join('\n');
    } else if (w < 50) {
        body = [
            'Lean on your general knowledge; use Second Brain notes as supporting context.',
            'Cite Brain files only when they materially shape the answer.',
        ].join('\n');
    } else if (w < 75) {
        body = [
            'Prefer Second Brain evidence when it is present.',
            'Use your general knowledge to connect, explain, and fill harmless background.',
            'Do not override explicit Second Brain evidence with model assumptions.',
        ].join('\n');
    } else if (w < 100) {
        body = [
            'Treat Second Brain notes as the primary evidence for this answer.',
            'Cite Brain files for any non-trivial claim and quote relevant lines when needed.',
            'If the notes do not cover a point, say so explicitly instead of guessing.',
        ].join('\n');
    } else {
        body = [
            'Second Brain notes are the only authoritative source for this answer.',
            'Cite Brain files for every substantive claim.',
            'If a point is not in the notes, reply that it is outside the recorded knowledge — do not fall back to general knowledge.',
        ].join('\n');
    }
    return `${header}\n${body}`;
}
