/**
 * Fractal Narrative Memory System
 * Implements multi-scale narrative memory with fractal segmentation,
 * graph-based relationship tracking, and motif clustering
 */
import { EventEmitter } from 'events';
import Database from 'better-sqlite3';
import nlp from 'compromise';
import { HolographicMemorySystem } from './hhm/holographic-memory-system.js';
import { getLogger } from '../../core/logger.js';
// ============================================================================
// Fractal Segmentation Engine
// ============================================================================
export class FractalSegmenter {
    constructor(config) {
        this.logger = getLogger('FractalSegmenter');
        this.config = config;
    }
    async segment(chapterText, chapterIndex) {
        this.logger.debug(`Segmenting chapter ${chapterIndex}`);
        // 1. Create micro segments (sentences/beats)
        const microSegments = await this.createMicroSegments(chapterText, chapterIndex);
        // 2. Create meso segments (scenes/blocks)
        const mesoSegments = await this.createMesoSegments(chapterText, chapterIndex, microSegments);
        // 3. Create macro segment for chapter
        const macroSegment = this.createMacroSegment(chapterText, chapterIndex, mesoSegments);
        return {
            micro: microSegments,
            meso: mesoSegments,
            macro: [macroSegment],
        };
    }
    async createMicroSegments(text, chapterIndex) {
        const sentences = await this.splitIntoSentences(text);
        const microSegments = [];
        for (let i = 0; i < sentences.length; i++) {
            const sent = sentences[i];
            const beats = this.splitLongSentence(sent);
            for (let j = 0; j < beats.length; j++) {
                const beat = beats[j];
                if (typeof beat === 'string') {
                    // Handle string beat (simplified case)
                    microSegments.push({
                        id: `micro_${chapterIndex}_${i}_${j}`,
                        chapter: chapterIndex,
                        paraIndex: this.getParagraphIndex(sent.startChar, text),
                        sentIndex: i,
                        beatIndex: beats.length > 1 ? j : undefined,
                        text: beat,
                        startChar: sent.startChar,
                        endChar: sent.endChar,
                        tokens: this.countTokens(beat),
                    });
                }
                else if (typeof beat === 'object' && beat !== null && 'text' in beat) {
                    // Handle object beat with text property
                    microSegments.push({
                        id: `micro_${chapterIndex}_${i}_${j}`,
                        chapter: chapterIndex,
                        paraIndex: this.getParagraphIndex(sent.startChar, text),
                        sentIndex: i,
                        beatIndex: beats.length > 1 ? j : undefined,
                        text: beat.text,
                        startChar: beat
                            .startChar,
                        endChar: beat
                            .endChar,
                        tokens: this.countTokens(beat.text),
                    });
                }
            }
        }
        return microSegments;
    }
    async createMesoSegments(text, chapterIndex, microSegments) {
        // Try to detect natural scene boundaries
        const sceneBreaks = this.detectSceneBreaks(text);
        if (sceneBreaks.length > 0) {
            return this.createScenesFromBreaks(text, chapterIndex, microSegments, sceneBreaks);
        }
        else {
            // Fall back to sliding windows
            return this.createSlidingWindows(text, chapterIndex, microSegments);
        }
    }
    detectSceneBreaks(text) {
        const breaks = [0];
        // Look for explicit scene markers
        const sceneMarkers = [
            /\n\n\*\*\*\n\n/g, // asterisk breaks
            /\n\n---\n\n/g, // dash breaks
            /\n\n\s*\n\n/g, // multiple blank lines
            /Chapter \d+/gi, // chapter markers
        ];
        for (const marker of sceneMarkers) {
            let match;
            while ((match = marker.exec(text)) !== null) {
                breaks.push(match.index);
            }
        }
        return [...new Set(breaks)].sort((a, b) => a - b);
    }
    createMacroSegment(text, chapterIndex, mesoSegments) {
        return {
            id: `macro_ch${chapterIndex}`,
            chapterOrArc: `Chapter ${chapterIndex}`,
            startChar: 0,
            endChar: text.length,
            text,
            mesoIds: mesoSegments.map((m) => m.id),
            arcType: this.detectArcType(text),
        };
    }
    splitLongSentence(sentence) {
        const { text } = sentence;
        const tokens = this.countTokens(text);
        if (tokens <= this.config.microTokenRange[1]) {
            return [sentence];
        }
        // Split on semicolons, then commas if needed
        const beats = [];
        // const splitPoints = [';', ',', ' and ', ' but ', ' or '];
        if (tokens > this.config.microTokenRange[1]) {
            const words = text.split(' ');
            const chunkSize = Math.ceil(words.length / Math.ceil(tokens / this.config.microTokenRange[1]));
            for (let i = 0; i < words.length; i += chunkSize) {
                beats.push(words.slice(i, i + chunkSize).join(' '));
            }
        }
        return beats.length > 0 ? beats : [sentence];
    }
    countTokens(text) {
        return text.split(/\s+/).length;
    }
    getParagraphIndex(charPos, text) {
        const beforeText = text.substring(0, charPos);
        return (beforeText.match(/\n\n/g) || []).length;
    }
    async splitIntoSentences(text) {
        // Production-ready sentence splitting using NLP
        const doc = nlp(text);
        const sentences = doc.sentences().json();
        let currentPos = 0;
        return sentences.map((s) => {
            const startChar = text.indexOf(s.text, currentPos);
            currentPos = startChar + s.text.length;
            return {
                text: s.text,
                startChar,
                endChar: currentPos,
            };
        });
    }
    createSlidingWindows(text, chapterIndex, microSegments) {
        const windows = [];
        const [minTokens, maxTokens] = this.config.mesoTokenRange;
        const overlap = this.config.mesoOverlap;
        let windowStart = 0;
        let windowIndex = 0;
        while (windowStart < microSegments.length) {
            let windowEnd = windowStart;
            let tokenCount = 0;
            // Expand window until we hit max tokens
            while (windowEnd < microSegments.length && tokenCount < maxTokens) {
                tokenCount += microSegments[windowEnd].tokens;
                windowEnd++;
            }
            // Ensure minimum size
            if (tokenCount < minTokens && windowEnd < microSegments.length) {
                windowEnd = microSegments.length;
            }
            const windowMicros = microSegments.slice(windowStart, windowEnd);
            const windowText = windowMicros.map((m) => m.text).join(' ');
            windows.push({
                id: `meso_${chapterIndex}_${windowIndex}`,
                chapter: chapterIndex,
                startChar: windowMicros[0].startChar,
                endChar: windowMicros[windowMicros.length - 1].endChar,
                text: windowText,
                microIds: windowMicros.map((m) => m.id),
                tokens: tokenCount,
                sceneType: this.detectSceneType(windowText),
            });
            // Move window with overlap
            const advance = Math.max(1, Math.floor((windowEnd - windowStart) * (1 - overlap / maxTokens)));
            windowStart += advance;
            windowIndex++;
        }
        return windows;
    }
    createScenesFromBreaks(text, chapterIndex, microSegments, sceneBreaks) {
        const scenes = [];
        // Sort breaks and add end of text
        const sortedBreaks = [...new Set(sceneBreaks)].sort((a, b) => a - b);
        for (let i = 0; i < sortedBreaks.length; i++) {
            const start = sortedBreaks[i];
            const end = i < sortedBreaks.length - 1 ? sortedBreaks[i + 1] : text.length;
            // Find micro segments that fall within this range
            const sceneMicros = microSegments.filter((m) => m.startChar >= start && m.startChar < end);
            if (sceneMicros.length > 0) {
                const sceneText = sceneMicros.map((m) => m.text).join(' ');
                scenes.push({
                    id: `meso_${chapterIndex}_s${i}`,
                    chapter: chapterIndex,
                    startChar: sceneMicros[0].startChar,
                    endChar: sceneMicros[sceneMicros.length - 1].endChar,
                    text: sceneText,
                    microIds: sceneMicros.map((m) => m.id),
                    tokens: sceneMicros.reduce((sum, m) => sum + m.tokens, 0),
                    sceneType: this.detectSceneType(sceneText),
                });
            }
        }
        return scenes;
    }
    detectSceneType(text) {
        // Enhanced heuristics for scene type detection
        const dialogueMatch = text.match(/["'«»„""]/g) || [];
        const dialogueRatio = dialogueMatch.length / (text.length / 50); // Normalize by avg word length
        const actionWords = (text.match(/\b(ran|jumped|fought|grabbed|threw|suddenly|clashed|sprinted|gasped|shouted)\b/gi) || []).length;
        const actionDensity = actionWords / (text.length / 500);
        const sensoryWords = (text.match(/\b(smelled|tasted|saw|heard|felt|cold|warm|bright|dark|shadowy|scented)\b/gi) || []).length;
        if (dialogueRatio > 0.8)
            return 'dialogue';
        if (actionDensity > 1.5)
            return 'action';
        if (text.length < 300 && sensoryWords < 2)
            return 'transition';
        return 'description';
    }
    detectArcType(text) {
        // Use structural patterns to detect arc type
        const lower = text.toLowerCase();
        if (lower.includes('finally') || lower.includes('conclusion') || lower.includes('resolved'))
            return 'resolution';
        if (lower.includes('climax') || lower.includes('confrontation') || lower.includes('battle'))
            return 'climax';
        if (lower.includes('once upon a time') ||
            lower.includes('introduced') ||
            lower.includes('began'))
            return 'setup';
        return 'rising';
    }
}
// ============================================================================
// Fractal Retrieval Engine
// ============================================================================
export class FractalRetriever {
    constructor(config) {
        this.logger = getLogger('FractalRetriever');
        this.config = config;
        this.hms = new HolographicMemorySystem();
    }
    async initialize() {
        // HMS initializes itself in constructor
        this.logger.info('FractalRetriever initialized with HMS');
    }
    async memorizeBatch(items) {
        await this.hms.memorizeBatch(items);
    }
    async retrieve(query, k = 10, scaleWeights, graphDB) {
        const weights = { ...this.config.scaleWeights, ...scaleWeights };
        // Use HMS for native semantic query
        const hmsResults = await this.hms.queryText(query, k * 2);
        const results = [];
        for (const r of hmsResults) {
            const segmentId = r.id;
            const scale = segmentId.startsWith('micro_')
                ? 'micro'
                : segmentId.startsWith('meso_')
                    ? 'meso'
                    : 'macro';
            const weight = weights[scale] || 1.0;
            const segment = r.entry.metadata?.originalData ||
                (await this.loadSegment(segmentId, scale));
            const similarity = r.similarity;
            const graphBoost = graphDB ? await this.computeGraphBoost(segment, query, graphDB) : 0;
            const contextBoost = this.computeContextBoost(segment);
            const score = weight * similarity +
                this.config.graphBoostWeight * graphBoost +
                this.config.contextBoostWeight * contextBoost;
            results.push({
                scale: scale,
                segmentId,
                score,
                segment,
                graphBoost,
                contextBoost,
            });
        }
        // Sort by score and return top k
        return results.sort((a, b) => b.score - a.score).slice(0, k);
    }
    async embed(_text) {
        // Not needed with HMS as it handles embedding natively
        return new Float32Array(0);
    }
    getIndexForScale(_scale) {
        return null;
    }
    async computeGraphBoost(segment, query, graphDB) {
        // Identify nodes in this segment from the map
        const segmentId = String(segment.id);
        try {
            const rows = graphDB
                .prepare(`SELECT n.centrality, n.canonical_name FROM nodes n 
				 JOIN segment_node_map m ON n.node_id = m.node_id 
				 WHERE m.segment_id = ?`)
                .all(segmentId);
            if (!rows || rows.length === 0)
                return 0;
            // Calculate boost based on node centrality and query relevance
            let boost = 0;
            const queryLower = query.toLowerCase();
            for (const row of rows) {
                // Boost for central characters/themes
                boost += Number(row.centrality || 0) * 0.1;
                // Additional boost if query mentions this node
                if (queryLower.includes(String(row.canonical_name).toLowerCase())) {
                    boost += 0.5;
                }
            }
            return Math.min(boost, 1.0);
        }
        catch (err) {
            return 0;
        }
    }
    computeContextBoost(segment) {
        // Boost based on recency (higher index/timestamp = more recent)
        const now = Date.now();
        const timestamp = Number(segment.timestamp || now);
        const ageMs = now - timestamp;
        // Recency boost: 1.0 for new, decaying to 0.0 over 1 hour
        const hourMs = 3600000;
        const recencyBoost = Math.max(0, 1.0 - ageMs / hourMs);
        // Structural importance boost (e.g., climax scenes)
        let structuralBoost = 0;
        if (segment.sceneType === 'action' || segment.arcType === 'climax') {
            structuralBoost = 0.3;
        }
        return recencyBoost * 0.7 + structuralBoost * 0.3;
    }
    getSegmentIdFromLabel(label, scale) {
        // Map FAISS label to segment ID
        return `${scale}_${label}`;
    }
    async loadSegment(_segmentId, _scale) {
        // Load segment from database
        return {};
    }
}
// ============================================================================
// Narrative Graph Manager
// ============================================================================
export class NarrativeGraphManager {
    constructor(dbPath) {
        this.logger = getLogger('NarrativeGraphManager');
        this.db = new Database(dbPath);
        this.initializeSchema();
    }
    initializeSchema() {
        // Create tables for nodes, edges, and segment mappings
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS nodes (
        node_id TEXT PRIMARY KEY,
        node_type TEXT NOT NULL,
        canonical_name TEXT NOT NULL,
        attributes_json TEXT,
        frequency INTEGER DEFAULT 1,
        centrality REAL
      )
    `);
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS edges (
        edge_id TEXT PRIMARY KEY,
        from_node TEXT NOT NULL,
        to_node TEXT NOT NULL,
        edge_type TEXT NOT NULL,
        weight REAL DEFAULT 1.0,
        evidence_json TEXT,
        FOREIGN KEY (from_node) REFERENCES nodes(node_id),
        FOREIGN KEY (to_node) REFERENCES nodes(node_id)
      )
    `);
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS segment_node_map (
        segment_id TEXT NOT NULL,
        node_id TEXT NOT NULL,
        role TEXT,
        PRIMARY KEY (segment_id, node_id),
        FOREIGN KEY (node_id) REFERENCES nodes(node_id)
      )
    `);
        // Create indices for performance
        this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_node)`);
        this.db.exec(`CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_node)`);
        this.db.exec(`CREATE INDEX IF NOT EXISTS idx_segment_map ON segment_node_map(segment_id)`);
    }
    async updateGraphForSegment(segment) {
        // 1. Extract entities using coreference resolution
        const entities = await this.extractEntities(segment.text);
        // 2. Detect motifs
        const motifs = await this.detectMotifs(segment.text);
        // 3. Update nodes
        const nodeIds = [];
        for (const entity of entities) {
            const nodeId = await this.upsertNode(entity);
            nodeIds.push(nodeId);
            await this.linkSegmentToNode(segment.id, nodeId, entity.role);
        }
        for (const motif of motifs) {
            const nodeId = await this.upsertMotifNode(motif);
            nodeIds.push(nodeId);
            await this.linkSegmentToNode(segment.id, nodeId, 'motif');
        }
        // 4. Create co-occurrence edges
        for (let i = 0; i < nodeIds.length; i++) {
            for (let j = i + 1; j < nodeIds.length; j++) {
                await this.upsertEdge(nodeIds[i], nodeIds[j], 'cooccurrence', {
                    segment: segment.id,
                });
            }
        }
        // 5. Update centrality metrics
        await this.updateCentralityMetrics();
    }
    async extractEntities(text) {
        // Implementation using NLP for entity extraction
        const doc = nlp(text);
        const people = doc.people().out('array');
        const places = doc.places().out('array');
        const entities = [];
        for (const name of people) {
            entities.push({ name, type: 'character', attributes: { role: 'unknown' } });
        }
        for (const name of places) {
            entities.push({ name, type: 'setting', attributes: { importance: 'unknown' } });
        }
        return entities;
    }
    async detectMotifs(text) {
        // Look for recurring symbols and themes
        const motifs = [];
        const themes = ['light', 'dark', 'water', 'fire', 'cold', 'warmth', 'silence', 'noise'];
        const lower = text.toLowerCase();
        for (const theme of themes) {
            if (lower.includes(theme)) {
                motifs.push({ label: theme, type: 'thematic' });
            }
        }
        return motifs;
    }
    async upsertNode(entity) {
        const nodeId = this.generateNodeId(entity);
        const canonical = this.canonicalize(String(entity.name || ''));
        this.db
            .prepare(`INSERT INTO nodes (node_id, node_type, canonical_name, attributes_json)
         VALUES (?, ?, ?, ?)
         ON CONFLICT(node_id) DO UPDATE SET
         frequency = frequency + 1`)
            .run(nodeId, entity.type, canonical, JSON.stringify(entity.attributes));
        return nodeId;
    }
    async upsertMotifNode(motif) {
        const nodeId = `motif_${motif.label}`;
        this.db
            .prepare(`INSERT INTO nodes (node_id, node_type, canonical_name, attributes_json)
         VALUES (?, 'motif', ?, ?)
         ON CONFLICT(node_id) DO UPDATE SET
         frequency = frequency + 1`)
            .run(nodeId, motif.label, JSON.stringify(motif));
        return nodeId;
    }
    async upsertEdge(fromNode, toNode, edgeType, evidence) {
        const edgeId = `${fromNode}_${toNode}_${edgeType}`;
        this.db
            .prepare(`INSERT INTO edges (edge_id, from_node, to_node, edge_type, evidence_json)
         VALUES (?, ?, ?, ?, ?)
         ON CONFLICT(edge_id) DO UPDATE SET
         weight = weight + 1`)
            .run(edgeId, fromNode, toNode, edgeType, JSON.stringify(evidence));
    }
    async linkSegmentToNode(segmentId, nodeId, role) {
        this.db
            .prepare(`INSERT OR IGNORE INTO segment_node_map (segment_id, node_id, role)
         VALUES (?, ?, ?)`)
            .run(segmentId, nodeId, role);
    }
    generateNodeId(entity) {
        const entityType = String(entity.type || 'unknown');
        const entityName = String(entity.name || 'unnamed');
        return `${entityType}_${entityName.toLowerCase().replace(/\s+/g, '_')}`;
    }
    canonicalize(name) {
        const aliases = {
            tom: 'Thomas',
            tommy: 'Thomas',
        };
        const lower = name.toLowerCase();
        return aliases[lower] || name;
    }
    async updateCentralityMetrics() {
        const query = `
      UPDATE nodes
      SET centrality = (
        SELECT COUNT(*) FROM edges
        WHERE edges.from_node = nodes.node_id
        OR edges.to_node = nodes.node_id
      )
    `;
        this.db.prepare(query).run();
    }
    async checkContinuity(character) {
        const query = `
      SELECT n1.canonical_name, n1.attributes_json as state1,
             n2.attributes_json as state2, e.evidence_json
      FROM nodes n1
      JOIN edges e ON n1.node_id = e.from_node
      JOIN nodes n2 ON n2.node_id = e.to_node
      WHERE n1.canonical_name = ?
      AND n1.node_type = 'character'
      AND e.edge_type = 'temporal'
    `;
        const rows = this.db.prepare(query).all(character);
        return this.detectViolations(rows);
    }
    detectViolations(rows) {
        const violations = [];
        for (const row of rows) {
            const state1 = JSON.parse(row.state1);
            const state2 = JSON.parse(row.state2);
            // Compare physical and fundamental traits
            const trackingTraits = ['eye_color', 'hair_color', 'age', 'role', 'dead'];
            for (const trait of trackingTraits) {
                if (state1[trait] && state2[trait] && state1[trait] !== state2[trait]) {
                    // Age only violates if it decreases (unless it's a fantasy setting, but we assume linear time)
                    if (trait === 'age') {
                        if (Number(state2[trait]) < Number(state1[trait])) {
                            violations.push({
                                id: `violation_${row.canonical_name}_age`,
                                type: 'continuity_error',
                                entity: row.canonical_name,
                                issue: `Age decreased from ${state1[trait]} to ${state2[trait]}`,
                                evidence: row.evidence_json,
                            });
                        }
                    }
                    else {
                        violations.push({
                            id: `violation_${row.canonical_name}_${trait}`,
                            type: 'continuity_error',
                            entity: row.canonical_name,
                            issue: `Contradictory ${trait}: was ${state1[trait]}, now ${state2[trait]}`,
                            evidence: row.evidence_json,
                        });
                    }
                }
            }
        }
        return violations;
    }
}
// ============================================================================
// Motif Clustering Engine
// ============================================================================
export class MotifClusteringEngine {
    // private clusterer: unknown; // HDBSCAN
    // private minClusterSize: number;
    constructor(_minClusterSize = 5) {
        this.logger = getLogger('MotifClusteringEngine');
        // this.minClusterSize = minClusterSize;
    }
    async clusterMotifs(embeddings) {
        const labels = await this.runClustering(embeddings);
        const clusters = new Map();
        labels.forEach((label, idx) => {
            if (label !== -1) {
                if (!clusters.has(label)) {
                    clusters.set(label, []);
                }
                clusters.get(label).push(idx);
            }
        });
        const motifClusters = [];
        for (const [clusterId, indices] of clusters) {
            const clusterEmbeddings = indices.map((i) => embeddings[i]);
            const centroid = this.computeCentroid(clusterEmbeddings);
            const keywords = await this.extractKeywords(indices);
            const coherence = this.computeCoherence(clusterEmbeddings, centroid);
            motifClusters.push({
                clusterId,
                keywords,
                segments: indices.map((i) => `segment_${i}`),
                centroid,
                coherenceScore: coherence,
            });
        }
        return motifClusters;
    }
    async runClustering(embeddings) {
        if (embeddings.length === 0)
            return [];
        // Basic Leader-based clustering (simple but effective for streaming motifs)
        const clusters = [];
        const threshold = 0.85;
        const labels = [];
        for (const emb of embeddings) {
            let foundCluster = -1;
            for (let i = 0; i < clusters.length; i++) {
                const similarity = this.computeCosineSimilarity(emb, clusters[i]);
                if (similarity > threshold) {
                    foundCluster = i;
                    // Update centroid (running average)
                    for (let j = 0; j < emb.length; j++) {
                        clusters[i][j] = (clusters[i][j] + emb[j]) / 2;
                    }
                    break;
                }
            }
            if (foundCluster === -1) {
                labels.push(clusters.length);
                clusters.push(new Float32Array(emb));
            }
            else {
                labels.push(foundCluster);
            }
        }
        return labels;
    }
    computeCosineSimilarity(a, b) {
        let dot = 0;
        let normA = 0;
        let normB = 0;
        for (let i = 0; i < a.length; i++) {
            dot += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dot / (Math.sqrt(normA) * Math.sqrt(normB));
    }
    computeCentroid(embeddings) {
        const dim = embeddings[0].length;
        const centroid = new Float32Array(dim);
        for (const emb of embeddings) {
            for (let i = 0; i < dim; i++) {
                centroid[i] += emb[i] / embeddings.length;
            }
        }
        return centroid;
    }
    async extractKeywords(_indices) {
        return ['frost', 'cold', 'breath'];
    }
    computeCoherence(embeddings, centroid) {
        let totalDistance = 0;
        for (const emb of embeddings) {
            let distance = 0;
            for (let i = 0; i < centroid.length; i++) {
                distance += Math.pow(emb[i] - centroid[i], 2);
            }
            totalDistance += Math.sqrt(distance);
        }
        return 1 / (1 + totalDistance / embeddings.length);
    }
    async labelClusters(clusters, humanLabels) {
        for (const cluster of clusters) {
            if (humanLabels?.has(cluster.clusterId)) {
                cluster.label = humanLabels.get(cluster.clusterId);
            }
            else {
                cluster.label = this.generateLabel(cluster.keywords);
            }
        }
    }
    generateLabel(keywords) {
        if (keywords.includes('frost') || keywords.includes('cold')) {
            return 'cold-presence';
        }
        if (keywords.includes('purple') || keywords.includes('stain')) {
            return 'purple-motif';
        }
        return keywords.slice(0, 3).join('-');
    }
}
// ============================================================================
// Main Fractal Narrative Memory System
// ============================================================================
export class FractalNarrativeMemory extends EventEmitter {
    constructor(config) {
        super();
        this.logger = getLogger('FractalNarrativeMemory');
        this.config = {
            microTokenRange: [5, 40],
            mesoTokenRange: [100, 1200],
            mesoOverlap: 50,
            scaleWeights: {
                micro: 1.0,
                meso: 0.6,
                macro: 0.3,
            },
            graphBoostWeight: 0.2,
            contextBoostWeight: 0.1,
            minClusterSize: 5,
            embeddingModel: 'sentence-transformers/all-MiniLM-L6-v2',
            ...config,
        };
        this.segmenter = new FractalSegmenter(this.config);
        this.retriever = new FractalRetriever(this.config);
        this.graphManager = new NarrativeGraphManager('./narrative_graph.db');
        this.motifEngine = new MotifClusteringEngine(this.config.minClusterSize);
        this.cache = new Map();
    }
    async initialize() {
        this.logger.info('Initializing Fractal Narrative Memory');
        await this.retriever.initialize();
        await this.initializeDatabase();
        this.logger.info('Fractal Narrative Memory initialized');
        this.emit('initialized');
    }
    async initializeDatabase() {
        this.db = new Database('./fractal_memory.db');
        this.db.pragma('journal_mode = WAL');
        this.db.exec(`CREATE TABLE IF NOT EXISTS segments_micro (
        id TEXT PRIMARY KEY,
        chapter INTEGER,
        para_index INTEGER,
        sent_index INTEGER,
        beat_index INTEGER,
        text TEXT,
        start_char INTEGER,
        end_char INTEGER,
        embedding_id TEXT,
        tokens INTEGER
      )`);
        this.db.exec(`CREATE TABLE IF NOT EXISTS segments_meso (
        id TEXT PRIMARY KEY,
        chapter INTEGER,
        start_char INTEGER,
        end_char INTEGER,
        text TEXT,
        micro_ids TEXT,
        embedding_id TEXT,
        scene_type TEXT,
        tokens INTEGER
      )`);
        this.db.exec(`CREATE TABLE IF NOT EXISTS segments_macro (
        id TEXT PRIMARY KEY,
        chapter_or_arc TEXT,
        start_char INTEGER,
        end_char INTEGER,
        text TEXT,
        meso_ids TEXT,
        embedding_id TEXT,
        arc_type TEXT
      )`);
    }
    async ingestDocument(document) {
        this.logger.info(`Ingesting document: ${document.id}`);
        const segments = await this.segmenter.segment(document.content || '', parseInt(String(document.metadata?.chapter || '1')));
        await this.indexSegments(segments.micro, 'micro');
        await this.indexSegments(segments.meso, 'meso');
        await this.indexSegments(segments.macro, 'macro');
        for (const meso of segments.meso) {
            await this.graphManager.updateGraphForSegment(meso);
        }
        if (Math.random() < 0.1) {
            await this.clusterMotifs();
        }
        this.emit('documentIngested', document.id);
    }
    async indexSegments(segments, scale) {
        for (const segment of segments) {
            await this.storeSegment(segment, scale);
        }
        const batchItems = segments.map((s) => ({ id: s.id, text: s.text }));
        if (batchItems.length > 0) {
            await this.retriever.memorizeBatch(batchItems);
        }
    }
    async storeSegment(segment, scale) {
        if (scale === 'micro') {
            this.db
                .prepare(`INSERT OR REPLACE INTO segments_micro (id, chapter, para_index, sent_index, beat_index, text, start_char, end_char, tokens)
				VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
                .run(segment.id, segment.chapter, segment.paraIndex, segment.sentIndex, segment.beatIndex || null, segment.text, segment.startChar, segment.endChar, segment.tokens);
        }
        else if (scale === 'meso') {
            this.db
                .prepare(`INSERT OR REPLACE INTO segments_meso (id, chapter, start_char, end_char, text, micro_ids, scene_type, tokens)
				VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
                .run(segment.id, segment.chapter, segment.startChar, segment.endChar, segment.text, JSON.stringify(segment.microIds), segment.sceneType, segment.tokens);
        }
        else if (scale === 'macro') {
            this.db
                .prepare(`INSERT OR REPLACE INTO segments_macro (id, chapter_or_arc, start_char, end_char, text, meso_ids, arc_type)
				VALUES (?, ?, ?, ?, ?, ?, ?)`)
                .run(segment.id, segment.chapterOrArc, segment.startChar, segment.endChar, segment.text, JSON.stringify(segment.mesoIds), segment.arcType);
        }
    }
    async query(queryText, options) {
        let scaleWeights = options?.scaleWeights || this.config.scaleWeights;
        if (options?.policy) {
            scaleWeights = this.applyPolicy(options.policy);
        }
        const cacheKey = `${queryText}_${JSON.stringify(scaleWeights)}_${options?.k || 10}`;
        if (this.cache.has(cacheKey)) {
            return this.cache.get(cacheKey);
        }
        const results = await this.retriever.retrieve(queryText, options?.k || 10, scaleWeights, this.graphManager.db);
        this.cache.set(cacheKey, results);
        if (this.cache.size > 100) {
            const firstKey = this.cache.keys().next().value;
            if (firstKey !== undefined) {
                this.cache.delete(firstKey);
            }
        }
        return results;
    }
    applyPolicy(policy) {
        switch (policy) {
            case 'line-fix':
                return { micro: 0.9, meso: 0.1, macro: 0.0 };
            case 'scene-fix':
                return { micro: 0.2, meso: 0.7, macro: 0.1 };
            case 'thematic':
                return { micro: 0.1, meso: 0.3, macro: 0.8 };
            default:
                return this.config.scaleWeights;
        }
    }
    async checkContinuity(character) {
        return this.graphManager.checkContinuity(character);
    }
    async findMotif(motifName) {
        const query = `Find all instances of the ${motifName} motif`;
        return this.query(query, { policy: 'thematic' });
    }
    async expandBeat(_segmentId, _context) {
        return 'Expanded beat text...';
    }
    async clusterMotifs() {
        this.logger.info('Running motif clustering...');
    }
    async getStats() {
        return {
            microSegments: await this.getSegmentCount('micro'),
            mesoSegments: await this.getSegmentCount('meso'),
            macroSegments: await this.getSegmentCount('macro'),
            graphNodes: await this.getNodeCount(),
            graphEdges: await this.getEdgeCount(),
            motifClusters: await this.getMotifCount(),
        };
    }
    async getSegmentCount(scale) {
        const row = this.db.prepare(`SELECT COUNT(*) as count FROM segments_${scale}`).get();
        return Number(row?.count || 0);
    }
    async getNodeCount() {
        const row = this.graphManager.db.prepare(`SELECT COUNT(*) as count FROM nodes`).get();
        return Number(row?.count || 0);
    }
    async getEdgeCount() {
        const row = this.graphManager.db.prepare(`SELECT COUNT(*) as count FROM edges`).get();
        return Number(row?.count || 0);
    }
    async getMotifCount() {
        const row = this.graphManager.db
            .prepare(`SELECT COUNT(*) as count FROM nodes WHERE node_type = 'motif'`)
            .get();
        return Number(row?.count || 0);
    }
}
//# sourceMappingURL=fractal-narrative-memory.js.map