/**
 * ============================================================
 * Long-Term Memory (장기 기억)
 * 
 * 사용자의 취향, 프로젝트 목표, 반복 규칙, 과거 결정 사항을 
 * 영구적으로 저장하고 관리합니다.
 * 저장 위치: {brainPath}/memory/long_term.json
 * ============================================================
 */

import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import { LongTermEntry, LongTermStore, LongTermCategory, MemoryContextResult } from './types';

export class LongTermMemory {
    private store: LongTermStore;
    private filePath: string;
    private dirty = false;

    constructor(brainPath: string) {
        const memoryDir = path.join(brainPath, 'memory');
        if (!fs.existsSync(memoryDir)) {
            fs.mkdirSync(memoryDir, { recursive: true });
        }
        this.filePath = path.join(memoryDir, 'long_term.json');
        this.store = this.load();
    }

    // ─── Persistence ───

    private load(): LongTermStore {
        try {
            if (fs.existsSync(this.filePath)) {
                const raw = fs.readFileSync(this.filePath, 'utf-8');
                return JSON.parse(raw) as LongTermStore;
            }
        } catch { /* start fresh */ }
        return { version: 1, entries: [], lastUpdated: Date.now() };
    }

    public save(): void {
        if (!this.dirty) return;
        try {
            this.store.lastUpdated = Date.now();
            fs.writeFileSync(this.filePath, JSON.stringify(this.store, null, 2), 'utf-8');
            this.dirty = false;
        } catch { /* silently fail — memory is not critical path */ }
    }

    // ─── CRUD ───

    public addEntry(category: LongTermCategory, content: string, source: string, confidence = 0.8): LongTermEntry {
        const entry: LongTermEntry = {
            id: crypto.randomUUID(),
            category,
            content: content.trim(),
            source,
            confidence,
            createdAt: Date.now(),
            lastReferencedAt: Date.now(),
            referenceCount: 0
        };
        this.store.entries.push(entry);
        this.dirty = true;
        this.save();
        return entry;
    }

    public removeEntry(id: string): boolean {
        const before = this.store.entries.length;
        this.store.entries = this.store.entries.filter((e) => e.id !== id);
        if (this.store.entries.length < before) {
            this.dirty = true;
            this.save();
            return true;
        }
        return false;
    }

    public getAllEntries(): LongTermEntry[] {
        return [...this.store.entries];
    }

    public getEntriesByCategory(category: LongTermCategory): LongTermEntry[] {
        return this.store.entries.filter((e) => e.category === category);
    }

    // ─── Context Building ───

    /**
     * 프롬프트와 관련성이 높은 Long-Term 기억을 반환합니다.
     */
    public buildContext(currentPrompt: string, maxEntries = 10): MemoryContextResult | null {
        if (this.store.entries.length === 0) return null;

        const promptLower = currentPrompt.toLowerCase();
        const terms = promptLower
            .split(/[^a-z0-9가-힣_]+/g)
            .filter((t) => t.length >= 2);

        // Score entries by relevance to prompt
        const scored = this.store.entries.map((entry) => {
            let score = 0;
            const contentLower = entry.content.toLowerCase();

            for (const term of terms) {
                if (contentLower.includes(term)) score += 2;
            }

            // Boost high-confidence and frequently referenced entries
            score += entry.confidence * 2;
            score += Math.min(entry.referenceCount * 0.5, 3);

            // Recency boost
            const daysSinceRef = (Date.now() - entry.lastReferencedAt) / (1000 * 60 * 60 * 24);
            if (daysSinceRef < 7) score += 1;

            return { entry, score };
        });

        const relevant = scored
            .filter((s) => s.score > 0)
            .sort((a, b) => b.score - a.score)
            .slice(0, maxEntries);

        if (relevant.length === 0) {
            // Still include all rules and goals even without prompt match
            const alwaysInclude = this.store.entries
                .filter((e) => e.category === 'rule' || e.category === 'goal')
                .slice(0, 5);
            if (alwaysInclude.length === 0) return null;

            const content = alwaysInclude
                .map((e) => `- [${e.category}] ${e.content}`)
                .join('\n');

            return {
                layer: 'long-term',
                label: 'Long-Term Memory (사용자 규칙 & 목표)',
                content,
                relevance: 0.5
            };
        }

        // Mark as referenced
        for (const { entry } of relevant) {
            entry.lastReferencedAt = Date.now();
            entry.referenceCount++;
        }
        this.dirty = true;

        const content = relevant
            .map(({ entry }) => `- [${entry.category}] ${entry.content}`)
            .join('\n');

        return {
            layer: 'long-term',
            label: 'Long-Term Memory (사용자 취향 / 규칙 / 결정)',
            content,
            relevance: Math.min(relevant[0]?.score / 10 || 0.5, 1.0)
        };
    }

    // ─── Extraction Helpers ───

    /**
     * 대화 메시지에서 장기 기억 후보를 패턴 매칭으로 추출합니다.
     * LLM 호출 없이 동작합니다.
     */
    public static extractCandidates(
        messages: Array<{ role: string; content: string }>
    ): Array<{ category: LongTermCategory; content: string }> {
        const candidates: Array<{ category: LongTermCategory; content: string }> = [];

        const rulePatterns = [
            /(?:항상|언제나|무조건|반드시)\s+(.{5,80})/g,
            /(?:규칙|rule|원칙)[\s:：]+(.{5,120})/gi,
            /(?:앞으로는?|이후에는?|다음부터는?)\s+(.{5,80})/g
        ];

        const preferencePatterns = [
            /(?:난|나는|저는|제가)\s+(.{5,60})\s*(?:좋아|선호|원해|싫어|안 ?좋아)/g,
            /(?:prefer|always use|don't use|never use)\s+(.{5,80})/gi
        ];

        const goalPatterns = [
            /(?:목표|goal|방향|direction)[\s:：]+(.{5,120})/gi,
            /(?:최종\s*목표|궁극적으로|결국에는?)\s+(.{5,80})/g
        ];

        const decisionPatterns = [
            /(?:결정|decided|결론|conclusion)[\s:：]+(.{5,120})/gi,
            /(?:으로\s*하자|으로\s*가자|으로\s*결정|으로\s*확정)/g
        ];

        for (const msg of messages) {
            if (msg.role !== 'user') continue;
            const text = msg.content;

            for (const pattern of rulePatterns) {
                pattern.lastIndex = 0;
                let match;
                while ((match = pattern.exec(text)) !== null) {
                    candidates.push({ category: 'rule', content: match[0].trim() });
                }
            }

            for (const pattern of preferencePatterns) {
                pattern.lastIndex = 0;
                let match;
                while ((match = pattern.exec(text)) !== null) {
                    candidates.push({ category: 'preference', content: match[0].trim() });
                }
            }

            for (const pattern of goalPatterns) {
                pattern.lastIndex = 0;
                let match;
                while ((match = pattern.exec(text)) !== null) {
                    candidates.push({ category: 'goal', content: match[0].trim() });
                }
            }

            for (const pattern of decisionPatterns) {
                pattern.lastIndex = 0;
                let match;
                while ((match = pattern.exec(text)) !== null) {
                    candidates.push({ category: 'decision', content: match[0].trim() });
                }
            }
        }

        // Deduplicate by content
        const seen = new Set<string>();
        return candidates.filter((c) => {
            const key = c.content.toLowerCase();
            if (seen.has(key)) return false;
            seen.add(key);
            return true;
        });
    }
}
