/**
 * Fractal Memory Service
 * Integrates Python fractal memory system with TypeScript MCP handlers
 * Uses high-performance better-sqlite3 for local storage
 */
import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import Database from 'better-sqlite3';
import { getLogger } from '../../core/logger.js';
const logger = getLogger('fractal-memory');
export class FractalMemoryService {
    constructor(dbPath) {
        this.db = null;
        this.pythonProcess = null;
        this.initialized = false;
        this.dbPath = dbPath || path.join(process.cwd(), 'narrative_memory.db');
        this.pythonScriptPath = path.join(process.cwd(), 'fractal_memory_advanced.py');
    }
    /**
     * Initialize the fractal memory service
     */
    async initialize() {
        if (this.initialized) {
            return;
        }
        try {
            await this.initializeDatabase();
            // Verify Python script exists
            if (!fs.existsSync(this.pythonScriptPath)) {
                logger.warn('Python fractal memory script not found, some features will be limited');
            }
            this.initialized = true;
            logger.info('Fractal memory service initialized');
        }
        catch (error) {
            logger.error('Failed to initialize fractal memory service', { error });
            throw error;
        }
    }
    /**
     * Initialize SQLite database
     */
    async initializeDatabase() {
        try {
            this.db = new Database(this.dbPath);
            const schemaPath = path.join(process.cwd(), 'src/services/memory/schema.sql');
            if (fs.existsSync(schemaPath)) {
                const schema = await fs.promises.readFile(schemaPath, 'utf-8');
                this.db.exec(schema);
                logger.info('Database schema initialized');
            }
            else {
                logger.warn('Schema file not found, database may need manual setup');
            }
        }
        catch (error) {
            logger.error('Failed to initialize database', { error });
            throw error;
        }
    }
    /**
     * Ingest text into fractal memory system
     */
    async ingestText(text, chapterId, options) {
        try {
            const result = await this.callPythonScript('ingest', {
                text,
                chapterId,
                ...options,
            });
            if (result.error) {
                throw new Error(`Ingestion failed: ${result.error}`);
            }
            logger.info('Text ingested successfully', {
                chapterId,
                segments: result.segmentCount,
                entities: result.entityCount,
                motifs: result.motifCount,
            });
        }
        catch (error) {
            logger.error('Failed to ingest text', { error });
            throw error;
        }
    }
    /**
     * Search using fractal retrieval
     */
    async search(query, options) {
        try {
            const result = await this.callPythonScript('search', {
                query,
                ...options,
            });
            if (result.error) {
                return this.searchFallback(query, options);
            }
            return result.results;
        }
        catch (error) {
            logger.error('Search failed', { error });
            return this.searchFallback(query, options);
        }
    }
    /**
     * Fallback search implementation in TypeScript
     */
    async searchFallback(query, options) {
        if (!this.db) {
            throw new Error('Database not initialized');
        }
        const policy = options?.policy || 'scene-fix';
        const limit = options?.k || 10;
        const sql = `
			SELECT 
				s.id, s.scale, s.text, s.chapter_id as chapterId,
				s.start_pos as startPos, s.end_pos as endPos,
				s.parent_id as parentId, s.sequence_num as sequenceNum
			FROM segments s
			WHERE s.text LIKE ?
			${options?.chapterId ? 'AND s.chapter_id = ?' : ''}
			ORDER BY 
				CASE s.scale 
					WHEN 'micro' THEN ${policy === 'line-fix' ? 1 : 3}
					WHEN 'meso' THEN 2
					WHEN 'macro' THEN ${policy === 'thematic' ? 1 : 3}
				END,
				s.sequence_num
			LIMIT ?
		`;
        const params = [`%${query}%`];
        if (options?.chapterId) {
            params.push(options.chapterId);
        }
        params.push(limit.toString());
        const rows = this.db.prepare(sql).all(...params);
        return rows.map((row) => ({
            segments: [row],
            score: 1.0,
            metadata: { policy, fallback: true },
        }));
    }
    /**
     * Find co-occurrences of entities/motifs
     */
    async findCoOccurrences(items, options) {
        try {
            const result = await this.callPythonScript('cooccurrences', {
                items,
                ...options,
            });
            if (result.error) {
                throw new Error(`Co-occurrence search failed: ${result.error}`);
            }
            return result.cooccurrences;
        }
        catch (error) {
            logger.error('Failed to find co-occurrences', { error });
            throw error;
        }
    }
    /**
     * Check character continuity
     */
    async checkContinuity(characterName, options) {
        if (!this.db) {
            throw new Error('Database not initialized');
        }
        const sql = `
			SELECT 
				character_name,
				chapter_id,
				scale,
				appearance_count,
				first_appearance_seq,
				last_appearance_seq,
				segment_ids
			FROM character_continuity
			WHERE character_name = ?
			${options?.chapterId ? 'AND chapter_id = ?' : ''}
			ORDER BY chapter_id, scale
		`;
        const params = [characterName];
        if (options?.chapterId) {
            params.push(options.chapterId);
        }
        const rows = this.db.prepare(sql).all(...params);
        return {
            character: characterName,
            continuity: rows,
            gaps: this.identifyContinuityGaps(rows),
        };
    }
    identifyContinuityGaps(appearances) {
        const gaps = [];
        for (let i = 1; i < appearances.length; i++) {
            const prev = appearances[i - 1];
            const curr = appearances[i];
            if (curr.first_appearance_seq - prev.last_appearance_seq > 10) {
                gaps.push({
                    from: prev,
                    to: curr,
                    gapSize: curr.first_appearance_seq - prev.last_appearance_seq,
                });
            }
        }
        return gaps;
    }
    /**
     * Track motif patterns
     */
    async trackMotifs(options) {
        if (!this.db) {
            throw new Error('Database not initialized');
        }
        let sql = `
			SELECT 
				motif_name,
				pattern_type,
				chapter_id,
				occurrence_count,
				avg_strength,
				segment_ids
			FROM motif_tracking
			WHERE 1=1
		`;
        const params = [];
        if (options?.chapterId) {
            sql += ' AND chapter_id = ?';
            params.push(options.chapterId);
        }
        if (options?.minStrength) {
            sql += ' AND avg_strength >= ?';
            params.push(options.minStrength);
        }
        if (options?.patternType) {
            sql += ' AND pattern_type = ?';
            params.push(options.patternType);
        }
        sql += ' ORDER BY avg_strength DESC, occurrence_count DESC';
        return this.db.prepare(sql).all(...params);
    }
    /**
     * Update retrieval policy
     */
    async updatePolicy(name, policy) {
        if (!this.db) {
            throw new Error('Database not initialized');
        }
        const sql = `
			UPDATE memory_policies
			SET 
				scale_weights = ?,
				entity_boost = ?,
				motif_boost = ?,
				recency_weight = ?,
				frequency_weight = ?,
				updated_at = CURRENT_TIMESTAMP
			WHERE name = ?
		`;
        this.db
            .prepare(sql)
            .run(JSON.stringify(policy.scaleWeights), policy.entityBoost || 1.0, policy.motifBoost || 1.0, policy.recencyWeight || 0.1, policy.frequencyWeight || 0.1, name);
    }
    /**
     * Call Python script for advanced operations
     */
    async callPythonScript(operation, params) {
        return new Promise((resolve) => {
            if (!fs.existsSync(this.pythonScriptPath)) {
                resolve({ error: 'Python script not available' });
                return;
            }
            const args = [
                this.pythonScriptPath,
                '--operation',
                operation,
                '--params',
                JSON.stringify(params),
                '--db',
                this.dbPath,
            ];
            const python = spawn('python3', args);
            let output = '';
            let error = '';
            python.stdout.on('data', (data) => {
                output += data.toString();
            });
            python.stderr.on('data', (data) => {
                error += data.toString();
            });
            python.on('close', (code) => {
                if (code !== 0) {
                    logger.error('Python script failed', { code, error });
                    resolve({ error: error || 'Python script failed' });
                }
                else {
                    try {
                        const result = JSON.parse(output);
                        resolve(result);
                    }
                    catch (e) {
                        logger.error('Failed to parse Python output', { output, error: e });
                        resolve({ error: 'Invalid Python output' });
                    }
                }
            });
            python.on('error', (err) => {
                logger.error('Failed to spawn Python process', { error: err });
                resolve({ error: 'Failed to spawn Python process' });
            });
            setTimeout(() => {
                python.kill();
                resolve({ error: 'Python script timeout' });
            }, 30000);
        });
    }
    /**
     * Get analytics and performance metrics
     */
    async getAnalytics(options) {
        if (!this.db) {
            throw new Error('Database not initialized');
        }
        const sql = `
			SELECT 
				COUNT(*) as total_queries,
				AVG(latency_ms) as avg_latency,
				AVG(results_count) as avg_results,
				AVG(relevance_score) as avg_relevance,
				policy,
				COUNT(CASE WHEN user_feedback = 'positive' THEN 1 END) as positive_feedback,
				COUNT(CASE WHEN user_feedback = 'negative' THEN 1 END) as negative_feedback
			FROM retrieval_analytics
			WHERE created_at BETWEEN ? AND ?
			GROUP BY policy
			ORDER BY total_queries DESC
			LIMIT ?
		`;
        const params = [
            (options?.startDate || new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)).toISOString(),
            (options?.endDate || new Date()).toISOString(),
            options?.limit || 10,
        ];
        const rows = this.db.prepare(sql).all(...params);
        return {
            metrics: rows,
            summary: this.summarizeAnalytics(rows),
        };
    }
    /**
     * Summarize analytics
     */
    summarizeAnalytics(metrics) {
        if (metrics.length === 0)
            return { totalQueries: 0 };
        const totalQueries = metrics.reduce((sum, m) => sum + Number(m.total_queries || 0), 0);
        const avgLatency = metrics.reduce((sum, m) => sum + Number(m.avg_latency || 0) * Number(m.total_queries || 0), 0) / totalQueries;
        const avgRelevance = metrics.reduce((sum, m) => sum + Number(m.avg_relevance || 0) * Number(m.total_queries || 0), 0) / totalQueries;
        return {
            totalQueries,
            avgLatency,
            avgRelevance,
            mostUsedPolicy: metrics[0]?.policy,
            satisfactionRate: metrics.reduce((sum, m) => sum +
                Number(m.positive_feedback || 0) /
                    (Number(m.positive_feedback || 0) + Number(m.negative_feedback || 0) ||
                        1), 0) / metrics.length,
        };
    }
    /**
     * Clean up resources
     */
    async cleanup() {
        if (this.pythonProcess) {
            this.pythonProcess.kill();
            this.pythonProcess = null;
        }
        if (this.db) {
            this.db.close();
            this.db = null;
        }
        this.initialized = false;
    }
}
//# sourceMappingURL=fractal-memory-service.js.map