/**
 * CLI History Store - SQLite Storage Backend
 * Provides persistent storage for CLI execution history with efficient queries
 */
import Database from 'better-sqlite3';
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
import { join } from 'path';
import { parseSessionFile, formatConversation, extractConversationPairs } from './session-content-parser.js';
import { StoragePaths, ensureStorageDir, getProjectId } from '../config/storage-paths.js';
/**
 * CLI History Store using SQLite
 */
export class CliHistoryStore {
    db;
    dbPath;
    projectPath;
    constructor(baseDir) {
        this.projectPath = baseDir;
        // Use centralized storage path
        const paths = StoragePaths.project(baseDir);
        const historyDir = paths.cliHistory;
        ensureStorageDir(historyDir);
        this.dbPath = paths.historyDb;
        this.db = new Database(this.dbPath);
        this.db.pragma('journal_mode = WAL');
        this.db.pragma('synchronous = NORMAL');
        this.initSchema();
        this.migrateFromJson(historyDir);
    }
    /**
     * Initialize database schema
     */
    initSchema() {
        this.db.exec(`
      -- Conversations table (conversation metadata)
      CREATE TABLE IF NOT EXISTS conversations (
        id TEXT PRIMARY KEY,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        tool TEXT NOT NULL,
        model TEXT DEFAULT 'default',
        mode TEXT DEFAULT 'analysis',
        category TEXT DEFAULT 'user',
        total_duration_ms INTEGER DEFAULT 0,
        turn_count INTEGER DEFAULT 0,
        latest_status TEXT DEFAULT 'success',
        prompt_preview TEXT,
        parent_execution_id TEXT,
        FOREIGN KEY (parent_execution_id) REFERENCES conversations(id) ON DELETE SET NULL
      );

      -- Turns table (individual conversation turns)
      CREATE TABLE IF NOT EXISTS turns (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        conversation_id TEXT NOT NULL,
        turn_number INTEGER NOT NULL,
        timestamp TEXT NOT NULL,
        prompt TEXT NOT NULL,
        duration_ms INTEGER DEFAULT 0,
        status TEXT DEFAULT 'success',
        exit_code INTEGER,
        stdout TEXT,
        stderr TEXT,
        truncated INTEGER DEFAULT 0,
        FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
        UNIQUE(conversation_id, turn_number)
      );

      -- Indexes for efficient queries
      CREATE INDEX IF NOT EXISTS idx_conversations_tool ON conversations(tool);
      CREATE INDEX IF NOT EXISTS idx_conversations_status ON conversations(latest_status);
      CREATE INDEX IF NOT EXISTS idx_conversations_category ON conversations(category);
      CREATE INDEX IF NOT EXISTS idx_conversations_updated ON conversations(updated_at DESC);
      CREATE INDEX IF NOT EXISTS idx_conversations_created ON conversations(created_at DESC);
      CREATE INDEX IF NOT EXISTS idx_turns_conversation ON turns(conversation_id);

      -- Full-text search for prompts
      CREATE VIRTUAL TABLE IF NOT EXISTS turns_fts USING fts5(
        prompt,
        stdout,
        content='turns',
        content_rowid='id'
      );

      -- Triggers to keep FTS index updated
      CREATE TRIGGER IF NOT EXISTS turns_ai AFTER INSERT ON turns BEGIN
        INSERT INTO turns_fts(rowid, prompt, stdout) VALUES (new.id, new.prompt, new.stdout);
      END;

      CREATE TRIGGER IF NOT EXISTS turns_ad AFTER DELETE ON turns BEGIN
        INSERT INTO turns_fts(turns_fts, rowid, prompt, stdout) VALUES('delete', old.id, old.prompt, old.stdout);
      END;

      CREATE TRIGGER IF NOT EXISTS turns_au AFTER UPDATE ON turns BEGIN
        INSERT INTO turns_fts(turns_fts, rowid, prompt, stdout) VALUES('delete', old.id, old.prompt, old.stdout);
        INSERT INTO turns_fts(rowid, prompt, stdout) VALUES (new.id, new.prompt, new.stdout);
      END;

      -- Native session mapping table (CCW ID <-> Native Session ID)
      CREATE TABLE IF NOT EXISTS native_session_mapping (
        ccw_id TEXT PRIMARY KEY,
        tool TEXT NOT NULL,
        native_session_id TEXT NOT NULL,
        native_session_path TEXT,
        project_hash TEXT,
        created_at TEXT DEFAULT CURRENT_TIMESTAMP,
        UNIQUE(tool, native_session_id)
      );

      -- Indexes for native session lookups
      CREATE INDEX IF NOT EXISTS idx_native_tool_session ON native_session_mapping(tool, native_session_id);
      CREATE INDEX IF NOT EXISTS idx_native_session_id ON native_session_mapping(native_session_id);

      -- Insights analysis history table
      CREATE TABLE IF NOT EXISTS insights (
        id TEXT PRIMARY KEY,
        created_at TEXT NOT NULL,
        tool TEXT NOT NULL,
        prompt_count INTEGER DEFAULT 0,
        patterns TEXT,
        suggestions TEXT,
        raw_output TEXT,
        execution_id TEXT,
        lang TEXT DEFAULT 'en'
      );

      CREATE INDEX IF NOT EXISTS idx_insights_created ON insights(created_at DESC);
      CREATE INDEX IF NOT EXISTS idx_insights_tool ON insights(tool);

      -- Reviews table for CLI execution reviews
      CREATE TABLE IF NOT EXISTS reviews (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        execution_id TEXT NOT NULL UNIQUE,
        status TEXT NOT NULL DEFAULT 'pending',
        rating INTEGER,
        comments TEXT,
        reviewer TEXT,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        FOREIGN KEY (execution_id) REFERENCES conversations(id) ON DELETE CASCADE
      );

      CREATE INDEX IF NOT EXISTS idx_reviews_execution ON reviews(execution_id);
      CREATE INDEX IF NOT EXISTS idx_reviews_status ON reviews(status);
      CREATE INDEX IF NOT EXISTS idx_reviews_created ON reviews(created_at DESC);
    `);
        // Migration: Add category column if not exists (for existing databases)
        this.migrateSchema();
    }
    /**
     * Migrate schema for existing databases
     */
    migrateSchema() {
        try {
            // Check if columns exist
            const tableInfo = this.db.prepare('PRAGMA table_info(conversations)').all();
            const hasCategory = tableInfo.some(col => col.name === 'category');
            const hasParentExecutionId = tableInfo.some(col => col.name === 'parent_execution_id');
            const hasProjectRoot = tableInfo.some(col => col.name === 'project_root');
            const hasRelativePath = tableInfo.some(col => col.name === 'relative_path');
            if (!hasCategory) {
                console.log('[CLI History] Migrating database: adding category column...');
                this.db.exec(`
          ALTER TABLE conversations ADD COLUMN category TEXT DEFAULT 'user';
        `);
                // Create index separately to handle potential errors
                try {
                    this.db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_category ON conversations(category);`);
                }
                catch (indexErr) {
                    console.warn('[CLI History] Category index creation warning:', indexErr.message);
                }
                console.log('[CLI History] Migration complete: category column added');
            }
            if (!hasParentExecutionId) {
                console.log('[CLI History] Migrating database: adding parent_execution_id column...');
                this.db.exec(`
          ALTER TABLE conversations ADD COLUMN parent_execution_id TEXT;
        `);
                try {
                    this.db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_parent ON conversations(parent_execution_id);`);
                }
                catch (indexErr) {
                    console.warn('[CLI History] Parent execution index creation warning:', indexErr.message);
                }
                console.log('[CLI History] Migration complete: parent_execution_id column added');
            }
            // Add hierarchical storage support columns
            if (!hasProjectRoot) {
                console.log('[CLI History] Migrating database: adding project_root column for hierarchical storage...');
                this.db.exec(`
          ALTER TABLE conversations ADD COLUMN project_root TEXT;
        `);
                try {
                    this.db.exec(`CREATE INDEX IF NOT EXISTS idx_conversations_project_root ON conversations(project_root);`);
                }
                catch (indexErr) {
                    console.warn('[CLI History] Project root index creation warning:', indexErr.message);
                }
                console.log('[CLI History] Migration complete: project_root column added');
            }
            if (!hasRelativePath) {
                console.log('[CLI History] Migrating database: adding relative_path column for hierarchical storage...');
                this.db.exec(`
          ALTER TABLE conversations ADD COLUMN relative_path TEXT;
        `);
                console.log('[CLI History] Migration complete: relative_path column added');
            }
            // Add missing timestamp index for turns table (for time-based queries)
            try {
                const indexExists = this.db.prepare(`
          SELECT name FROM sqlite_master
          WHERE type='index' AND name='idx_turns_timestamp'
        `).get();
                if (!indexExists) {
                    console.log('[CLI History] Adding missing timestamp index to turns table...');
                    this.db.exec(`CREATE INDEX IF NOT EXISTS idx_turns_timestamp ON turns(timestamp DESC);`);
                    console.log('[CLI History] Migration complete: turns timestamp index added');
                }
            }
            catch (indexErr) {
                console.warn('[CLI History] Turns timestamp index creation warning:', indexErr.message);
            }
            // Add cached output columns to turns table for non-streaming mode
            const turnsInfo = this.db.prepare('PRAGMA table_info(turns)').all();
            const hasCached = turnsInfo.some(col => col.name === 'cached');
            const hasStdoutFull = turnsInfo.some(col => col.name === 'stdout_full');
            const hasStderrFull = turnsInfo.some(col => col.name === 'stderr_full');
            if (!hasCached) {
                console.log('[CLI History] Migrating database: adding cached column to turns table...');
                this.db.exec('ALTER TABLE turns ADD COLUMN cached INTEGER DEFAULT 0;');
                console.log('[CLI History] Migration complete: cached column added');
            }
            if (!hasStdoutFull) {
                console.log('[CLI History] Migrating database: adding stdout_full column to turns table...');
                this.db.exec('ALTER TABLE turns ADD COLUMN stdout_full TEXT;');
                console.log('[CLI History] Migration complete: stdout_full column added');
            }
            if (!hasStderrFull) {
                console.log('[CLI History] Migrating database: adding stderr_full column to turns table...');
                this.db.exec('ALTER TABLE turns ADD COLUMN stderr_full TEXT;');
                console.log('[CLI History] Migration complete: stderr_full column added');
            }
        }
        catch (err) {
            console.error('[CLI History] Migration error:', err.message);
            // Don't throw - allow the store to continue working with existing schema
        }
    }
    /**
     * Migrate existing JSON files to SQLite
     */
    migrateFromJson(historyDir) {
        const migrationMarker = join(historyDir, '.migrated');
        if (existsSync(migrationMarker)) {
            return; // Already migrated
        }
        // Find all date directories
        const dateDirs = readdirSync(historyDir).filter(d => {
            const dirPath = join(historyDir, d);
            return statSync(dirPath).isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(d);
        });
        let migratedCount = 0;
        for (const dateDir of dateDirs) {
            const dirPath = join(historyDir, dateDir);
            const files = readdirSync(dirPath).filter(f => f.endsWith('.json'));
            for (const file of files) {
                try {
                    const filePath = join(dirPath, file);
                    const data = JSON.parse(readFileSync(filePath, 'utf8'));
                    // Convert to conversation record if legacy format
                    const conversation = this.normalizeRecord(data);
                    this.saveConversation(conversation);
                    migratedCount++;
                    // Optionally delete the JSON file after migration
                    // unlinkSync(filePath);
                }
                catch (err) {
                    console.error(`Failed to migrate ${file}:`, err.message);
                }
            }
        }
        // Create migration marker
        if (migratedCount > 0) {
            require('fs').writeFileSync(migrationMarker, new Date().toISOString());
            console.log(`[CLI History] Migrated ${migratedCount} records to SQLite`);
        }
    }
    /**
     * Normalize legacy record to ConversationRecord format
     */
    normalizeRecord(data) {
        if (data.turns && Array.isArray(data.turns)) {
            return data;
        }
        // Legacy single execution format
        return {
            id: data.id,
            created_at: data.timestamp,
            updated_at: data.timestamp,
            tool: data.tool,
            model: data.model || 'default',
            mode: data.mode || 'analysis',
            category: data.category || 'user',
            total_duration_ms: data.duration_ms || 0,
            turn_count: 1,
            latest_status: data.status || 'success',
            turns: [{
                    turn: 1,
                    timestamp: data.timestamp,
                    prompt: data.prompt,
                    duration_ms: data.duration_ms || 0,
                    status: data.status || 'success',
                    exit_code: data.exit_code,
                    output: data.output || { stdout: '', stderr: '', truncated: false }
                }]
        };
    }
    /**
     * Save or update a conversation
     */
    saveConversation(conversation) {
        const promptPreview = conversation.turns.length > 0
            ? conversation.turns[conversation.turns.length - 1].prompt.substring(0, 100)
            : '';
        const upsertConversation = this.db.prepare(`
      INSERT INTO conversations (id, created_at, updated_at, tool, model, mode, category, total_duration_ms, turn_count, latest_status, prompt_preview, parent_execution_id, project_root, relative_path)
      VALUES (@id, @created_at, @updated_at, @tool, @model, @mode, @category, @total_duration_ms, @turn_count, @latest_status, @prompt_preview, @parent_execution_id, @project_root, @relative_path)
      ON CONFLICT(id) DO UPDATE SET
        updated_at = @updated_at,
        total_duration_ms = @total_duration_ms,
        turn_count = @turn_count,
        latest_status = @latest_status,
        prompt_preview = @prompt_preview,
        project_root = @project_root,
        relative_path = @relative_path
    `);
        const upsertTurn = this.db.prepare(`
      INSERT INTO turns (conversation_id, turn_number, timestamp, prompt, duration_ms, status, exit_code, stdout, stderr, truncated, cached, stdout_full, stderr_full)
      VALUES (@conversation_id, @turn_number, @timestamp, @prompt, @duration_ms, @status, @exit_code, @stdout, @stderr, @truncated, @cached, @stdout_full, @stderr_full)
      ON CONFLICT(conversation_id, turn_number) DO UPDATE SET
        timestamp = @timestamp,
        prompt = @prompt,
        duration_ms = @duration_ms,
        status = @status,
        exit_code = @exit_code,
        stdout = @stdout,
        stderr = @stderr,
        truncated = @truncated,
        cached = @cached,
        stdout_full = @stdout_full,
        stderr_full = @stderr_full
    `);
        const transaction = this.db.transaction(() => {
            upsertConversation.run({
                id: conversation.id,
                created_at: conversation.created_at,
                updated_at: conversation.updated_at,
                tool: conversation.tool,
                model: conversation.model,
                mode: conversation.mode,
                category: conversation.category || 'user',
                total_duration_ms: conversation.total_duration_ms,
                turn_count: conversation.turn_count,
                latest_status: conversation.latest_status,
                prompt_preview: promptPreview,
                parent_execution_id: conversation.parent_execution_id || null,
                project_root: this.projectPath,
                relative_path: null // For future hierarchical tracking
            });
            for (const turn of conversation.turns) {
                upsertTurn.run({
                    conversation_id: conversation.id,
                    turn_number: turn.turn,
                    timestamp: turn.timestamp,
                    prompt: turn.prompt,
                    duration_ms: turn.duration_ms,
                    status: turn.status,
                    exit_code: turn.exit_code,
                    stdout: turn.output.stdout,
                    stderr: turn.output.stderr,
                    truncated: turn.output.truncated ? 1 : 0,
                    cached: turn.output.cached ? 1 : 0,
                    stdout_full: turn.output.stdout_full || null,
                    stderr_full: turn.output.stderr_full || null
                });
            }
        });
        transaction();
    }
    /**
     * Get conversation by ID
     */
    getConversation(id) {
        const conv = this.db.prepare(`
      SELECT * FROM conversations WHERE id = ?
    `).get(id);
        if (!conv)
            return null;
        const turns = this.db.prepare(`
      SELECT * FROM turns WHERE conversation_id = ? ORDER BY turn_number ASC
    `).all(id);
        return {
            id: conv.id,
            created_at: conv.created_at,
            updated_at: conv.updated_at,
            tool: conv.tool,
            model: conv.model,
            mode: conv.mode,
            category: conv.category || 'user',
            total_duration_ms: conv.total_duration_ms,
            turn_count: conv.turn_count,
            latest_status: conv.latest_status,
            parent_execution_id: conv.parent_execution_id || undefined,
            turns: turns.map(t => ({
                turn: t.turn_number,
                timestamp: t.timestamp,
                prompt: t.prompt,
                duration_ms: t.duration_ms,
                status: t.status,
                exit_code: t.exit_code,
                output: {
                    stdout: t.stdout || '',
                    stderr: t.stderr || '',
                    truncated: !!t.truncated,
                    cached: !!t.cached,
                    stdout_full: t.stdout_full || undefined,
                    stderr_full: t.stderr_full || undefined
                }
            }))
        };
    }
    /**
     * Get conversation with native session info
     */
    getConversationWithNativeInfo(id) {
        const conv = this.getConversation(id);
        if (!conv)
            return null;
        const mapping = this.getNativeSessionMapping(id);
        return {
            ...conv,
            hasNativeSession: !!mapping,
            nativeSessionId: mapping?.native_session_id,
            nativeSessionPath: mapping?.native_session_path
        };
    }
    /**
     * Get paginated cached output for a conversation turn
     * @param conversationId - Conversation ID
     * @param turnNumber - Turn number (default: latest turn)
     * @param options - Pagination options
     */
    getCachedOutput(conversationId, turnNumber, options = {}) {
        const { offset = 0, limit = 10000, outputType = 'both' } = options;
        // Get turn (latest if not specified)
        let turn;
        if (turnNumber !== undefined) {
            turn = this.db.prepare(`
        SELECT * FROM turns WHERE conversation_id = ? AND turn_number = ?
      `).get(conversationId, turnNumber);
        }
        else {
            turn = this.db.prepare(`
        SELECT * FROM turns WHERE conversation_id = ? ORDER BY turn_number DESC LIMIT 1
      `).get(conversationId);
        }
        if (!turn)
            return null;
        const result = {
            conversationId,
            turnNumber: turn.turn_number,
            cached: !!turn.cached,
            prompt: turn.prompt,
            status: turn.status,
            timestamp: turn.timestamp
        };
        // Use full output if cached, otherwise use truncated
        if (outputType === 'stdout' || outputType === 'both') {
            const fullStdout = turn.cached ? (turn.stdout_full || '') : (turn.stdout || '');
            const totalBytes = fullStdout.length;
            const content = fullStdout.substring(offset, offset + limit);
            result.stdout = {
                content,
                totalBytes,
                offset,
                hasMore: offset + limit < totalBytes
            };
        }
        if (outputType === 'stderr' || outputType === 'both') {
            const fullStderr = turn.cached ? (turn.stderr_full || '') : (turn.stderr || '');
            const totalBytes = fullStderr.length;
            const content = fullStderr.substring(offset, offset + limit);
            result.stderr = {
                content,
                totalBytes,
                offset,
                hasMore: offset + limit < totalBytes
            };
        }
        return result;
    }
    /**
     * Query execution history
     */
    getHistory(options = {}) {
        const { limit = 50, offset = 0, tool, status, category, search, startDate, endDate } = options;
        let whereClause = '1=1';
        const params = {};
        if (tool) {
            whereClause += ' AND tool = @tool';
            params.tool = tool;
        }
        if (status) {
            whereClause += ' AND latest_status = @status';
            params.status = status;
        }
        if (category) {
            whereClause += ' AND category = @category';
            params.category = category;
        }
        if (startDate) {
            whereClause += ' AND created_at >= @startDate';
            params.startDate = startDate;
        }
        if (endDate) {
            whereClause += ' AND created_at <= @endDate';
            params.endDate = endDate;
        }
        // Full-text search
        let joinClause = '';
        if (search) {
            joinClause = `
        INNER JOIN (
          SELECT DISTINCT conversation_id FROM turns t
          INNER JOIN turns_fts ON turns_fts.rowid = t.id
          WHERE turns_fts MATCH @search
        ) AS matched ON c.id = matched.conversation_id
      `;
            params.search = search;
        }
        const countQuery = this.db.prepare(`
      SELECT COUNT(*) as count FROM conversations c ${joinClause} WHERE ${whereClause}
    `);
        const total = countQuery.get(params).count;
        const dataQuery = this.db.prepare(`
      SELECT c.* FROM conversations c ${joinClause}
      WHERE ${whereClause}
      ORDER BY c.updated_at DESC
      LIMIT @limit OFFSET @offset
    `);
        const rows = dataQuery.all({ ...params, limit, offset });
        return {
            total,
            count: rows.length,
            executions: rows.map(r => ({
                id: r.id,
                timestamp: r.created_at,
                updated_at: r.updated_at,
                tool: r.tool,
                status: r.latest_status,
                category: r.category || 'user',
                duration_ms: r.total_duration_ms,
                turn_count: r.turn_count,
                prompt_preview: r.prompt_preview || ''
            }))
        };
    }
    /**
     * Delete a conversation
     */
    deleteConversation(id) {
        try {
            const result = this.db.prepare('DELETE FROM conversations WHERE id = ?').run(id);
            return { success: result.changes > 0 };
        }
        catch (err) {
            return { success: false, error: err.message };
        }
    }
    /**
     * Batch delete conversations
     */
    batchDelete(ids) {
        const deleteStmt = this.db.prepare('DELETE FROM conversations WHERE id = ?');
        const errors = [];
        let deleted = 0;
        const transaction = this.db.transaction(() => {
            for (const id of ids) {
                try {
                    const result = deleteStmt.run(id);
                    if (result.changes > 0)
                        deleted++;
                }
                catch (err) {
                    errors.push(`${id}: ${err.message}`);
                }
            }
        });
        transaction();
        return {
            success: true,
            deleted,
            errors: errors.length > 0 ? errors : undefined
        };
    }
    /**
     * Delete conversations by tool
     */
    deleteByTool(tool) {
        const result = this.db.prepare('DELETE FROM conversations WHERE tool = ?').run(tool);
        return { success: true, deleted: result.changes };
    }
    /**
     * Delete all conversations
     */
    deleteAll() {
        const count = this.db.prepare('SELECT COUNT(*) as c FROM conversations').get().c;
        this.db.prepare('DELETE FROM conversations').run();
        return { success: true, deleted: count };
    }
    /**
     * Get statistics
     */
    getStats() {
        const total = this.db.prepare('SELECT COUNT(*) as c FROM conversations').get().c;
        const byToolRows = this.db.prepare(`
      SELECT tool, COUNT(*) as count FROM conversations GROUP BY tool
    `).all();
        const byTool = {};
        for (const row of byToolRows) {
            byTool[row.tool] = row.count;
        }
        const byStatusRows = this.db.prepare(`
      SELECT latest_status, COUNT(*) as count FROM conversations GROUP BY latest_status
    `).all();
        const byStatus = {};
        for (const row of byStatusRows) {
            byStatus[row.latest_status] = row.count;
        }
        const totalDuration = this.db.prepare(`
      SELECT COALESCE(SUM(total_duration_ms), 0) as total FROM conversations
    `).get().total;
        return { total, byTool, byStatus, totalDuration };
    }
    // ========== Native Session Mapping Methods ==========
    /**
     * Save or update native session mapping
     */
    saveNativeSessionMapping(mapping) {
        const stmt = this.db.prepare(`
      INSERT INTO native_session_mapping (ccw_id, tool, native_session_id, native_session_path, project_hash, created_at)
      VALUES (@ccw_id, @tool, @native_session_id, @native_session_path, @project_hash, @created_at)
      ON CONFLICT(ccw_id) DO UPDATE SET
        native_session_id = @native_session_id,
        native_session_path = @native_session_path,
        project_hash = @project_hash
    `);
        stmt.run({
            ccw_id: mapping.ccw_id,
            tool: mapping.tool,
            native_session_id: mapping.native_session_id,
            native_session_path: mapping.native_session_path || null,
            project_hash: mapping.project_hash || null,
            created_at: mapping.created_at || new Date().toISOString()
        });
    }
    /**
     * Get native session ID by CCW ID
     */
    getNativeSessionId(ccwId) {
        const row = this.db.prepare(`
      SELECT native_session_id FROM native_session_mapping WHERE ccw_id = ?
    `).get(ccwId);
        return row?.native_session_id || null;
    }
    /**
     * Get CCW ID by native session ID
     */
    getCcwIdByNativeSession(tool, nativeSessionId) {
        const row = this.db.prepare(`
      SELECT ccw_id FROM native_session_mapping WHERE tool = ? AND native_session_id = ?
    `).get(tool, nativeSessionId);
        return row?.ccw_id || null;
    }
    /**
     * Get full mapping by CCW ID
     */
    getNativeSessionMapping(ccwId) {
        const row = this.db.prepare(`
      SELECT * FROM native_session_mapping WHERE ccw_id = ?
    `).get(ccwId);
        if (!row)
            return null;
        return {
            ccw_id: row.ccw_id,
            tool: row.tool,
            native_session_id: row.native_session_id,
            native_session_path: row.native_session_path,
            project_hash: row.project_hash,
            created_at: row.created_at
        };
    }
    /**
     * Get latest native session mapping for a tool
     */
    getLatestNativeMapping(tool) {
        const row = this.db.prepare(`
      SELECT * FROM native_session_mapping 
      WHERE tool = ? 
      ORDER BY created_at DESC 
      LIMIT 1
    `).get(tool);
        if (!row)
            return null;
        return {
            ccw_id: row.ccw_id,
            tool: row.tool,
            native_session_id: row.native_session_id,
            native_session_path: row.native_session_path,
            project_hash: row.project_hash,
            created_at: row.created_at
        };
    }
    /**
     * Delete native session mapping
     */
    deleteNativeSessionMapping(ccwId) {
        const result = this.db.prepare('DELETE FROM native_session_mapping WHERE ccw_id = ?').run(ccwId);
        return result.changes > 0;
    }
    /**
     * Check if CCW ID has native session mapping
     */
    hasNativeSession(ccwId) {
        const row = this.db.prepare(`
      SELECT 1 FROM native_session_mapping WHERE ccw_id = ? LIMIT 1
    `).get(ccwId);
        return !!row;
    }
    // ========== Native Session Content Methods ==========
    /**
     * Get parsed native session content by CCW ID
     * Returns full conversation with all turns from native session file
     */
    getNativeSessionContent(ccwId) {
        const mapping = this.getNativeSessionMapping(ccwId);
        if (!mapping || !mapping.native_session_path) {
            return null;
        }
        return parseSessionFile(mapping.native_session_path, mapping.tool);
    }
    /**
     * Get formatted conversation text from native session
     */
    getFormattedNativeConversation(ccwId, options) {
        const session = this.getNativeSessionContent(ccwId);
        if (!session) {
            return null;
        }
        return formatConversation(session, options);
    }
    /**
     * Get conversation pairs (user prompt + assistant response) from native session
     */
    getNativeConversationPairs(ccwId) {
        const session = this.getNativeSessionContent(ccwId);
        if (!session) {
            return null;
        }
        return extractConversationPairs(session);
    }
    /**
     * Get conversation with enriched native session data
     * Merges CCW history with native session content
     */
    getEnrichedConversation(ccwId) {
        const ccwConv = this.getConversation(ccwId);
        const nativeSession = this.getNativeSessionContent(ccwId);
        if (!ccwConv && !nativeSession) {
            return null;
        }
        const merged = [];
        // Determine max turn count
        const maxTurns = Math.max(ccwConv?.turn_count || 0, nativeSession?.turns.filter(t => t.role === 'user').length || 0);
        for (let i = 1; i <= maxTurns; i++) {
            const ccwTurn = ccwConv?.turns.find(t => t.turn === i);
            const nativeUserTurn = nativeSession?.turns.find(t => t.turnNumber === i && t.role === 'user');
            const nativeAssistantTurn = nativeSession?.turns.find(t => t.turnNumber === i && t.role === 'assistant');
            merged.push({
                turn: i,
                timestamp: ccwTurn?.timestamp || nativeUserTurn?.timestamp || '',
                ccwPrompt: ccwTurn?.prompt,
                ccwOutput: ccwTurn?.output.stdout,
                nativeUserContent: nativeUserTurn?.content,
                nativeAssistantContent: nativeAssistantTurn?.content,
                nativeThoughts: nativeAssistantTurn?.thoughts,
                nativeToolCalls: nativeAssistantTurn?.toolCalls
            });
        }
        return { ccw: ccwConv, native: nativeSession, merged };
    }
    /**
     * List all conversations with native session info
     */
    getHistoryWithNativeInfo(options = {}) {
        const history = this.getHistory(options);
        const enrichedExecutions = history.executions.map(exec => {
            const mapping = this.getNativeSessionMapping(exec.id);
            return {
                ...exec,
                hasNativeSession: !!mapping,
                nativeSessionId: mapping?.native_session_id,
                nativeSessionPath: mapping?.native_session_path
            };
        });
        return {
            total: history.total,
            count: history.count,
            executions: enrichedExecutions
        };
    }
    // ========== Insights Methods ==========
    /**
     * Save an insights analysis result
     */
    saveInsight(insight) {
        const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO insights (id, created_at, tool, prompt_count, patterns, suggestions, raw_output, execution_id, lang)
      VALUES (@id, @created_at, @tool, @prompt_count, @patterns, @suggestions, @raw_output, @execution_id, @lang)
    `);
        stmt.run({
            id: insight.id,
            created_at: new Date().toISOString(),
            tool: insight.tool,
            prompt_count: insight.promptCount,
            patterns: JSON.stringify(insight.patterns || []),
            suggestions: JSON.stringify(insight.suggestions || []),
            raw_output: insight.rawOutput || null,
            execution_id: insight.executionId || null,
            lang: insight.lang || 'en'
        });
    }
    /**
     * Get insights history
     */
    getInsights(options = {}) {
        const { limit = 20, tool } = options;
        let sql = 'SELECT id, created_at, tool, prompt_count, patterns, suggestions, execution_id, lang FROM insights';
        const params = {};
        if (tool) {
            sql += ' WHERE tool = @tool';
            params.tool = tool;
        }
        sql += ' ORDER BY created_at DESC LIMIT @limit';
        params.limit = limit;
        const rows = this.db.prepare(sql).all(params);
        return rows.map(row => ({
            ...row,
            patterns: JSON.parse(row.patterns || '[]'),
            suggestions: JSON.parse(row.suggestions || '[]')
        }));
    }
    /**
     * Get a single insight by ID
     */
    getInsight(id) {
        const row = this.db.prepare('SELECT * FROM insights WHERE id = ?').get(id);
        if (!row)
            return null;
        return {
            ...row,
            patterns: JSON.parse(row.patterns || '[]'),
            suggestions: JSON.parse(row.suggestions || '[]')
        };
    }
    /**
     * Delete an insight
     */
    deleteInsight(id) {
        const result = this.db.prepare('DELETE FROM insights WHERE id = ?').run(id);
        return result.changes > 0;
    }
    /**
     * Save or update a review for an execution
     */
    saveReview(review) {
        const now = new Date().toISOString();
        const created_at = review.created_at || now;
        const updated_at = review.updated_at || now;
        const stmt = this.db.prepare(`
      INSERT INTO reviews (execution_id, status, rating, comments, reviewer, created_at, updated_at)
      VALUES (@execution_id, @status, @rating, @comments, @reviewer, @created_at, @updated_at)
      ON CONFLICT(execution_id) DO UPDATE SET
        status = @status,
        rating = @rating,
        comments = @comments,
        reviewer = @reviewer,
        updated_at = @updated_at
    `);
        const result = stmt.run({
            execution_id: review.execution_id,
            status: review.status,
            rating: review.rating ?? null,
            comments: review.comments ?? null,
            reviewer: review.reviewer ?? null,
            created_at,
            updated_at
        });
        return {
            id: result.lastInsertRowid,
            execution_id: review.execution_id,
            status: review.status,
            rating: review.rating,
            comments: review.comments,
            reviewer: review.reviewer,
            created_at,
            updated_at
        };
    }
    /**
     * Get review for an execution
     */
    getReview(executionId) {
        const row = this.db.prepare('SELECT * FROM reviews WHERE execution_id = ?').get(executionId);
        if (!row)
            return null;
        return {
            id: row.id,
            execution_id: row.execution_id,
            status: row.status,
            rating: row.rating,
            comments: row.comments,
            reviewer: row.reviewer,
            created_at: row.created_at,
            updated_at: row.updated_at
        };
    }
    /**
     * Get reviews with optional filtering
     */
    getReviews(options = {}) {
        const { status, limit = 50 } = options;
        let sql = 'SELECT * FROM reviews';
        const params = { limit };
        if (status) {
            sql += ' WHERE status = @status';
            params.status = status;
        }
        sql += ' ORDER BY updated_at DESC LIMIT @limit';
        const rows = this.db.prepare(sql).all(params);
        return rows.map(row => ({
            id: row.id,
            execution_id: row.execution_id,
            status: row.status,
            rating: row.rating,
            comments: row.comments,
            reviewer: row.reviewer,
            created_at: row.created_at,
            updated_at: row.updated_at
        }));
    }
    /**
     * Delete a review
     */
    deleteReview(executionId) {
        const result = this.db.prepare('DELETE FROM reviews WHERE execution_id = ?').run(executionId);
        return result.changes > 0;
    }
    /**
     * Close database connection
     */
    close() {
        this.db.close();
    }
}
// Singleton instance cache - keyed by normalized project ID for consistency
const storeCache = new Map();
/**
 * Get or create a store instance for a directory
 * Uses normalized project ID as cache key to handle path casing differences
 */
export function getHistoryStore(baseDir) {
    // Use getProjectId to normalize path for consistent cache key
    const cacheKey = getProjectId(baseDir);
    if (!storeCache.has(cacheKey)) {
        storeCache.set(cacheKey, new CliHistoryStore(baseDir));
    }
    return storeCache.get(cacheKey);
}
/**
 * Close all store instances
 */
export function closeAllStores() {
    for (const store of storeCache.values()) {
        store.close();
    }
    storeCache.clear();
}
//# sourceMappingURL=cli-history-store.js.map