/**
 * Actionability Scoring — 검색 결과를 "현재 작업 상태" 신호로 재가중.
 *
 * 기존 TF-IDF (단어 매칭) + recency (시간) 만으로는 "지금 이 사용자가 하고 있는
 * 작업과 *직접 연결* 된 문서" 가 우선되지 않음. 예: 사용자가 `/runway` 명령을
 * 막 실행했다면 runway / 재무 관련 문서가 같은 키워드 매치 점수여도 더 위로 와야 함.
 *
 * v1 신호 (사용자 선택):
 *   1. **최근 슬래시 명령** — 마지막 N개 실행된 슬래시 명령 이름을 키워드로 활용
 *      → 명령 이름이 chunk title/content 에 포함되면 boost
 *   2. **열린 파일 경로** — VS Code 활성 에디터의 파일 이름·확장자·부모 디렉터리를
 *      키워드로 활용
 *
 * 점수 결합: TF-IDF normalized score (0~1) × (1 + actionabilityScore × weight)
 *   - weight 기본 0.3 → actionability=1.0 인 chunk 는 30% boost
 *   - actionability=0.0 인 chunk 는 변화 없음
 *   - TF-IDF 가 여전히 dominant 인 보수적 합산
 *
 * 향후 신호 (#1 v2 후보 — 사용자 선택 안 함):
 *   3. 최근 7일 Chronicle ADR / decisions
 *   4. 최근 24시간 customers/hire/runway 이벤트
 */

import * as path from 'path';
import * as vscode from 'vscode';
import { RetrievalChunk } from './types';

export interface WorkStateSignals {
    /** 최근 실행된 슬래시 명령 이름 목록 ('/' 포함, 최신 순). 빈 배열이면 신호 없음. */
    recentSlashCommands: string[];
    /** VS Code 활성 에디터의 파일 절대 경로. undefined 면 신호 없음. */
    openFilePath?: string;
}

export interface ActionabilityWeights {
    /** 슬래시 명령 매치당 boost. 기본 0.30. */
    slashCommandMatch: number;
    /** 파일명 매치 boost. 기본 0.40 (가장 강함). */
    openFileNameMatch: number;
    /** 부모 디렉터리 매치 boost. 기본 0.20. */
    openFileParentDirMatch: number;
    /** 확장자 의미 매치 boost (e.g. .ts → typescript/tsx 단어). 기본 0.10. */
    openFileExtMatch: number;
    /** 최종 결합 가중치 — finalScore = base × (1 + actionability × this). 기본 0.30. */
    combinedWeight: number;
}

export const DEFAULT_ACTIONABILITY_WEIGHTS: ActionabilityWeights = {
    slashCommandMatch: 0.30,
    openFileNameMatch: 0.40,
    openFileParentDirMatch: 0.20,
    openFileExtMatch: 0.10,
    combinedWeight: 0.30,
};

/** VS Code 활성 에디터·최근 슬래시 명령에서 work-state 신호 캡처. */
export function captureWorkStateSignals(recentSlashCommands: string[]): WorkStateSignals {
    const editor = vscode.window.activeTextEditor;
    return {
        recentSlashCommands: recentSlashCommands.slice(0, 5), // 최신 5개로 cap
        openFilePath: editor?.document.uri.fsPath,
    };
}

const EXTENSION_KEYWORDS: Record<string, RegExp> = {
    '.ts': /\b(typescript|tsx?|ts)\b/i,
    '.tsx': /\b(typescript|tsx|react)\b/i,
    '.js': /\b(javascript|jsx?)\b/i,
    '.jsx': /\b(javascript|jsx|react)\b/i,
    '.py': /\b(python|py)\b/i,
    '.md': /\b(markdown|md|문서)\b/i,
    '.json': /\b(json|config)\b/i,
    '.go': /\b(golang|go)\b/i,
    '.rs': /\b(rust|rs)\b/i,
};

/**
 * 한 chunk 의 actionability 점수 계산 — 0.0 ~ 1.0 (cap).
 * 매치 boost 들의 단순 합산 후 1.0 cap.
 */
export function computeActionabilityScore(
    chunk: RetrievalChunk,
    signals: WorkStateSignals,
    weights: ActionabilityWeights = DEFAULT_ACTIONABILITY_WEIGHTS,
): number {
    if (!chunk) return 0;
    const haystack = ((chunk.title || '') + ' ' + (chunk.content || '')).toLowerCase();
    if (!haystack.trim()) return 0;

    let score = 0;

    // Signal 1: 최근 슬래시 명령 — '/runway' → 'runway' 키워드 매치
    for (const cmd of signals.recentSlashCommands) {
        const kw = cmd.replace(/^\//, '').toLowerCase().trim();
        if (kw.length < 3) continue; // 너무 짧으면 노이즈 (/q 등)
        // 단어 경계 매치 — substring 이 아닌 단어 단위 (영문 한정, 한글은 substring)
        const isAscii = /^[a-z0-9-]+$/.test(kw);
        const wordRe = isAscii ? new RegExp(`\\b${escapeRegex(kw)}\\b`, 'i') : null;
        if (wordRe ? wordRe.test(haystack) : haystack.includes(kw)) {
            score += weights.slashCommandMatch;
        }
    }

    // Signal 2: 열린 파일 — 파일명·부모 디렉터리·확장자
    if (signals.openFilePath) {
        const fp = signals.openFilePath;
        const ext = path.extname(fp).toLowerCase();
        const base = path.basename(fp, ext).toLowerCase();
        const parent = path.basename(path.dirname(fp)).toLowerCase();

        // 파일 자체가 chunk 의 filePath 와 같으면 강한 boost (max)
        const chunkFile = chunk.metadata?.filePath?.toLowerCase();
        if (chunkFile && chunkFile === fp.toLowerCase()) {
            score += weights.openFileNameMatch * 1.5; // exact file = 보너스
        } else if (base.length >= 3) {
            const baseRe = new RegExp(`\\b${escapeRegex(base)}\\b`, 'i');
            if (baseRe.test(haystack)) score += weights.openFileNameMatch;
        }

        if (parent.length >= 3 && parent !== 'src' && parent !== 'lib') {
            // 'src' / 'lib' 같은 일반 디렉터리는 신호로서 약함 — 제외
            const parentRe = new RegExp(`\\b${escapeRegex(parent)}\\b`, 'i');
            if (parentRe.test(haystack)) score += weights.openFileParentDirMatch;
        }

        const extRe = EXTENSION_KEYWORDS[ext];
        if (extRe && extRe.test(haystack)) score += weights.openFileExtMatch;
    }

    return Math.min(score, 1.0);
}

function escapeRegex(s: string): string {
    return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

/**
 * Chunks 배열의 score 를 actionability 로 재가중. *원본 score 를 덮어씀* — 호출자는
 * 반드시 retrieval pipeline 안에서 normalizeScores 직후, selectWithinBudget 직전에만 호출.
 *
 * 각 chunk 에 actionabilityScore 를 metadata 에 기록 (디버깅·UI 표시 용도).
 */
export function applyActionabilityBoost(
    chunks: RetrievalChunk[],
    signals: WorkStateSignals,
    weights: ActionabilityWeights = DEFAULT_ACTIONABILITY_WEIGHTS,
): void {
    if (!signals || (signals.recentSlashCommands.length === 0 && !signals.openFilePath)) return;
    for (const c of chunks) {
        const a = computeActionabilityScore(c, signals, weights);
        if (a > 0) {
            (c.metadata as any).actionabilityScore = a;
            c.score = c.score * (1 + a * weights.combinedWeight);
        }
    }
}
