/* ===================================================
   JAESWIFT.XYZ — JAE-AI Memory System (Memoria-style)
   Browser-local persistent memory for chat continuity
   All data stays in localStorage — never leaves device
   except as context in the next user message.
   =================================================== */

(function (global) {
    'use strict';

    const MEM_KEY = 'jae-ai-memory-v1';
    const HIST_KEY = 'jae-ai-history-v1';
    const MAX_MEMORIES = 100;
    const MAX_HISTORY = 50;
    const EXTRACT_EVERY_N_USER_MSGS = 4;

    // ─── Storage-safe wrappers ───
    function lsGet(key, fallback) {
        try {
            const raw = localStorage.getItem(key);
            if (!raw) return fallback;
            return JSON.parse(raw);
        } catch (e) {
            console.warn('[chat-memory] storage read fail', e);
            return fallback;
        }
    }

    function lsSet(key, val) {
        try {
            localStorage.setItem(key, JSON.stringify(val));
            return true;
        } catch (e) {
            console.warn('[chat-memory] storage write fail', e);
            return false;
        }
    }

    // ─── ID helper ───
    function uid() {
        return 'm_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
    }

    // ─── Memory ops ───
    function getAll() {
        return lsGet(MEM_KEY, []);
    }

    function saveAll(list) {
        return lsSet(MEM_KEY, list);
    }

    function prune(list) {
        if (list.length <= MAX_MEMORIES) return list;
        // sort by importance asc, then timestamp asc (oldest low importance first to drop)
        const sorted = [...list].sort(function (a, b) {
            if (a.importance !== b.importance) return a.importance - b.importance;
            return (a.timestamp || 0) - (b.timestamp || 0);
        });
        const toDrop = list.length - MAX_MEMORIES;
        const dropIds = new Set(sorted.slice(0, toDrop).map(function (m) { return m.id; }));
        return list.filter(function (m) { return !dropIds.has(m.id); });
    }

    // Simple normalized tokens for fuzzy matching
    function tokens(str) {
        return (str || '')
            .toLowerCase()
            .replace(/[^a-z0-9\s]/g, ' ')
            .split(/\s+/)
            .filter(function (t) { return t.length > 2; });
    }

    // Jaccard similarity of token sets
    function similarity(a, b) {
        const ta = new Set(tokens(a));
        const tb = new Set(tokens(b));
        if (ta.size === 0 || tb.size === 0) return 0;
        let inter = 0;
        ta.forEach(function (t) { if (tb.has(t)) inter++; });
        const union = ta.size + tb.size - inter;
        return union === 0 ? 0 : inter / union;
    }

    function add(text, category, importance) {
        text = (text || '').trim();
        if (!text) return null;
        category = category || 'other';
        importance = typeof importance === 'number' ? Math.max(0, Math.min(1, importance)) : 0.5;

        let list = getAll();

        // Dedup: if very similar memory exists, update importance instead of adding
        for (let i = 0; i < list.length; i++) {
            if (similarity(list[i].text, text) > 0.8) {
                list[i].importance = Math.max(list[i].importance || 0, importance);
                list[i].timestamp = Date.now();
                saveAll(list);
                return list[i];
            }
        }

        const mem = {
            id: uid(),
            text: text,
            category: category,
            importance: importance,
            timestamp: Date.now()
        };
        list.push(mem);
        list = prune(list);
        saveAll(list);
        return mem;
    }

    function remove(id) {
        const list = getAll().filter(function (m) { return m.id !== id; });
        saveAll(list);
    }

    function clear() {
        saveAll([]);
    }

    // ─── Relevance-ranked search ───
    function search(query, limit) {
        limit = limit || 5;
        const qTokens = tokens(query);
        const list = getAll();
        if (qTokens.length === 0 || list.length === 0) return [];

        const scored = list.map(function (m) {
            const mTokens = tokens(m.text);
            const mSet = new Set(mTokens);
            let overlap = 0;
            qTokens.forEach(function (t) { if (mSet.has(t)) overlap++; });
            // substring bonus
            const lowerText = m.text.toLowerCase();
            const lowerQ = (query || '').toLowerCase();
            let subBonus = 0;
            qTokens.forEach(function (t) {
                if (lowerText.indexOf(t) !== -1) subBonus += 0.3;
            });
            if (lowerQ && lowerText.indexOf(lowerQ) !== -1) subBonus += 1.0;

            const score = overlap + subBonus + (m.importance || 0) * 0.5;
            return { mem: m, score: score };
        });

        scored.sort(function (a, b) { return b.score - a.score; });
        return scored.filter(function (s) { return s.score > 0; }).slice(0, limit).map(function (s) { return s.mem; });
    }

    function recent(limit) {
        limit = limit || 3;
        return [...getAll()].sort(function (a, b) { return (b.timestamp || 0) - (a.timestamp || 0); }).slice(0, limit);
    }

    // ─── Context block for system prompt ───
    function getContextBlock(query) {
        const relevant = search(query, 5);
        const recentOnes = recent(3);
        const seen = new Set(relevant.map(function (m) { return m.id; }));
        const combined = relevant.slice();
        recentOnes.forEach(function (m) {
            if (!seen.has(m.id)) combined.push(m);
        });
        if (combined.length === 0) return '';

        const lines = combined.map(function (m) {
            return '- [' + (m.category || 'other') + '] ' + m.text;
        });
        return '[REMEMBERED FACTS ABOUT THIS USER]\n' + lines.join('\n') + '\n[END REMEMBERED FACTS]';
    }

    // ─── Chat history persistence ───
    function getHistory() {
        return lsGet(HIST_KEY, []);
    }

    function saveHistory(messages) {
        const trimmed = messages.slice(-MAX_HISTORY);
        return lsSet(HIST_KEY, trimmed);
    }

    function appendHistory(msg) {
        const h = getHistory();
        h.push(msg);
        saveHistory(h);
    }

    function clearHistory() {
        lsSet(HIST_KEY, []);
    }

    // ─── Memory extraction via backend ───
    let _userMsgCounter = 0;
    let _extracting = false;

    function incrementUserMsgAndMaybeExtract() {
        _userMsgCounter++;
        if (_userMsgCounter >= EXTRACT_EVERY_N_USER_MSGS) {
            _userMsgCounter = 0;
            return extractFromRecentChat();
        }
        return Promise.resolve(null);
    }

    function extractFromRecentChat() {
        if (_extracting) return Promise.resolve(null);
        _extracting = true;

        const hist = getHistory().slice(-8);
        if (hist.length < 2) {
            _extracting = false;
            return Promise.resolve(null);
        }

        return fetch('/api/chat/extract-memories', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ messages: hist })
        })
            .then(function (r) { return r.ok ? r.json() : []; })
            .then(function (data) {
                const arr = Array.isArray(data) ? data : (data && Array.isArray(data.memories) ? data.memories : []);
                const added = [];
                arr.forEach(function (item) {
                    if (item && typeof item.text === 'string' && item.text.trim()) {
                        const m = add(item.text, item.category || 'other', typeof item.importance === 'number' ? item.importance : 0.5);
                        if (m) added.push(m);
                    }
                });
                _extracting = false;
                return added;
            })
            .catch(function (e) {
                console.warn('[chat-memory] extraction failed', e);
                _extracting = false;
                return null;
            });
    }

    // ─── Export JSON ───
    function exportJSON() {
        const data = {
            version: 1,
            exported: new Date().toISOString(),
            memories: getAll(),
            history: getHistory()
        };
        return JSON.stringify(data, null, 2);
    }

    // ─── Public API ───
    global.chatMemory = {
        add: add,
        getAll: getAll,
        search: search,
        remove: remove,
        clear: clear,
        getContextBlock: getContextBlock,
        extractFromRecentChat: extractFromRecentChat,
        incrementUserMsgAndMaybeExtract: incrementUserMsgAndMaybeExtract,
        getHistory: getHistory,
        saveHistory: saveHistory,
        appendHistory: appendHistory,
        clearHistory: clearHistory,
        exportJSON: exportJSON
    };

})(window);
