/**
 * Enhanced Memory Persistence System
 *
 * Features:
 * - Long-term memory with semantic search
 * - Project context memory
 * - User preferences learning
 * - Conversation summaries
 * - Memory categories and tags
 * - Memory decay and importance scoring
 *
 * Enables Grok to remember context across sessions.
 */
import { EventEmitter } from 'events';
import fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
import * as crypto from 'crypto';
import { getMemoryRepository } from '../database/repositories/memory-repository.js';
import { getEmbeddingProvider } from '../embeddings/embedding-provider.js';
import { logger } from '../utils/logger.js';
const DEFAULT_CONFIG = {
    enabled: true,
    maxMemories: 10000,
    maxMemoryAge: 365,
    decayRate: 0.01,
    minImportance: 0.1,
    autoSummarize: true,
    summarizeThreshold: 20,
    embeddingEnabled: true, // Enable local embeddings with @xenova/transformers
    useSQLite: true, // SQLite by default
};
/**
 * Enhanced Memory Manager
 */
export class EnhancedMemory extends EventEmitter {
    config;
    dataDir;
    memories = new Map();
    projects = new Map();
    summaries = [];
    userProfile = null;
    currentProjectId = null;
    decayIntervalId = null;
    dbRepository = null;
    embeddingProvider = null;
    constructor(config = {}) {
        super();
        this.config = { ...DEFAULT_CONFIG, ...config };
        this.dataDir = path.join(os.homedir(), '.codebuddy', 'memory');
        // Initialize SQLite repository if enabled
        if (this.config.useSQLite) {
            try {
                this.dbRepository = getMemoryRepository();
            }
            catch {
                // Fallback to JSON if SQLite fails
                this.config.useSQLite = false;
            }
        }
        // Initialize embedding provider if embeddings are enabled
        if (this.config.embeddingEnabled) {
            try {
                this.embeddingProvider = getEmbeddingProvider({
                    provider: 'local', // Use @xenova/transformers by default
                    modelName: this.config.embeddingModel || 'Xenova/all-MiniLM-L6-v2',
                });
            }
            catch {
                // Disable embeddings if provider fails
                logger.warn('Failed to initialize embedding provider, disabling embeddings');
                this.config.embeddingEnabled = false;
            }
        }
        this.initialize();
    }
    /**
     * Initialize memory system
     */
    async initialize() {
        await fs.ensureDir(this.dataDir);
        await fs.ensureDir(path.join(this.dataDir, 'projects'));
        await fs.ensureDir(path.join(this.dataDir, 'memories'));
        await this.loadMemories();
        await this.loadProjects();
        await this.loadUserProfile();
        await this.loadSummaries();
        // Start decay timer
        this.decayIntervalId = setInterval(() => this.applyDecay(), 3600000); // Every hour
    }
    /**
     * Load memories from disk or SQLite
     */
    async loadMemories() {
        // Load from SQLite if enabled
        if (this.dbRepository) {
            try {
                const dbMemories = this.dbRepository.find({ limit: this.config.maxMemories });
                for (const dbMem of dbMemories) {
                    const entry = {
                        id: dbMem.id,
                        type: dbMem.type,
                        content: dbMem.content,
                        summary: dbMem.metadata?.summary,
                        embedding: dbMem.embedding ? Array.from(dbMem.embedding) : undefined,
                        importance: dbMem.importance,
                        accessCount: dbMem.access_count,
                        createdAt: new Date(dbMem.created_at),
                        updatedAt: new Date(dbMem.created_at),
                        lastAccessedAt: new Date(dbMem.last_accessed),
                        expiresAt: dbMem.expires_at ? new Date(dbMem.expires_at) : undefined,
                        tags: dbMem.metadata?.tags || [],
                        metadata: dbMem.metadata || {},
                        projectId: dbMem.project_id,
                    };
                    this.memories.set(entry.id, entry);
                }
                return;
            }
            catch {
                // Fallback to JSON
            }
        }
        // Fallback: Load from JSON
        const indexPath = path.join(this.dataDir, 'memory-index.json');
        if (await fs.pathExists(indexPath)) {
            try {
                const entries = await fs.readJSON(indexPath);
                for (const entry of entries) {
                    this.memories.set(entry.id, entry);
                }
            }
            catch {
                // Start fresh
            }
        }
    }
    /**
     * Load projects from disk
     */
    async loadProjects() {
        const projectsDir = path.join(this.dataDir, 'projects');
        // Check if directory exists before reading
        if (!await fs.pathExists(projectsDir)) {
            return;
        }
        const files = await fs.readdir(projectsDir);
        // Load project files in parallel for better performance
        const jsonFiles = files.filter(file => file.endsWith('.json'));
        const loadResults = await Promise.allSettled(jsonFiles.map(async (file) => {
            const project = await fs.readJSON(path.join(projectsDir, file));
            return project;
        }));
        for (const result of loadResults) {
            if (result.status === 'fulfilled') {
                this.projects.set(result.value.projectId, result.value);
            }
            // Skip rejected promises (invalid files)
        }
    }
    /**
     * Load user profile
     */
    async loadUserProfile() {
        const profilePath = path.join(this.dataDir, 'user-profile.json');
        if (await fs.pathExists(profilePath)) {
            try {
                this.userProfile = await fs.readJSON(profilePath);
            }
            catch {
                this.userProfile = null;
            }
        }
    }
    /**
     * Load conversation summaries
     */
    async loadSummaries() {
        const summariesPath = path.join(this.dataDir, 'summaries.json');
        if (await fs.pathExists(summariesPath)) {
            try {
                this.summaries = await fs.readJSON(summariesPath);
            }
            catch {
                this.summaries = [];
            }
        }
    }
    /**
     * Save all data
     */
    async saveAll() {
        // Save all data files in parallel for better performance
        const saveOperations = [
            // Save memory index
            fs.writeJSON(path.join(this.dataDir, 'memory-index.json'), Array.from(this.memories.values()), { spaces: 2 }),
            // Save summaries
            fs.writeJSON(path.join(this.dataDir, 'summaries.json'), this.summaries, { spaces: 2 }),
        ];
        // Save user profile if exists
        if (this.userProfile) {
            saveOperations.push(fs.writeJSON(path.join(this.dataDir, 'user-profile.json'), this.userProfile, { spaces: 2 }));
        }
        await Promise.all(saveOperations);
    }
    /**
     * Store a memory
     */
    async store(options) {
        const id = crypto.randomBytes(8).toString('hex');
        const now = new Date();
        const memory = {
            id,
            type: options.type,
            content: options.content,
            summary: options.summary,
            importance: options.importance || this.calculateImportance(options),
            accessCount: 0,
            createdAt: now,
            updatedAt: now,
            lastAccessedAt: now,
            expiresAt: options.expiresIn
                ? new Date(now.getTime() + options.expiresIn * 24 * 60 * 60 * 1000)
                : undefined,
            tags: options.tags || [],
            metadata: options.metadata || {},
            projectId: options.projectId || this.currentProjectId || undefined,
            sessionId: options.sessionId,
        };
        // Generate embedding if enabled
        if (this.config.embeddingEnabled) {
            memory.embedding = await this.generateEmbedding(options.content);
        }
        // Store in SQLite if enabled
        if (this.dbRepository) {
            const dbMemory = {
                id,
                type: options.type,
                scope: options.projectId ? 'project' : 'user',
                project_id: options.projectId || this.currentProjectId || undefined,
                content: options.content,
                embedding: memory.embedding ? new Float32Array(memory.embedding) : undefined,
                importance: memory.importance,
                expires_at: memory.expiresAt?.toISOString(),
                metadata: { ...options.metadata, tags: options.tags, summary: options.summary },
            };
            this.dbRepository.create(dbMemory);
        }
        this.memories.set(id, memory);
        // Add to project if applicable
        if (memory.projectId) {
            const project = this.projects.get(memory.projectId);
            if (project) {
                project.memories.push(id);
                await this.saveProject(project);
            }
        }
        if (!this.dbRepository) {
            await this.saveAll();
        }
        await this.enforceMemoryLimits();
        this.emit('memory:stored', { memory });
        return memory;
    }
    /**
     * Calculate importance score
     */
    calculateImportance(options) {
        let score = 0.5;
        // Type-based importance
        const typeScores = {
            decision: 0.9,
            instruction: 0.85,
            preference: 0.8,
            pattern: 0.75,
            error: 0.7,
            fact: 0.6,
            definition: 0.55,
            context: 0.5,
            summary: 0.45,
        };
        score = typeScores[options.type] || 0.5;
        // Adjust based on content length (medium length is often more useful)
        const contentLength = options.content.length;
        if (contentLength > 50 && contentLength < 500) {
            score += 0.1;
        }
        // Adjust based on tags
        if (options.tags && options.tags.length > 0) {
            score += Math.min(options.tags.length * 0.05, 0.15);
        }
        return Math.min(1, Math.max(0, score));
    }
    /**
     * Generate embedding using @xenova/transformers (local) or fallback to hash
     */
    async generateEmbedding(text) {
        // Use real embedding provider if available
        if (this.embeddingProvider) {
            try {
                const result = await this.embeddingProvider.embed(text);
                // Convert Float32Array to number[]
                return Array.from(result.embedding);
            }
            catch (error) {
                logger.warn(`Embedding generation failed, using fallback: ${error instanceof Error ? error.message : String(error)}`);
                // Fall through to hash-based fallback
            }
        }
        // Fallback: hash-based pseudo-embedding (for when @xenova/transformers is not available)
        const hash = crypto.createHash('sha256').update(text).digest();
        const embedding = [];
        for (let i = 0; i < 384; i++) { // 384 dimensions to match all-MiniLM-L6-v2
            embedding.push((hash[i % hash.length] / 255) * 2 - 1);
        }
        // Normalize
        const norm = Math.sqrt(embedding.reduce((sum, val) => sum + val * val, 0));
        return embedding.map(val => val / norm);
    }
    /**
     * Recall memories
     */
    async recall(options = {}) {
        let results = Array.from(this.memories.values());
        // Filter expired
        if (!options.includeExpired) {
            const now = new Date();
            results = results.filter(m => !m.expiresAt || new Date(m.expiresAt) > now);
        }
        // Filter by type
        if (options.types && options.types.length > 0) {
            results = results.filter(m => options.types.includes(m.type));
        }
        // Filter by tags
        if (options.tags && options.tags.length > 0) {
            results = results.filter(m => options.tags.some(tag => m.tags.includes(tag)));
        }
        // Filter by project
        if (options.projectId) {
            results = results.filter(m => m.projectId === options.projectId);
        }
        // Filter by importance
        if (options.minImportance !== undefined) {
            results = results.filter(m => m.importance >= options.minImportance);
        }
        // Text search
        if (options.query) {
            const query = options.query.toLowerCase();
            if (this.config.embeddingEnabled) {
                // Semantic search
                const queryEmbedding = await this.generateEmbedding(options.query);
                results = results
                    .map(m => ({
                    memory: m,
                    similarity: m.embedding
                        ? this.cosineSimilarity(queryEmbedding, m.embedding)
                        : 0,
                }))
                    .filter(r => r.similarity > 0.5)
                    .sort((a, b) => b.similarity - a.similarity)
                    .map(r => r.memory);
            }
            else {
                // Keyword search
                results = results.filter(m => m.content.toLowerCase().includes(query) ||
                    m.summary?.toLowerCase().includes(query) ||
                    m.tags.some(t => t.toLowerCase().includes(query)));
            }
        }
        // Sort by importance and recency
        results.sort((a, b) => {
            const importanceWeight = 0.6;
            const recencyWeight = 0.4;
            const aScore = a.importance * importanceWeight +
                (new Date(a.lastAccessedAt).getTime() / Date.now()) * recencyWeight;
            const bScore = b.importance * importanceWeight +
                (new Date(b.lastAccessedAt).getTime() / Date.now()) * recencyWeight;
            return bScore - aScore;
        });
        // Limit results
        if (options.limit) {
            results = results.slice(0, options.limit);
        }
        // Update access stats
        for (const memory of results) {
            memory.accessCount++;
            memory.lastAccessedAt = new Date();
        }
        await this.saveAll();
        return results;
    }
    /**
     * Cosine similarity
     */
    cosineSimilarity(a, b) {
        if (a.length !== b.length)
            return 0;
        let dotProduct = 0;
        let normA = 0;
        let normB = 0;
        for (let i = 0; i < a.length; i++) {
            dotProduct += a[i] * b[i];
            normA += a[i] * a[i];
            normB += b[i] * b[i];
        }
        return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
    }
    /**
     * Forget a memory
     */
    async forget(id) {
        const memory = this.memories.get(id);
        if (!memory)
            return false;
        // Remove from SQLite if enabled
        if (this.dbRepository) {
            this.dbRepository.delete(id);
        }
        // Remove from project
        if (memory.projectId) {
            const project = this.projects.get(memory.projectId);
            if (project) {
                project.memories = project.memories.filter(m => m !== id);
                await this.saveProject(project);
            }
        }
        this.memories.delete(id);
        if (!this.dbRepository) {
            await this.saveAll();
        }
        this.emit('memory:forgotten', { id });
        return true;
    }
    /**
     * Apply memory decay
     */
    async applyDecay() {
        const now = new Date();
        for (const [id, memory] of this.memories) {
            // Calculate days since last access
            const daysSinceAccess = (now.getTime() - new Date(memory.lastAccessedAt).getTime()) /
                (1000 * 60 * 60 * 24);
            // Apply decay
            const decay = this.config.decayRate * daysSinceAccess;
            memory.importance = Math.max(this.config.minImportance, memory.importance - decay);
            // Remove if below threshold
            if (memory.importance <= this.config.minImportance && memory.accessCount < 3) {
                await this.forget(id);
            }
        }
        await this.saveAll();
    }
    /**
     * Enforce memory limits
     */
    async enforceMemoryLimits() {
        if (this.memories.size <= this.config.maxMemories)
            return;
        // Sort by importance (lowest first)
        const sorted = Array.from(this.memories.values())
            .sort((a, b) => a.importance - b.importance);
        // Remove excess memories
        const toRemove = sorted.slice(0, this.memories.size - this.config.maxMemories);
        for (const memory of toRemove) {
            await this.forget(memory.id);
        }
    }
    /**
     * Store project context
     */
    async setProjectContext(projectPath) {
        const projectId = crypto
            .createHash('sha256')
            .update(projectPath)
            .digest('hex')
            .slice(0, 16);
        let project = this.projects.get(projectId);
        if (!project) {
            project = {
                projectId,
                projectPath,
                name: path.basename(projectPath),
                languages: [],
                frameworks: [],
                conventions: [],
                memories: [],
                createdAt: new Date(),
                updatedAt: new Date(),
            };
            this.projects.set(projectId, project);
        }
        this.currentProjectId = projectId;
        await this.saveProject(project);
        this.emit('project:set', { project });
        return project;
    }
    /**
     * Save project
     */
    async saveProject(project) {
        const projectPath = path.join(this.dataDir, 'projects', `${project.projectId}.json`);
        await fs.writeJSON(projectPath, project, { spaces: 2 });
    }
    /**
     * Learn a code convention
     */
    async learnConvention(options) {
        if (!this.currentProjectId)
            return;
        const project = this.projects.get(this.currentProjectId);
        if (!project)
            return;
        // Check if convention already exists
        const existing = project.conventions.find(c => c.type === options.type && c.rule === options.rule);
        if (existing) {
            existing.confidence = Math.min(1, existing.confidence + 0.1);
            if (options.examples) {
                existing.examples = [...new Set([...(existing.examples || []), ...options.examples])];
            }
        }
        else {
            project.conventions.push({
                type: options.type,
                rule: options.rule,
                examples: options.examples,
                confidence: options.confidence || 0.5,
            });
        }
        project.updatedAt = new Date();
        await this.saveProject(project);
        this.emit('convention:learned', { convention: options });
    }
    /**
     * Store conversation summary
     */
    async storeSummary(options) {
        const summary = {
            id: crypto.randomBytes(8).toString('hex'),
            sessionId: options.sessionId,
            summary: options.summary,
            topics: options.topics,
            decisions: options.decisions || [],
            todos: options.todos || [],
            timestamp: new Date(),
            messageCount: options.messageCount,
        };
        this.summaries.push(summary);
        // Keep only last 100 summaries
        if (this.summaries.length > 100) {
            this.summaries = this.summaries.slice(-100);
        }
        await this.saveAll();
        // Also store as memory
        await this.store({
            type: 'summary',
            content: options.summary,
            tags: options.topics,
            metadata: {
                decisions: options.decisions,
                todos: options.todos,
            },
            sessionId: options.sessionId,
        });
        this.emit('summary:stored', { summary });
        return summary;
    }
    /**
     * Update user profile
     */
    async updateUserProfile(updates) {
        if (!this.userProfile) {
            this.userProfile = {
                id: crypto.randomBytes(8).toString('hex'),
                preferences: {
                    codeStyle: 'standard',
                    verbosity: 'moderate',
                },
                skills: [],
                interests: [],
                history: {
                    topLanguages: [],
                    commonTasks: [],
                    lastProjects: [],
                },
                createdAt: new Date(),
                updatedAt: new Date(),
            };
        }
        this.userProfile = {
            ...this.userProfile,
            ...updates,
            updatedAt: new Date(),
        };
        await this.saveAll();
        this.emit('profile:updated', { profile: this.userProfile });
        return this.userProfile;
    }
    /**
     * Get user profile
     */
    getUserProfile() {
        return this.userProfile;
    }
    /**
     * Get project memory
     */
    getProjectMemory(projectId) {
        const id = projectId || this.currentProjectId;
        return id ? this.projects.get(id) || null : null;
    }
    /**
     * Build context from memories
     */
    async buildContext(options = {}) {
        const parts = [];
        // Add user preferences
        if (options.includePreferences && this.userProfile) {
            parts.push(`User preferences:\n${JSON.stringify(this.userProfile.preferences, null, 2)}`);
        }
        // Add project context
        if (options.includeProject && this.currentProjectId) {
            const project = this.projects.get(this.currentProjectId);
            if (project) {
                parts.push(`\nProject: ${project.name}`);
                if (project.languages.length > 0) {
                    parts.push(`Languages: ${project.languages.join(', ')}`);
                }
                if (project.conventions.length > 0) {
                    parts.push('Conventions:');
                    for (const conv of project.conventions.slice(0, 5)) {
                        parts.push(`- ${conv.rule}`);
                    }
                }
            }
        }
        // Add recent summaries
        if (options.includeRecentSummaries) {
            const recentSummaries = this.summaries.slice(-3);
            if (recentSummaries.length > 0) {
                parts.push('\nRecent conversation context:');
                for (const summary of recentSummaries) {
                    parts.push(`- ${summary.summary}`);
                }
            }
        }
        // Add relevant memories
        const memories = await this.recall({
            query: options.query,
            limit: 10,
            minImportance: 0.5,
        });
        if (memories.length > 0) {
            parts.push('\nRelevant memories:');
            for (const memory of memories) {
                parts.push(`- [${memory.type}] ${memory.summary || memory.content.slice(0, 100)}`);
            }
        }
        return parts.join('\n');
    }
    /**
     * Get stats
     */
    getStats() {
        const byType = {};
        for (const memory of this.memories.values()) {
            byType[memory.type] = (byType[memory.type] || 0) + 1;
        }
        return {
            totalMemories: this.memories.size,
            byType: byType,
            projects: this.projects.size,
            summaries: this.summaries.length,
        };
    }
    /**
     * Format status
     */
    formatStatus() {
        const stats = this.getStats();
        const lines = [
            '╔══════════════════════════════════════════════════════════════╗',
            '║                    🧠 MEMORY SYSTEM                          ║',
            '╠══════════════════════════════════════════════════════════════╣',
            `║ Total Memories: ${stats.totalMemories.toString().padEnd(43)}║`,
            `║ Projects:       ${stats.projects.toString().padEnd(43)}║`,
            `║ Summaries:      ${stats.summaries.toString().padEnd(43)}║`,
            '╠══════════════════════════════════════════════════════════════╣',
            '║ MEMORIES BY TYPE                                             ║',
        ];
        for (const [type, count] of Object.entries(stats.byType)) {
            lines.push(`║   ${type.padEnd(15)} ${count.toString().padEnd(40)}║`);
        }
        const project = this.getProjectMemory();
        if (project) {
            lines.push('╠══════════════════════════════════════════════════════════════╣');
            lines.push(`║ Current Project: ${project.name.slice(0, 40).padEnd(40)}║`);
            lines.push(`║ Conventions:     ${project.conventions.length.toString().padEnd(40)}║`);
        }
        lines.push('╠══════════════════════════════════════════════════════════════╣');
        lines.push('║ /memory store | /memory recall | /memory forget              ║');
        lines.push('╚══════════════════════════════════════════════════════════════╝');
        return lines.join('\n');
    }
    /**
     * Clear all memories
     */
    async clear() {
        this.memories.clear();
        this.summaries = [];
        await this.saveAll();
        this.emit('memory:cleared');
    }
    /**
     * Dispose
     */
    dispose() {
        // Clear decay interval timer
        if (this.decayIntervalId) {
            clearInterval(this.decayIntervalId);
            this.decayIntervalId = null;
        }
        this.saveAll();
        this.memories.clear();
        this.projects.clear();
        this.summaries = [];
        this.removeAllListeners();
    }
}
// Singleton
let memoryInstance = null;
export function getEnhancedMemory(config) {
    if (!memoryInstance) {
        memoryInstance = new EnhancedMemory(config);
    }
    return memoryInstance;
}
export function resetEnhancedMemory() {
    if (memoryInstance) {
        memoryInstance.dispose();
    }
    memoryInstance = null;
}
//# sourceMappingURL=enhanced-memory.js.map