/**
 * 레슨 네트워크 — A-MEM(NeurIPS 2025, Zettelkasten 원리)의 ASTRA 이식.
 *
 * 문제: lessons/ 가 고립된 카드 모음 — 새 레슨이 과거의 비슷한 교훈과 연결되지
 * 않아 "같은 종류의 실수"라는 패턴이 파일 수준에서 보이지 않는다.
 *
 * A-MEM 의 두 메커니즘을 파일 기반으로 구현:
 *  1. 동적 링킹: 새 레슨 저장 시 기존 레슨과 유사도(토큰 자카드)를 계산해
 *     상위 K 개를 "## 관련 레슨" 섹션으로 연결.
 *  2. 역방향 갱신(memory evolution): 연결된 기존 레슨에도 새 레슨으로의
 *     백링크를 추가 — 네트워크가 양방향으로 자란다.
 *
 * 레슨 수가 적으므로(수백 이하) 임베딩 없이 토큰 유사도로 충분 — LLM 호출 0,
 * 캡처 경로의 지연 비용 없음. 링크는 [[제목]] 위키링크 — 두뇌 RAG 가 그대로 검색.
 */
import * as fs from 'fs';
import * as path from 'path';
import { tokenize } from '../retrieval/scoring';

const RELATED_HEADING = '## 관련 레슨';
const MAX_LINKS = 3;
const MIN_SIMILARITY = 0.12;
const MAX_LESSONS_SCANNED = 300;

/** 토큰 자카드 유사도 (순수). */
export function lessonSimilarity(tokensA: string[], tokensB: string[]): number {
    if (!tokensA.length || !tokensB.length) return 0;
    const a = new Set(tokensA), b = new Set(tokensB);
    let inter = 0;
    for (const t of a) if (b.has(t)) inter++;
    return inter / (a.size + b.size - inter);
}

/**
 * 본문에 "## 관련 레슨" 링크를 추가 (섹션 없으면 생성, 있으면 항목 append).
 * 이미 같은 링크가 있으면 그대로 반환 (멱등 — 재실행 안전).
 */
export function addRelatedLink(content: string, linkTitle: string): string {
    const link = `- [[${linkTitle}]]`;
    if (content.includes(link)) return content;
    const idx = content.indexOf(RELATED_HEADING);
    if (idx < 0) {
        return content.trimEnd() + `\n\n${RELATED_HEADING}\n${link}\n`;
    }
    // 섹션 끝(다음 헤딩 또는 EOF) 직전에 삽입.
    const after = content.slice(idx + RELATED_HEADING.length);
    const nextHeading = after.search(/\n#{1,6}\s/);
    const insertAt = idx + RELATED_HEADING.length + (nextHeading >= 0 ? nextHeading : after.length);
    return content.slice(0, insertAt).trimEnd() + `\n${link}` + content.slice(insertAt);
}

/**
 * 새 레슨을 기존 레슨 네트워크에 연결한다 — 상위 K 유사 레슨과 상호 링크.
 * 반환: 연결된 레슨 수. 실패는 0 (레슨 저장 자체를 막지 않음).
 */
export function linkRelatedLessons(brainPath: string, newLessonFile: string): number {
    try {
        const lessonsDir = path.join(brainPath, 'lessons');
        if (!fs.existsSync(lessonsDir) || !fs.existsSync(newLessonFile)) return 0;
        const newContent = fs.readFileSync(newLessonFile, 'utf8');
        const newTitle = path.basename(newLessonFile, '.md');
        const newTokens = tokenize(newContent.slice(0, 3000));

        const candidates = fs.readdirSync(lessonsDir)
            .filter(f => f.endsWith('.md') && path.join(lessonsDir, f) !== newLessonFile)
            .slice(0, MAX_LESSONS_SCANNED)
            .map(f => {
                const fp = path.join(lessonsDir, f);
                try {
                    const c = fs.readFileSync(fp, 'utf8');
                    return { fp, title: path.basename(f, '.md'), content: c, sim: lessonSimilarity(newTokens, tokenize(c.slice(0, 3000))) };
                } catch { return null; }
            })
            .filter((x): x is NonNullable<typeof x> => !!x && x.sim >= MIN_SIMILARITY)
            .sort((a, b) => b.sim - a.sim)
            .slice(0, MAX_LINKS);

        if (candidates.length === 0) return 0;

        // 새 레슨 → 관련 레슨 링크.
        let updatedNew = newContent;
        for (const c of candidates) updatedNew = addRelatedLink(updatedNew, c.title);
        fs.writeFileSync(newLessonFile, updatedNew, 'utf8');

        // 역방향 갱신 — 기존 레슨에 백링크 (A-MEM memory evolution).
        for (const c of candidates) {
            try { fs.writeFileSync(c.fp, addRelatedLink(c.content, newTitle), 'utf8'); } catch { /* 한쪽 실패 무시 */ }
        }
        return candidates.length;
    } catch {
        return 0;
    }
}
