"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContextRepository = void 0;
const BaseRepository_js_1 = require("./BaseRepository.js");
const timestamps_js_1 = require("../utils/timestamps.js");
const validation_js_1 = require("../utils/validation.js");
class ContextRepository extends BaseRepository_js_1.BaseRepository {
    // Constants
    static SQLITE_ESCAPE_CHAR = '\\';
    // Helper methods for DRY code
    buildSortClause(sort) {
        const sortMap = {
            created_desc: 'created_at DESC',
            created_at_desc: 'created_at DESC',
            created_asc: 'created_at ASC',
            created_at_asc: 'created_at ASC',
            updated_desc: 'updated_at DESC',
            updated_at_desc: 'updated_at DESC',
            updated_at_asc: 'updated_at ASC',
            key_asc: 'key ASC',
            key_desc: 'key DESC',
        };
        const defaultSort = sort?.includes('priority')
            ? 'priority DESC, created_at DESC'
            : 'created_at DESC';
        return sortMap[sort || ''] || defaultSort;
    }
    parseRelativeTime(relativeTime) {
        const now = new Date();
        const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
        if (relativeTime === 'today') {
            return today.toISOString();
        }
        else if (relativeTime === 'yesterday') {
            return new Date(today.getTime() - 24 * 60 * 60 * 1000).toISOString();
        }
        else if (relativeTime.match(/^(\d+) hours? ago$/)) {
            const hours = parseInt(relativeTime.match(/^(\d+)/)[1]);
            return new Date(now.getTime() - hours * 60 * 60 * 1000).toISOString();
        }
        else if (relativeTime.match(/^(\d+) days? ago$/)) {
            const days = parseInt(relativeTime.match(/^(\d+)/)[1]);
            return new Date(now.getTime() - days * 24 * 60 * 60 * 1000).toISOString();
        }
        else if (relativeTime === 'this week') {
            const startOfWeek = new Date(today);
            startOfWeek.setDate(today.getDate() - today.getDay());
            return startOfWeek.toISOString();
        }
        else if (relativeTime === 'last week') {
            const startOfLastWeek = new Date(today);
            startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
            return startOfLastWeek.toISOString();
        }
        return null;
    }
    convertToGlobPattern(pattern) {
        // SQLite GLOB supports character classes with brackets, so preserve them
        // Only convert regex-style patterns to GLOB
        return pattern
            .replace(/\./g, '?') // . -> single char
            .replace(/^\^/, '') // Remove start anchor
            .replace(/\$$/, ''); // Remove end anchor
        // Note: * and [...] are already GLOB syntax, so we keep them as-is
    }
    addPaginationToQuery(sql, params, limit, offset) {
        let modifiedSql = sql;
        if (limit) {
            modifiedSql += ' LIMIT ?';
            params.push(limit);
        }
        if (offset && offset > 0) {
            modifiedSql += ' OFFSET ?';
            params.push(offset);
        }
        return modifiedSql;
    }
    getTotalCount(baseSql, params) {
        const countSql = baseSql.replace('SELECT *', 'SELECT COUNT(*) as count');
        const countStmt = this.db.prepare(countSql);
        const countResult = countStmt.get(...params);
        return countResult.count || 0;
    }
    save(sessionId, input) {
        // Validate the key
        const validatedKey = (0, validation_js_1.validateKey)(input.key);
        const id = this.generateId();
        const size = this.calculateSize(input.value);
        // Determine channel - use explicit channel, or session default, or 'general'
        let channel = input.channel;
        if (!channel) {
            const sessionStmt = this.db.prepare('SELECT default_channel FROM sessions WHERE id = ?');
            const session = sessionStmt.get(sessionId);
            channel = session?.default_channel || 'general';
        }
        const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO context_items 
      (id, session_id, key, value, category, priority, metadata, size, is_private, channel)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
        stmt.run(id, sessionId, validatedKey, input.value, input.category || null, input.priority || 'normal', input.metadata || null, size, input.isPrivate ? 1 : 0, channel);
        return this.getById(id);
    }
    getById(id) {
        const stmt = this.db.prepare('SELECT * FROM context_items WHERE id = ?');
        return stmt.get(id);
    }
    getBySessionId(sessionId) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? 
      ORDER BY priority DESC, created_at DESC
    `);
        return stmt.all(sessionId);
    }
    getByKey(sessionId, key) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? AND key = ?
    `);
        return stmt.get(sessionId, key);
    }
    getByCategory(sessionId, category) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? AND category = ?
      ORDER BY priority DESC, created_at DESC
    `);
        return stmt.all(sessionId, category);
    }
    getByPriority(sessionId, priority) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? AND priority = ?
      ORDER BY created_at DESC
    `);
        return stmt.all(sessionId, priority);
    }
    search(query, sessionId, includePrivate = false) {
        let sql = `
      SELECT * FROM context_items 
      WHERE (key LIKE ? OR value LIKE ?)
    `;
        const params = [`%${query}%`, `%${query}%`];
        if (sessionId) {
            if (includePrivate) {
                sql += ' AND (is_private = 0 OR session_id = ?)';
                params.push(sessionId);
            }
            else {
                sql += ' AND is_private = 0';
            }
        }
        else {
            sql += ' AND is_private = 0';
        }
        sql += ' ORDER BY priority DESC, created_at DESC';
        const stmt = this.db.prepare(sql);
        return stmt.all(...params);
    }
    // Enhanced search method with all new parameters
    searchEnhanced(options) {
        const { query, sessionId, searchIn = ['key', 'value'], category, channel, channels, sort = 'created_desc', limit, offset = 0, createdAfter, createdBefore, keyPattern, priorities, } = options;
        // Build the base query with proper privacy filtering
        let sql = `
      SELECT * FROM context_items 
      WHERE (is_private = 0 OR session_id = ?)
    `;
        const params = [sessionId];
        // Add search query with searchIn support
        if (query) {
            const searchConditions = [];
            // Escape special characters for LIKE operator
            const escapedQuery = query.replace(/[%_\\]/g, `${ContextRepository.SQLITE_ESCAPE_CHAR}$&`);
            if (searchIn.includes('key')) {
                searchConditions.push(`key LIKE ? ESCAPE '${ContextRepository.SQLITE_ESCAPE_CHAR}'`);
                params.push(`%${escapedQuery}%`);
            }
            if (searchIn.includes('value')) {
                searchConditions.push(`value LIKE ? ESCAPE '${ContextRepository.SQLITE_ESCAPE_CHAR}'`);
                params.push(`%${escapedQuery}%`);
            }
            if (searchConditions.length > 0) {
                sql += ` AND (${searchConditions.join(' OR ')})`;
            }
        }
        // Add filters
        if (category) {
            sql += ' AND category = ?';
            params.push(category);
        }
        if (channel) {
            sql += ' AND channel = ?';
            params.push(channel);
        }
        if (channels && channels.length > 0) {
            sql += ` AND channel IN (${channels.map(() => '?').join(',')})`;
            params.push(...channels);
        }
        // Handle relative time parsing for createdAfter
        if (createdAfter) {
            const parsedDate = this.parseRelativeTime(createdAfter);
            const effectiveDate = parsedDate || createdAfter;
            sql += ' AND created_at > ?';
            params.push(effectiveDate);
        }
        // Handle relative time parsing for createdBefore
        if (createdBefore) {
            let effectiveDate = createdBefore;
            // Special handling for "today" and "yesterday" for createdBefore
            const now = new Date();
            const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
            if (createdBefore === 'today') {
                effectiveDate = today.toISOString(); // Start of today
            }
            else if (createdBefore === 'yesterday') {
                const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
                effectiveDate = yesterday.toISOString(); // Start of yesterday
            }
            else {
                const parsedDate = this.parseRelativeTime(createdBefore);
                if (parsedDate) {
                    effectiveDate = parsedDate;
                }
            }
            sql += ' AND created_at < ?';
            params.push(effectiveDate);
        }
        if (keyPattern) {
            const globPattern = this.convertToGlobPattern(keyPattern);
            sql += ' AND key GLOB ?';
            params.push(globPattern);
        }
        if (priorities && priorities.length > 0) {
            sql += ` AND priority IN (${priorities.map(() => '?').join(',')})`;
            params.push(...priorities);
        }
        // Count total before pagination
        const totalCount = this.getTotalCount(sql, params);
        // Add sorting
        sql += ` ORDER BY ${this.buildSortClause(sort)}`;
        // Add pagination
        sql = this.addPaginationToQuery(sql, params, limit, offset);
        const stmt = this.db.prepare(sql);
        const items = stmt.all(...params);
        return { items, totalCount };
    }
    update(id, updates) {
        const fieldsToUpdate = { ...updates };
        const setClause = Object.keys(fieldsToUpdate)
            .filter(key => key !== 'id' && key !== 'session_id' && key !== 'created_at')
            .map(key => `${key} = ?`)
            .join(', ');
        if (setClause) {
            const values = Object.keys(fieldsToUpdate)
                .filter(key => key !== 'id' && key !== 'session_id' && key !== 'created_at')
                .map(key => fieldsToUpdate[key]);
            const stmt = this.db.prepare(`
        UPDATE context_items 
        SET ${setClause}
        WHERE id = ?
      `);
            stmt.run(...values, id);
        }
    }
    delete(id) {
        const stmt = this.db.prepare('DELETE FROM context_items WHERE id = ?');
        stmt.run(id);
    }
    deleteBySessionId(sessionId) {
        const stmt = this.db.prepare('DELETE FROM context_items WHERE session_id = ?');
        stmt.run(sessionId);
    }
    deleteByKey(sessionId, key) {
        const stmt = this.db.prepare('DELETE FROM context_items WHERE session_id = ? AND key = ?');
        stmt.run(sessionId, key);
    }
    copyBetweenSessions(fromSessionId, toSessionId) {
        const stmt = this.db.prepare(`
      INSERT OR IGNORE INTO context_items (id, session_id, key, value, category, priority, metadata, size, is_private, channel, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
    `);
        const items = this.getBySessionId(fromSessionId);
        let copied = 0;
        for (const item of items) {
            try {
                stmt.run(this.generateId(), toSessionId, item.key, item.value, item.category, item.priority, item.metadata, item.size, item.is_private, item.channel || 'general', item.created_at);
                copied++;
            }
            catch (_error) {
                // Skip items that would cause unique constraint violations
                console.warn(`Skipping duplicate key '${item.key}' when copying to session ${toSessionId}`);
            }
        }
        return copied;
    }
    // Get items accessible from a specific session (all public items + own private items)
    getAccessibleItems(sessionId, options) {
        let sql = `
      SELECT * FROM context_items 
      WHERE (is_private = 0 OR session_id = ?)
    `;
        const params = [sessionId];
        if (options?.key) {
            sql += ' AND key = ?';
            params.push(options.key);
        }
        if (options?.category) {
            sql += ' AND category = ?';
            params.push(options.category);
        }
        sql += ' ORDER BY priority DESC, created_at DESC';
        const stmt = this.db.prepare(sql);
        return stmt.all(...params);
    }
    // Get a specific item by key, respecting privacy
    getAccessibleByKey(sessionId, key) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE key = ? AND (is_private = 0 OR session_id = ?)
      ORDER BY 
        CASE WHEN session_id = ? THEN 0 ELSE 1 END,  -- Prioritize own session's items
        created_at DESC
      LIMIT 1
    `);
        const result = stmt.get(key, sessionId, sessionId);
        return result || null;
    }
    searchAcrossSessions(query, currentSessionId) {
        let sql = `
      SELECT * FROM context_items 
      WHERE (key LIKE ? OR value LIKE ?) AND is_private = 0
    `;
        const params = [`%${query}%`, `%${query}%`];
        // Include private items from current session if provided
        if (currentSessionId) {
            sql = `
        SELECT * FROM context_items 
        WHERE (key LIKE ? OR value LIKE ?) 
        AND (is_private = 0 OR session_id = ?)
      `;
            params.push(currentSessionId);
        }
        sql += ' ORDER BY priority DESC, created_at DESC';
        const stmt = this.db.prepare(sql);
        return stmt.all(...params);
    }
    // Enhanced search across sessions with pagination support
    searchAcrossSessionsEnhanced(options) {
        const { query, currentSessionId, sessions, includeShared = true, searchIn = ['key', 'value'], limit = 25, // Default pagination limit
        offset = 0, sort = 'created_desc', category, channel, channels, priorities, createdAfter, createdBefore, keyPattern, } = options;
        // Validate pagination parameters
        const validLimit = Math.min(Math.max(1, limit), 100); // 1-100 range
        const validOffset = Math.max(0, offset);
        // Build the base query for cross-session search
        let sql = `
      SELECT * FROM context_items 
      WHERE 1=1
    `;
        const params = [];
        // Handle privacy filtering
        if (currentSessionId && includeShared) {
            sql += ' AND (is_private = 0 OR session_id = ?)';
            params.push(currentSessionId);
        }
        else if (includeShared) {
            sql += ' AND is_private = 0';
        }
        else if (currentSessionId) {
            sql += ' AND session_id = ?';
            params.push(currentSessionId);
        }
        else {
            sql += ' AND is_private = 0';
        }
        // Session filtering
        if (sessions && sessions.length > 0) {
            sql += ` AND session_id IN (${sessions.map(() => '?').join(',')})`;
            params.push(...sessions);
        }
        // Add search query with searchIn support
        if (query) {
            const searchConditions = [];
            const escapedQuery = query.replace(/[%_\\]/g, `${ContextRepository.SQLITE_ESCAPE_CHAR}$&`);
            if (searchIn.includes('key')) {
                searchConditions.push(`key LIKE ? ESCAPE '${ContextRepository.SQLITE_ESCAPE_CHAR}'`);
                params.push(`%${escapedQuery}%`);
            }
            if (searchIn.includes('value')) {
                searchConditions.push(`value LIKE ? ESCAPE '${ContextRepository.SQLITE_ESCAPE_CHAR}'`);
                params.push(`%${escapedQuery}%`);
            }
            if (searchConditions.length > 0) {
                sql += ` AND (${searchConditions.join(' OR ')})`;
            }
        }
        // Add filters (same pattern as searchEnhanced)
        if (category) {
            sql += ' AND category = ?';
            params.push(category);
        }
        if (channel) {
            sql += ' AND channel = ?';
            params.push(channel);
        }
        if (channels && channels.length > 0) {
            sql += ` AND channel IN (${channels.map(() => '?').join(',')})`;
            params.push(...channels);
        }
        if (createdAfter) {
            const parsedDate = this.parseRelativeTime(createdAfter);
            const effectiveDate = parsedDate || createdAfter;
            sql += ' AND created_at > ?';
            params.push(effectiveDate);
        }
        if (createdBefore) {
            let effectiveDate = createdBefore;
            const now = new Date();
            const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
            if (createdBefore === 'today') {
                effectiveDate = today.toISOString();
            }
            else if (createdBefore === 'yesterday') {
                const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
                effectiveDate = yesterday.toISOString();
            }
            else {
                const parsedDate = this.parseRelativeTime(createdBefore);
                if (parsedDate) {
                    effectiveDate = parsedDate;
                }
            }
            sql += ' AND created_at < ?';
            params.push(effectiveDate);
        }
        if (keyPattern) {
            const globPattern = this.convertToGlobPattern(keyPattern);
            sql += ' AND key GLOB ?';
            params.push(globPattern);
        }
        if (priorities && priorities.length > 0) {
            sql += ` AND priority IN (${priorities.map(() => '?').join(',')})`;
            params.push(...priorities);
        }
        // Count total before pagination
        const totalCount = this.getTotalCount(sql, params);
        // Add sorting
        sql += ` ORDER BY ${this.buildSortClause(sort)}`;
        // Add pagination
        sql = this.addPaginationToQuery(sql, params, validLimit, validOffset);
        const stmt = this.db.prepare(sql);
        const items = stmt.all(...params);
        // Calculate pagination metadata
        const totalPages = Math.ceil(totalCount / validLimit);
        const currentPage = Math.floor(validOffset / validLimit) + 1;
        const hasNextPage = currentPage < totalPages;
        const hasPreviousPage = currentPage > 1;
        const nextOffset = hasNextPage ? validOffset + validLimit : null;
        const previousOffset = hasPreviousPage ? Math.max(0, validOffset - validLimit) : null;
        const pagination = {
            currentPage,
            totalPages,
            totalItems: totalCount,
            itemsPerPage: validLimit,
            hasNextPage,
            hasPreviousPage,
            nextOffset,
            previousOffset,
        };
        return { items, totalCount, pagination };
    }
    getStatsBySession(sessionId) {
        const countStmt = this.db.prepare('SELECT COUNT(*) as count, SUM(size) as totalSize FROM context_items WHERE session_id = ?');
        const result = countStmt.get(sessionId);
        const categoryStmt = this.db.prepare(`
      SELECT category, COUNT(*) as count 
      FROM context_items 
      WHERE session_id = ? 
      GROUP BY category
    `);
        const categories = categoryStmt.all(sessionId);
        const priorityStmt = this.db.prepare(`
      SELECT priority, COUNT(*) as count 
      FROM context_items 
      WHERE session_id = ? 
      GROUP BY priority
    `);
        const priorities = priorityStmt.all(sessionId);
        return {
            count: result.count || 0,
            totalSize: result.totalSize || 0,
            byCategory: categories.reduce((acc, cat) => {
                acc[cat.category || 'uncategorized'] = cat.count;
                return acc;
            }, {}),
            byPriority: priorities.reduce((acc, pri) => {
                acc[pri.priority] = pri.count;
                return acc;
            }, {}),
        };
    }
    // Get items by channel
    getByChannel(sessionId, channel) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? AND channel = ?
      ORDER BY priority DESC, created_at DESC
    `);
        return stmt.all(sessionId, channel);
    }
    // Get items by multiple channels
    getByChannels(sessionId, channels) {
        if (channels.length === 0) {
            return [];
        }
        const placeholders = channels.map(() => '?').join(',');
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE session_id = ? AND channel IN (${placeholders})
      ORDER BY priority DESC, created_at DESC
    `);
        return stmt.all(sessionId, ...channels);
    }
    // Get items by channel across all sessions
    getByChannelAcrossSessions(channel) {
        const stmt = this.db.prepare(`
      SELECT * FROM context_items 
      WHERE channel = ? AND is_private = 0
      ORDER BY priority DESC, created_at DESC
    `);
        return stmt.all(channel);
    }
    // Enhanced query method with all new parameters
    queryEnhanced(options) {
        const { sessionId, key, category, channel, channels, sort, limit, offset = 0, createdAfter, createdBefore, keyPattern, priorities, } = options;
        // Apply default pagination parameters
        // Default sort: created_desc (most recent first)
        const effectiveSort = sort || 'created_desc';
        // Default limit: 100 items (or unlimited if explicitly set to 0)
        // Negative limits are treated as default
        // Invalid types are treated as default
        let effectiveLimit;
        const numericLimit = typeof limit === 'number' ? limit : undefined;
        if (numericLimit === 0) {
            effectiveLimit = undefined; // Unlimited
        }
        else if (numericLimit === undefined || numericLimit < 0) {
            effectiveLimit = 100; // Default limit
        }
        else {
            effectiveLimit = numericLimit; // Use provided limit
        }
        // Validate offset is not negative
        const validOffset = Math.max(0, offset || 0);
        // Build the base query with proper privacy filtering
        let sql = `
      SELECT * FROM context_items 
      WHERE (is_private = 0 OR session_id = ?)
    `;
        const params = [sessionId];
        // Add filters
        if (key) {
            sql += ' AND key = ?';
            params.push(key);
        }
        if (category) {
            sql += ' AND category = ?';
            params.push(category);
        }
        if (channel) {
            sql += ' AND channel = ?';
            params.push(channel);
        }
        if (channels && channels.length > 0) {
            sql += ` AND channel IN (${channels.map(() => '?').join(',')})`;
            params.push(...channels);
        }
        // Handle relative time parsing for createdAfter
        if (createdAfter) {
            const parsedDate = this.parseRelativeTime(createdAfter);
            const effectiveDate = parsedDate || createdAfter;
            sql += ' AND created_at > ?';
            params.push(effectiveDate);
        }
        // Handle relative time parsing for createdBefore
        if (createdBefore) {
            let effectiveDate = createdBefore;
            // Special handling for "today" and "yesterday" for createdBefore
            const now = new Date();
            const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
            if (createdBefore === 'today') {
                effectiveDate = today.toISOString(); // Start of today
            }
            else if (createdBefore === 'yesterday') {
                const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
                effectiveDate = yesterday.toISOString(); // Start of yesterday
            }
            else {
                const parsedDate = this.parseRelativeTime(createdBefore);
                if (parsedDate) {
                    effectiveDate = parsedDate;
                }
            }
            sql += ' AND created_at < ?';
            params.push(effectiveDate);
        }
        if (keyPattern) {
            const globPattern = this.convertToGlobPattern(keyPattern);
            sql += ' AND key GLOB ?';
            params.push(globPattern);
        }
        if (priorities && priorities.length > 0) {
            sql += ` AND priority IN (${priorities.map(() => '?').join(',')})`;
            params.push(...priorities);
        }
        // Count total before pagination
        const totalCount = this.getTotalCount(sql, params);
        // Add sorting
        sql += ` ORDER BY ${this.buildSortClause(effectiveSort)}`;
        // Add pagination
        sql = this.addPaginationToQuery(sql, params, effectiveLimit, validOffset);
        const stmt = this.db.prepare(sql);
        const items = stmt.all(...params);
        return { items, totalCount };
    }
    // Get timeline data with enhanced options
    getTimelineData(options) {
        const { sessionId, startDate, endDate, categories, relativeTime, itemsPerPeriod, includeItems, groupBy = 'day', minItemsPerPeriod, showEmpty = false, } = options;
        // Calculate date range from relative time
        let effectiveStartDate = startDate;
        let effectiveEndDate = endDate;
        if (relativeTime) {
            const parsedStartDate = this.parseRelativeTime(relativeTime);
            if (parsedStartDate) {
                effectiveStartDate = parsedStartDate;
            }
            // Special handling for end dates
            const now = new Date();
            const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
            if (relativeTime === 'today') {
                effectiveEndDate = new Date(today.getTime() + 24 * 60 * 60 * 1000).toISOString();
            }
            else if (relativeTime === 'yesterday') {
                const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
                effectiveStartDate = yesterday.toISOString();
                effectiveEndDate = today.toISOString();
            }
            else if (relativeTime === 'last week') {
                const startOfLastWeek = new Date(today);
                startOfLastWeek.setDate(today.getDate() - today.getDay() - 7);
                const endOfLastWeek = new Date(startOfLastWeek);
                endOfLastWeek.setDate(startOfLastWeek.getDate() + 7);
                effectiveStartDate = startOfLastWeek.toISOString();
                effectiveEndDate = endOfLastWeek.toISOString();
            }
        }
        // Build query for timeline
        let dateFmt = '%Y-%m-%d'; // day grouping
        if (groupBy === 'hour') {
            dateFmt = '%Y-%m-%d %H:00';
        }
        else if (groupBy === 'week') {
            dateFmt = '%Y-W%W';
        }
        let sql = `
      SELECT 
        strftime('${dateFmt}', created_at) as period,
        COUNT(*) as count,
        ${includeItems ? 'GROUP_CONCAT(id) as item_ids' : 'NULL as item_ids'}
      FROM context_items
      WHERE session_id = ?
    `;
        const params = [sessionId];
        if (effectiveStartDate) {
            sql += ' AND created_at >= ?';
            params.push(effectiveStartDate);
        }
        if (effectiveEndDate) {
            sql += ' AND created_at <= ?';
            params.push(effectiveEndDate);
        }
        if (categories && categories.length > 0) {
            sql += ` AND category IN (${categories.map(() => '?').join(',')})`;
            params.push(...categories);
        }
        sql += ' GROUP BY period ORDER BY period DESC';
        const stmt = this.db.prepare(sql);
        let timeline = stmt.all(...params);
        // Handle minItemsPerPeriod filter (before showEmpty logic)
        if (minItemsPerPeriod && minItemsPerPeriod > 0 && !showEmpty) {
            // Treat negative values as 0, round fractional values up
            const minItems = Math.max(0, Math.ceil(minItemsPerPeriod));
            timeline = timeline.filter(period => period.count >= minItems);
        }
        // Handle showEmpty - generate empty periods for date ranges
        if (showEmpty && (effectiveStartDate || effectiveEndDate)) {
            const existingPeriods = new Map(timeline.map(p => [p.period, p]));
            const allPeriods = [];
            // Determine date range
            const start = effectiveStartDate ? new Date(effectiveStartDate) : new Date();
            const end = effectiveEndDate ? new Date(effectiveEndDate) : new Date();
            // Return empty array if end is before start
            if (end < start) {
                return [];
            }
            // Generate all periods in range
            const current = new Date(start);
            let periodCount = 0;
            const maxPeriods = groupBy === 'hour' ? 24 * 365 : 365; // Reasonable limits
            while (current <= end && periodCount < maxPeriods) {
                let periodKey;
                if (groupBy === 'hour') {
                    periodKey = current.toISOString().slice(0, 13) + ':00';
                }
                else if (groupBy === 'week') {
                    // ISO week format - need to calculate properly
                    const thursday = new Date(current);
                    thursday.setDate(current.getDate() - current.getDay() + 4); // Thursday of current week
                    const yearStart = new Date(thursday.getFullYear(), 0, 1);
                    const weekNum = Math.ceil(((thursday.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
                    periodKey = `${thursday.getFullYear()}-W${weekNum.toString().padStart(2, '0')}`;
                }
                else {
                    // day
                    periodKey = current.toISOString().slice(0, 10);
                }
                // Use existing period data or create empty one
                const existingPeriod = existingPeriods.get(periodKey);
                if (existingPeriod) {
                    allPeriods.push(existingPeriod);
                }
                else {
                    allPeriods.push({
                        period: periodKey,
                        count: 0,
                        item_ids: null,
                        items: [],
                    });
                }
                // Increment current date
                if (groupBy === 'hour') {
                    current.setHours(current.getHours() + 1);
                }
                else if (groupBy === 'week') {
                    current.setDate(current.getDate() + 7);
                }
                else {
                    current.setDate(current.getDate() + 1);
                }
                periodCount++;
            }
            // Sort by period descending
            timeline = allPeriods.sort((a, b) => b.period.localeCompare(a.period));
            // Apply minItemsPerPeriod filter after generating empty periods if showEmpty overrides it
            if (minItemsPerPeriod && minItemsPerPeriod > 0) {
                // When showEmpty is true, we still show all periods but can use minItemsPerPeriod for other purposes
                // According to tests, showEmpty should override minItemsPerPeriod behavior
            }
        }
        // If includeItems is true, fetch the actual items for each period
        if (includeItems && timeline.length > 0) {
            for (const period of timeline) {
                if (period.item_ids) {
                    const itemIds = period.item_ids.split(',');
                    let itemsToFetch = itemIds;
                    // Limit items per period if specified
                    if (itemsPerPeriod && itemIds.length > itemsPerPeriod) {
                        itemsToFetch = itemIds.slice(0, itemsPerPeriod);
                        period.hasMore = true;
                        period.totalCount = itemIds.length;
                    }
                    // Fetch the items
                    const itemStmt = this.db.prepare(`
            SELECT * FROM context_items 
            WHERE id IN (${itemsToFetch.map(() => '?').join(',')})
            ORDER BY created_at DESC
          `);
                    period.items = itemStmt.all(...itemsToFetch);
                }
                else {
                    // No items for this period
                    period.items = [];
                }
            }
        }
        else if (includeItems) {
            // Ensure all periods have items array when includeItems is true
            for (const period of timeline) {
                if (!period.items) {
                    period.items = [];
                }
            }
        }
        return timeline;
    }
    // Get diff data for context items
    getDiff(options) {
        const { sessionId, sinceTimestamp, category, channel, channels, limit, offset, includeValues = true, } = options;
        // Ensure timestamp is in SQLite format for comparison
        const sqliteTimestamp = (0, timestamps_js_1.ensureSQLiteFormat)(sinceTimestamp);
        // Build queries for added and modified items
        let addedSql = `
      SELECT * FROM context_items 
      WHERE session_id = ? 
      AND created_at > ?
    `;
        const addedParams = [sessionId, sqliteTimestamp];
        let modifiedSql = `
      SELECT * FROM context_items 
      WHERE session_id = ? 
      AND created_at <= ?
      AND updated_at > ?
      AND created_at != updated_at
    `;
        const modifiedParams = [sessionId, sqliteTimestamp, sqliteTimestamp];
        // Add category filter
        if (category) {
            addedSql += ' AND category = ?';
            modifiedSql += ' AND category = ?';
            addedParams.push(category);
            modifiedParams.push(category);
        }
        // Add channel filter
        if (channel) {
            addedSql += ' AND channel = ?';
            modifiedSql += ' AND channel = ?';
            addedParams.push(channel);
            modifiedParams.push(channel);
        }
        if (channels && channels.length > 0) {
            const placeholders = channels.map(() => '?').join(',');
            addedSql += ` AND channel IN (${placeholders})`;
            modifiedSql += ` AND channel IN (${placeholders})`;
            addedParams.push(...channels);
            modifiedParams.push(...channels);
        }
        // Add ordering
        addedSql += ' ORDER BY created_at DESC';
        modifiedSql += ' ORDER BY updated_at DESC';
        // Add pagination if requested
        if (limit) {
            addedSql += ' LIMIT ?';
            modifiedSql += ' LIMIT ?';
            addedParams.push(limit);
            modifiedParams.push(limit);
            if (offset) {
                addedSql += ' OFFSET ?';
                modifiedSql += ' OFFSET ?';
                addedParams.push(offset);
                modifiedParams.push(offset);
            }
        }
        // Execute queries
        const addedItems = this.db.prepare(addedSql).all(...addedParams);
        const modifiedItems = this.db.prepare(modifiedSql).all(...modifiedParams);
        // Filter out values if not needed
        if (!includeValues) {
            const stripValue = (item) => ({
                ...item,
                value: undefined,
            });
            return {
                added: addedItems.map(stripValue),
                modified: modifiedItems.map(stripValue),
            };
        }
        return { added: addedItems, modified: modifiedItems };
    }
    // Get deleted keys by comparing with checkpoint
    getDeletedKeysFromCheckpoint(sessionId, checkpointId) {
        // Get items from checkpoint
        const checkpointItems = this.db
            .prepare(`
        SELECT ci.key FROM context_items ci
        JOIN checkpoint_items cpi ON ci.id = cpi.context_item_id
        WHERE cpi.checkpoint_id = ?
        AND ci.session_id = ?
      `)
            .all(checkpointId, sessionId);
        // Get current items
        const currentItems = this.db
            .prepare('SELECT key FROM context_items WHERE session_id = ?')
            .all(sessionId);
        const checkpointKeys = new Set(checkpointItems.map((i) => i.key));
        const currentKeys = new Set(currentItems.map((i) => i.key));
        // Find deleted items
        return Array.from(checkpointKeys).filter(key => !currentKeys.has(key));
    }
    // List all channels with metadata
    listChannels(options) {
        const { sessionId, sessionIds, sort = 'name', includeEmpty = false } = options;
        // Build the base query
        let sql = `
      SELECT 
        channel,
        COUNT(*) as total_count,
        SUM(CASE WHEN is_private = 0 THEN 1 ELSE 0 END) as public_count,
        SUM(CASE WHEN is_private = 1 THEN 1 ELSE 0 END) as private_count,
        MAX(updated_at) as last_activity,
        GROUP_CONCAT(DISTINCT category) as categories,
        GROUP_CONCAT(DISTINCT priority) as priorities,
        COUNT(DISTINCT session_id) as session_count
      FROM context_items
    `;
        const params = [];
        // Add session filters
        if (sessionId) {
            sql += ' WHERE session_id = ?';
            params.push(sessionId);
        }
        else if (sessionIds && sessionIds.length > 0) {
            sql += ` WHERE session_id IN (${sessionIds.map(() => '?').join(',')})`;
            params.push(...sessionIds);
        }
        sql += ' GROUP BY channel';
        // Add having clause if not including empty channels
        if (!includeEmpty) {
            sql += ' HAVING total_count > 0';
        }
        // Add sorting
        switch (sort) {
            case 'count':
                sql += ' ORDER BY total_count DESC, channel ASC';
                break;
            case 'activity':
                sql += ' ORDER BY last_activity DESC, channel ASC';
                break;
            case 'name':
            default:
                sql += ' ORDER BY channel ASC';
                break;
        }
        const stmt = this.db.prepare(sql);
        const channels = stmt.all(...params);
        // Post-process results
        return channels.map(channel => ({
            ...channel,
            categories: channel.categories ? channel.categories.split(',').filter(Boolean) : [],
            priorities: channel.priorities ? channel.priorities.split(',').filter(Boolean) : [],
        }));
    }
    // Get detailed statistics for channels
    getChannelStats(options) {
        const { channel, sessionId, includeTimeSeries = false, includeInsights = false } = options;
        if (channel) {
            // Single channel stats
            return this.getSingleChannelStats(channel, sessionId, includeTimeSeries, includeInsights);
        }
        else {
            // All channels overview
            return this.getAllChannelsStats(sessionId, includeTimeSeries, includeInsights);
        }
    }
    getSingleChannelStats(channel, sessionId, includeTimeSeries = false, includeInsights = false) {
        // Base stats query
        let statsSQL = `
      SELECT 
        ? as channel,
        COUNT(*) as total_items,
        COUNT(DISTINCT session_id) as unique_sessions,
        MAX(updated_at) as last_activity,
        MIN(created_at) as first_activity,
        SUM(size) as total_size,
        AVG(size) as avg_size,
        SUM(CASE WHEN is_private = 0 THEN 1 ELSE 0 END) as public_items,
        SUM(CASE WHEN is_private = 1 THEN 1 ELSE 0 END) as private_items
      FROM context_items
      WHERE channel = ?
    `;
        const statsParams = [channel, channel];
        if (sessionId) {
            statsSQL += ' AND (is_private = 0 OR session_id = ?)';
            statsParams.push(sessionId);
        }
        const stats = this.db.prepare(statsSQL).get(...statsParams);
        // Category distribution
        let categorySQL = `
      SELECT 
        COALESCE(category, 'uncategorized') as category,
        COUNT(*) as count,
        ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM context_items WHERE channel = ?), 2) as percentage
      FROM context_items
      WHERE channel = ?
    `;
        const categoryParams = [channel, channel];
        if (sessionId) {
            categorySQL += ' AND (is_private = 0 OR session_id = ?)';
            categoryParams.push(sessionId);
        }
        categorySQL += ' GROUP BY category ORDER BY count DESC';
        const categoryDistribution = this.db.prepare(categorySQL).all(...categoryParams);
        // Priority distribution
        let prioritySQL = `
      SELECT 
        priority,
        COUNT(*) as count,
        ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM context_items WHERE channel = ?), 2) as percentage
      FROM context_items
      WHERE channel = ?
    `;
        const priorityParams = [channel, channel];
        if (sessionId) {
            prioritySQL += ' AND (is_private = 0 OR session_id = ?)';
            priorityParams.push(sessionId);
        }
        prioritySQL += ' GROUP BY priority ORDER BY count DESC';
        const priorityDistribution = this.db.prepare(prioritySQL).all(...priorityParams);
        // Top contributors
        let contributorsSQL = `
      SELECT 
        session_id,
        COUNT(*) as item_count,
        MAX(updated_at) as last_contribution
      FROM context_items
      WHERE channel = ?
    `;
        const contributorsParams = [channel];
        if (sessionId) {
            contributorsSQL += ' AND (is_private = 0 OR session_id = ?)';
            contributorsParams.push(sessionId);
        }
        contributorsSQL += ' GROUP BY session_id ORDER BY item_count DESC LIMIT 5';
        const topContributors = this.db.prepare(contributorsSQL).all(...contributorsParams);
        // Activity metrics
        const now = new Date();
        const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
        const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString();
        let activitySQL = `
      SELECT 
        SUM(CASE WHEN created_at > ? THEN 1 ELSE 0 END) as items_last_24h,
        SUM(CASE WHEN updated_at > ? THEN 1 ELSE 0 END) as updates_last_24h,
        SUM(CASE WHEN created_at > ? THEN 1 ELSE 0 END) as items_last_week,
        SUM(CASE WHEN updated_at > ? THEN 1 ELSE 0 END) as updates_last_week
      FROM context_items
      WHERE channel = ?
    `;
        const activityParams = [oneDayAgo, oneDayAgo, oneWeekAgo, oneWeekAgo, channel];
        if (sessionId) {
            activitySQL += ' AND (is_private = 0 OR session_id = ?)';
            activityParams.push(sessionId);
        }
        const activityStats = this.db.prepare(activitySQL).get(...activityParams);
        // Build result
        const result = {
            channel,
            stats,
            categoryDistribution,
            priorityDistribution,
            topContributors,
            activityStats,
        };
        // Add time series if requested
        if (includeTimeSeries) {
            const hourlySQL = `
        SELECT 
          strftime('%H', created_at) as hour,
          COUNT(*) as count
        FROM context_items
        WHERE channel = ?
        ${sessionId ? ' AND (is_private = 0 OR session_id = ?)' : ''}
        GROUP BY hour
        ORDER BY hour
      `;
            const hourlyParams = sessionId ? [channel, sessionId] : [channel];
            result.hourlyActivity = this.db.prepare(hourlySQL).all(...hourlyParams);
            const dailySQL = `
        SELECT 
          strftime('%Y-%m-%d', created_at) as date,
          COUNT(*) as count
        FROM context_items
        WHERE channel = ?
        ${sessionId ? ' AND (is_private = 0 OR session_id = ?)' : ''}
        GROUP BY date
        ORDER BY date DESC
        LIMIT 30
      `;
            const dailyParams = sessionId ? [channel, sessionId] : [channel];
            result.dailyActivity = this.db.prepare(dailySQL).all(...dailyParams);
        }
        // Add insights if requested
        if (includeInsights) {
            result.insights = this.generateChannelInsights(channel, stats, activityStats, categoryDistribution);
        }
        return result;
    }
    getAllChannelsStats(sessionId, _includeTimeSeries = false, includeInsights = false) {
        // Overall stats
        let overallSQL = `
      SELECT 
        COUNT(DISTINCT channel) as total_channels,
        COUNT(*) as total_items,
        COUNT(DISTINCT session_id) as total_sessions,
        SUM(size) as total_size,
        AVG(size) as avg_size_per_item
      FROM context_items
    `;
        const overallParams = [];
        if (sessionId) {
            overallSQL += ' WHERE (is_private = 0 OR session_id = ?)';
            overallParams.push(sessionId);
        }
        const overallStats = this.db.prepare(overallSQL).get(...overallParams);
        // Channel rankings
        let rankingSQL = `
      SELECT 
        channel,
        COUNT(*) as item_count,
        SUM(size) as total_size,
        MAX(updated_at) as last_activity,
        COUNT(DISTINCT session_id) as session_count,
        ROUND(COUNT(*) * 100.0 / (SELECT COUNT(*) FROM context_items${sessionId ? ' WHERE (is_private = 0 OR session_id = ?)' : ''}), 2) as percentage_of_total
      FROM context_items
    `;
        const rankingParams = [];
        if (sessionId) {
            rankingSQL += ' WHERE (is_private = 0 OR session_id = ?)';
            rankingParams.push(sessionId);
            rankingParams.unshift(sessionId); // For the subquery
        }
        rankingSQL += ' GROUP BY channel ORDER BY item_count DESC';
        const channelRankings = this.db.prepare(rankingSQL).all(...rankingParams);
        // Health metrics
        const healthMetrics = channelRankings.map(ch => {
            const daysSinceActivity = Math.floor((Date.now() - new Date(ch.last_activity).getTime()) / (1000 * 60 * 60 * 24));
            const avgItemsPerSession = ch.item_count / ch.session_count;
            return {
                channel: ch.channel,
                health_score: this.calculateHealthScore(daysSinceActivity, avgItemsPerSession, ch.item_count),
                days_since_activity: daysSinceActivity,
                avg_items_per_session: avgItemsPerSession,
            };
        });
        // Channel relationships
        const relationshipSQL = `
      SELECT 
        c1.channel as channel1,
        c2.channel as channel2,
        COUNT(DISTINCT c1.session_id) as shared_sessions
      FROM context_items c1
      JOIN context_items c2 ON c1.session_id = c2.session_id AND c1.channel < c2.channel
      ${sessionId ? 'WHERE (c1.is_private = 0 OR c1.session_id = ?) AND (c2.is_private = 0 OR c2.session_id = ?)' : ''}
      GROUP BY c1.channel, c2.channel
      HAVING shared_sessions > 1
      ORDER BY shared_sessions DESC
      LIMIT 10
    `;
        const relationshipParams = sessionId ? [sessionId, sessionId] : [];
        const channelRelationships = this.db
            .prepare(relationshipSQL)
            .all(...relationshipParams);
        const result = {
            overallStats,
            channelRankings,
            healthMetrics,
            channelRelationships,
        };
        if (includeInsights) {
            result.insights = this.generateOverallInsights(channelRankings, healthMetrics);
        }
        return result;
    }
    calculateHealthScore(daysSinceActivity, avgItemsPerSession, totalItems) {
        // Health score based on activity recency, engagement, and size
        let score = 100;
        // Penalize for inactivity
        if (daysSinceActivity > 30)
            score -= 30;
        else if (daysSinceActivity > 14)
            score -= 20;
        else if (daysSinceActivity > 7)
            score -= 10;
        // Reward for engagement
        if (avgItemsPerSession > 10)
            score += 10;
        else if (avgItemsPerSession > 5)
            score += 5;
        // Reward for size
        if (totalItems > 100)
            score += 10;
        else if (totalItems > 50)
            score += 5;
        return Math.max(0, Math.min(100, score));
    }
    generateChannelInsights(channel, stats, activityStats, categoryDistribution) {
        const insights = [];
        // Activity insights
        if (activityStats.items_last_24h === 0 && activityStats.updates_last_24h === 0) {
            insights.push(`Channel "${channel}" has been inactive for the last 24 hours`);
        }
        if (activityStats.items_last_week > activityStats.items_last_24h * 7) {
            insights.push(`Channel "${channel}" shows declining activity compared to weekly average`);
        }
        // Category insights
        if (categoryDistribution.length > 0) {
            const topCategory = categoryDistribution[0];
            if (topCategory.percentage > 60) {
                insights.push(`Channel "${channel}" is heavily focused on "${topCategory.category}" (${topCategory.percentage}%)`);
            }
        }
        // Size insights
        if (stats.avg_size > 10000) {
            insights.push(`Channel "${channel}" contains large items (avg ${Math.round(stats.avg_size / 1024)}KB)`);
        }
        return insights;
    }
    generateOverallInsights(channelRankings, healthMetrics) {
        const insights = [];
        // Channel concentration
        if (channelRankings.length > 0) {
            const top3Percentage = channelRankings
                .slice(0, 3)
                .reduce((sum, ch) => sum + parseFloat(ch.percentage_of_total), 0);
            if (top3Percentage > 80) {
                insights.push(`Top 3 channels contain ${top3Percentage.toFixed(1)}% of all items - consider better distribution`);
            }
        }
        // Health insights
        const unhealthyChannels = healthMetrics.filter(m => m.health_score < 50);
        if (unhealthyChannels.length > 0) {
            insights.push(`${unhealthyChannels.length} channels show signs of low health (inactive or low engagement)`);
        }
        // Activity patterns
        const inactiveChannels = healthMetrics.filter(m => m.days_since_activity > 30);
        if (inactiveChannels.length > 0) {
            insights.push(`${inactiveChannels.length} channels have been inactive for over 30 days`);
        }
        return insights;
    }
    // Reassign channel for context items
    reassignChannel(options) {
        const { keys, keyPattern, fromChannel, toChannel, sessionId, category, priorities, dryRun = false, } = options;
        const errors = [];
        const itemsMoved = [];
        try {
            // Start transaction
            this.db.prepare('BEGIN TRANSACTION').run();
            // Build the base query
            let sql = 'SELECT id, key, channel FROM context_items WHERE session_id = ?';
            const params = [sessionId];
            // Add conditions based on parameters
            if (keys && keys.length > 0) {
                const placeholders = keys.map(() => '?').join(',');
                sql += ` AND key IN (${placeholders})`;
                params.push(...keys);
            }
            else if (keyPattern) {
                // Convert wildcard pattern to SQL GLOB pattern
                sql += ' AND key GLOB ?';
                params.push(keyPattern);
            }
            else if (fromChannel) {
                sql += ' AND channel = ?';
                params.push(fromChannel);
            }
            // Add category filter
            if (category) {
                sql += ' AND category = ?';
                params.push(category);
            }
            // Add priority filter
            if (priorities && priorities.length > 0) {
                const placeholders = priorities.map(() => '?').join(',');
                sql += ` AND priority IN (${placeholders})`;
                params.push(...priorities);
            }
            // Get items to be moved
            const itemsToMove = this.db.prepare(sql).all(...params);
            if (itemsToMove.length === 0) {
                this.db.prepare('ROLLBACK').run();
                return {
                    itemsAffected: 0,
                    itemsMoved: [],
                    errors: ['No items found matching the specified criteria'],
                };
            }
            // Prepare response data
            for (const item of itemsToMove) {
                itemsMoved.push({
                    key: item.key,
                    oldChannel: item.channel,
                    newChannel: toChannel,
                });
            }
            // If dry run, rollback and return preview
            if (dryRun) {
                this.db.prepare('ROLLBACK').run();
                return {
                    itemsAffected: itemsToMove.length,
                    itemsMoved,
                };
            }
            // Perform the update
            const updateSql = `
        UPDATE context_items 
        SET channel = ?, updated_at = CURRENT_TIMESTAMP
        WHERE id IN (${itemsToMove.map(() => '?').join(',')})
      `;
            const updateParams = [toChannel, ...itemsToMove.map(item => item.id)];
            const result = this.db.prepare(updateSql).run(...updateParams);
            // Commit transaction
            this.db.prepare('COMMIT').run();
            return {
                itemsAffected: result.changes,
                itemsMoved,
            };
        }
        catch (error) {
            // Rollback on error
            try {
                this.db.prepare('ROLLBACK').run();
            }
            catch (_e) {
                // Ignore rollback errors
            }
            errors.push(`Database error: ${error.message}`);
            return {
                itemsAffected: 0,
                itemsMoved: [],
                errors,
            };
        }
    }
    // Batch Operations
    /**
     * Save multiple context items in a single transaction
     */
    batchSave(sessionId, items, options = {}) {
        const { updateExisting = true } = options;
        const results = [];
        let totalSize = 0;
        // Prepare statements
        const checkStmt = this.db.prepare('SELECT id FROM context_items WHERE session_id = ? AND key = ?');
        const insertStmt = this.db.prepare(`
      INSERT INTO context_items (
        id, session_id, key, value, category, priority, channel, 
        created_at, updated_at, size, is_private
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
        const updateStmt = this.db.prepare(`
      UPDATE context_items 
      SET value = ?, category = ?, priority = ?, channel = ?, 
          updated_at = ?, size = ?
      WHERE session_id = ? AND key = ?
    `);
        // Get session default channel
        const sessionStmt = this.db.prepare('SELECT default_channel FROM sessions WHERE id = ?');
        const session = sessionStmt.get(sessionId);
        const defaultChannel = session?.default_channel || 'general';
        items.forEach((item, index) => {
            try {
                // Skip items with missing required fields
                if (!item.key || !item.value) {
                    throw new Error('Missing required fields');
                }
                // Validate the key
                const validatedKey = (0, validation_js_1.validateKey)(item.key);
                const size = this.calculateSize(item.value);
                totalSize += size;
                // Check if key exists
                const existing = checkStmt.get(sessionId, validatedKey);
                if (existing && updateExisting) {
                    // Update existing
                    const now = (0, timestamps_js_1.ensureSQLiteFormat)(new Date().toISOString());
                    updateStmt.run(item.value, item.category || null, item.priority || 'normal', item.channel || defaultChannel, now, size, sessionId, validatedKey);
                    results.push({
                        index,
                        key: validatedKey,
                        success: true,
                        action: 'updated',
                        size,
                    });
                }
                else if (!existing) {
                    // Insert new
                    const id = this.generateId();
                    const now = (0, timestamps_js_1.ensureSQLiteFormat)(new Date().toISOString());
                    insertStmt.run(id, sessionId, validatedKey, item.value, item.category || null, item.priority || 'normal', item.channel || defaultChannel, now, now, size, item.isPrivate ? 1 : 0);
                    results.push({
                        index,
                        key: validatedKey,
                        success: true,
                        action: 'created',
                        id,
                        size,
                    });
                }
                else {
                    // Existing but updateExisting is false
                    throw new Error('Item with this key already exists');
                }
            }
            catch (error) {
                results.push({
                    index,
                    key: item.key,
                    success: false,
                    error: error.message,
                });
            }
        });
        return { results, totalSize };
    }
    /**
     * Delete multiple context items in a single transaction
     */
    batchDelete(sessionId, options) {
        const { keys, keyPattern } = options;
        let totalDeleted = 0;
        if (keys) {
            // Delete by specific keys
            const deleteStmt = this.db.prepare('DELETE FROM context_items WHERE session_id = ? AND key = ?');
            const results = [];
            keys.forEach((key, index) => {
                if (key && key.trim()) {
                    const result = deleteStmt.run(sessionId, key);
                    const deleted = result.changes > 0;
                    results.push({
                        index,
                        key,
                        deleted,
                        count: result.changes,
                    });
                    totalDeleted += result.changes;
                }
                else {
                    results.push({
                        index,
                        key: key || 'undefined',
                        deleted: false,
                        count: 0,
                        error: 'Key cannot be empty',
                    });
                }
            });
            return { results, totalDeleted };
        }
        else if (keyPattern) {
            // Delete by pattern
            const sqlPattern = keyPattern.replace(/\*/g, '%').replace(/\?/g, '_');
            const result = this.db
                .prepare('DELETE FROM context_items WHERE session_id = ? AND key LIKE ?')
                .run(sessionId, sqlPattern);
            totalDeleted = result.changes;
            return { totalDeleted };
        }
        return { totalDeleted: 0 };
    }
    /**
     * Update multiple context items in a single transaction
     */
    batchUpdate(sessionId, updates) {
        const results = [];
        updates.forEach((update, index) => {
            try {
                // Build dynamic UPDATE statement
                const setClauses = [];
                const values = [];
                if (update.value !== undefined) {
                    setClauses.push('value = ?');
                    values.push(update.value);
                    setClauses.push('size = ?');
                    values.push(this.calculateSize(update.value));
                }
                if (update.category !== undefined) {
                    setClauses.push('category = ?');
                    values.push(update.category);
                }
                if (update.priority !== undefined) {
                    setClauses.push('priority = ?');
                    values.push(update.priority);
                }
                if (update.channel !== undefined) {
                    setClauses.push('channel = ?');
                    values.push(update.channel);
                }
                if (setClauses.length === 0) {
                    throw new Error('No updates provided');
                }
                setClauses.push('updated_at = ?');
                values.push((0, timestamps_js_1.ensureSQLiteFormat)(new Date().toISOString()));
                const sql = `
          UPDATE context_items 
          SET ${setClauses.join(', ')}
          WHERE session_id = ? AND key = ?
        `;
                values.push(sessionId, update.key);
                const result = this.db.prepare(sql).run(...values);
                if (result.changes === 0) {
                    throw new Error('Item not found');
                }
                results.push({
                    index,
                    key: update.key,
                    updated: true,
                    fields: Object.keys(update).filter(k => k !== 'key' && update[k] !== undefined),
                });
            }
            catch (error) {
                results.push({
                    index,
                    key: update.key,
                    updated: false,
                    error: error.message,
                });
            }
        });
        return { results };
    }
    /**
     * Get items for dry run operations
     */
    getDryRunItems(sessionId, options) {
        const { keys, keyPattern } = options;
        let items = [];
        if (keys) {
            const stmt = this.db.prepare(`
        SELECT key, value, category, priority, channel 
        FROM context_items 
        WHERE session_id = ? AND key = ?
      `);
            keys.forEach(key => {
                if (key && key.trim()) {
                    const item = stmt.get(sessionId, key);
                    if (item) {
                        items.push({
                            ...item,
                            value: item.value.substring(0, 50) + (item.value.length > 50 ? '...' : ''),
                        });
                    }
                }
            });
        }
        else if (keyPattern) {
            const sqlPattern = keyPattern.replace(/\*/g, '%').replace(/\?/g, '_');
            const stmt = this.db.prepare(`
        SELECT key, value, category, priority, channel 
        FROM context_items 
        WHERE session_id = ? AND key LIKE ?
      `);
            const foundItems = stmt.all(sessionId, sqlPattern);
            items = foundItems.map(item => ({
                ...item,
                value: item.value.substring(0, 50) + (item.value.length > 50 ? '...' : ''),
            }));
        }
        return items;
    }
    // Context Relationships Methods
    createRelationship(params) {
        const { sessionId, sourceKey, targetKey, relationship, metadata } = params;
        // Validate relationship type
        const validTypes = [
            'contains',
            'depends_on',
            'references',
            'implements',
            'extends',
            'related_to',
            'blocks',
            'blocked_by',
            'parent_of',
            'child_of',
            'has_task',
            'documented_in',
            'serves',
            'leads_to',
        ];
        if (!validTypes.includes(relationship)) {
            return { id: '', created: false, error: `Invalid relationship type: ${relationship}` };
        }
        // Check if both items exist
        const sourceExists = this.db
            .prepare('SELECT 1 FROM context_items WHERE session_id = ? AND key = ?')
            .get(sessionId, sourceKey);
        const targetExists = this.db
            .prepare('SELECT 1 FROM context_items WHERE session_id = ? AND key = ?')
            .get(sessionId, targetKey);
        if (!sourceExists || !targetExists) {
            const missingKeys = [];
            if (!sourceExists)
                missingKeys.push(sourceKey);
            if (!targetExists)
                missingKeys.push(targetKey);
            return {
                id: '',
                created: false,
                error: `The following items do not exist: ${missingKeys.join(', ')}`,
            };
        }
        try {
            const relationshipId = require('uuid').v4();
            const metadataStr = metadata ? JSON.stringify(metadata) : null;
            this.db
                .prepare(`INSERT INTO context_relationships (id, session_id, from_key, to_key, relationship_type, metadata)
           VALUES (?, ?, ?, ?, ?, ?)`)
                .run(relationshipId, sessionId, sourceKey, targetKey, relationship, metadataStr);
            return { id: relationshipId, created: true };
        }
        catch (error) {
            if (error.message.includes('UNIQUE constraint failed')) {
                return { id: '', created: false, error: 'Relationship already exists' };
            }
            throw error;
        }
    }
    getRelatedItems(params) {
        const { sessionId, key, relationship, depth = 1, direction = 'both' } = params;
        const result = {
            outgoing: [],
            incoming: [],
        };
        // Get direct relationships
        if (direction === 'outgoing' || direction === 'both') {
            let outgoingSql = `
        SELECT r.*, ci.value, ci.category, ci.priority
        FROM context_relationships r
        JOIN context_items ci ON ci.key = r.to_key AND ci.session_id = r.session_id
        WHERE r.session_id = ? AND r.from_key = ?
      `;
            const outgoingParams = [sessionId, key];
            if (relationship) {
                outgoingSql += ' AND r.relationship_type = ?';
                outgoingParams.push(relationship);
            }
            const outgoingRels = this.db.prepare(outgoingSql).all(...outgoingParams);
            result.outgoing = outgoingRels.map(r => ({
                key: r.to_key,
                value: r.value,
                category: r.category,
                priority: r.priority,
                relationshipType: r.relationship_type,
                relationshipId: r.id,
                metadata: r.metadata ? JSON.parse(r.metadata) : null,
                direction: 'outgoing',
            }));
        }
        if (direction === 'incoming' || direction === 'both') {
            let incomingSql = `
        SELECT r.*, ci.value, ci.category, ci.priority
        FROM context_relationships r
        JOIN context_items ci ON ci.key = r.from_key AND ci.session_id = r.session_id
        WHERE r.session_id = ? AND r.to_key = ?
      `;
            const incomingParams = [sessionId, key];
            if (relationship) {
                incomingSql += ' AND r.relationship_type = ?';
                incomingParams.push(relationship);
            }
            const incomingRels = this.db.prepare(incomingSql).all(...incomingParams);
            result.incoming = incomingRels.map(r => ({
                key: r.from_key,
                value: r.value,
                category: r.category,
                priority: r.priority,
                relationshipType: r.relationship_type,
                relationshipId: r.id,
                metadata: r.metadata ? JSON.parse(r.metadata) : null,
                direction: 'incoming',
            }));
        }
        // Handle depth traversal if depth > 1
        if (depth > 1) {
            const visited = new Set();
            const relationships = [];
            const nodes = new Map();
            // Add the starting node
            const startItem = this.db
                .prepare('SELECT * FROM context_items WHERE session_id = ? AND key = ?')
                .get(sessionId, key);
            if (startItem) {
                nodes.set(key, {
                    id: key,
                    label: startItem.value,
                    type: startItem.category || 'default',
                });
            }
            // Traverse function
            const traverse = (currentKey, currentDepth, path) => {
                if (currentDepth > depth || visited.has(currentKey))
                    return;
                visited.add(currentKey);
                // Get outgoing relationships
                let sql = `
          SELECT r.*, ci.value, ci.category
          FROM context_relationships r
          JOIN context_items ci ON ci.key = r.to_key AND ci.session_id = r.session_id
          WHERE r.session_id = ? AND r.from_key = ?
        `;
                const params = [sessionId, currentKey];
                if (relationship) {
                    sql += ' AND r.relationship_type = ?';
                    params.push(relationship);
                }
                const rels = this.db.prepare(sql).all(...params);
                rels.forEach(rel => {
                    // Add node if not exists
                    if (!nodes.has(rel.to_key)) {
                        nodes.set(rel.to_key, {
                            id: rel.to_key,
                            label: rel.value,
                            type: rel.category || 'default',
                        });
                    }
                    // Add relationship
                    relationships.push({
                        path: [...path, currentKey],
                        from: currentKey,
                        to: rel.to_key,
                        type: rel.relationship_type,
                        metadata: rel.metadata ? JSON.parse(rel.metadata) : null,
                        depth: currentDepth,
                    });
                    // Detect cycles
                    if (path.includes(rel.to_key)) {
                        // Cycle detected, don't traverse deeper
                        return;
                    }
                    // Traverse deeper
                    traverse(rel.to_key, currentDepth + 1, [...path, currentKey]);
                });
                // Also get incoming relationships if direction is 'both'
                if (direction === 'both') {
                    let inSql = `
            SELECT r.*, ci.value, ci.category
            FROM context_relationships r
            JOIN context_items ci ON ci.key = r.from_key AND ci.session_id = r.session_id
            WHERE r.session_id = ? AND r.to_key = ?
          `;
                    const inParams = [sessionId, currentKey];
                    if (relationship) {
                        inSql += ' AND r.relationship_type = ?';
                        inParams.push(relationship);
                    }
                    const inRels = this.db.prepare(inSql).all(...inParams);
                    inRels.forEach(rel => {
                        if (!nodes.has(rel.from_key)) {
                            nodes.set(rel.from_key, {
                                id: rel.from_key,
                                label: rel.value,
                                type: rel.category || 'default',
                            });
                        }
                        relationships.push({
                            path: [...path, currentKey],
                            from: rel.from_key,
                            to: currentKey,
                            type: rel.relationship_type,
                            metadata: rel.metadata ? JSON.parse(rel.metadata) : null,
                            depth: currentDepth,
                        });
                        if (!path.includes(rel.from_key)) {
                            traverse(rel.from_key, currentDepth + 1, [...path, currentKey]);
                        }
                    });
                }
            };
            traverse(key, 1, []);
            // Build graph structure
            result.graph = {
                nodes: Array.from(nodes.values()),
                edges: relationships.map(r => ({
                    from: r.from,
                    to: r.to,
                    type: r.type,
                    label: r.type.replace(/_/g, ' '),
                    metadata: r.metadata,
                })),
                relationships: relationships,
            };
        }
        return result;
    }
    deleteRelationship(params) {
        const { sessionId, sourceKey, targetKey, relationship } = params;
        const result = this.db
            .prepare(`DELETE FROM context_relationships 
         WHERE session_id = ? AND from_key = ? AND to_key = ? AND relationship_type = ?`)
            .run(sessionId, sourceKey, targetKey, relationship);
        return { deleted: result.changes > 0 };
    }
    deleteAllRelationshipsForItem(sessionId, key) {
        const result = this.db
            .prepare(`DELETE FROM context_relationships 
         WHERE session_id = ? AND (from_key = ? OR to_key = ?)`)
            .run(sessionId, key, key);
        return { deletedCount: result.changes };
    }
    getRelationshipStats(sessionId) {
        const totalRelationships = this.db
            .prepare('SELECT COUNT(*) as count FROM context_relationships WHERE session_id = ?')
            .get(sessionId).count;
        const byType = this.db
            .prepare(`SELECT relationship_type AS type, COUNT(*) as count 
         FROM context_relationships 
         WHERE session_id = ? 
         GROUP BY relationship_type
         ORDER BY count DESC`)
            .all(sessionId);
        const mostConnected = this.db
            .prepare(`SELECT key, COUNT(*) as connection_count
         FROM (
           SELECT from_key as key FROM context_relationships WHERE session_id = ?
           UNION ALL
           SELECT to_key as key FROM context_relationships WHERE session_id = ?
         )
         GROUP BY key
         ORDER BY connection_count DESC
         LIMIT 10`)
            .all(sessionId, sessionId);
        const orphanedItems = this.db
            .prepare(`SELECT key, value FROM context_items
         WHERE session_id = ?
         AND key NOT IN (
           SELECT from_key FROM context_relationships WHERE session_id = ?
           UNION
           SELECT to_key FROM context_relationships WHERE session_id = ?
         )
         LIMIT 20`)
            .all(sessionId, sessionId, sessionId);
        return {
            totalRelationships,
            byType,
            mostConnected,
            orphanedItems,
        };
    }
    findCycles(sessionId) {
        const cycles = [];
        const visited = new Set();
        const recursionStack = new Set();
        const detectCycle = (key, path) => {
            visited.add(key);
            recursionStack.add(key);
            const neighbors = this.db
                .prepare('SELECT to_key FROM context_relationships WHERE session_id = ? AND from_key = ?')
                .all(sessionId, key);
            for (const neighbor of neighbors) {
                if (recursionStack.has(neighbor.to_key)) {
                    // Found cycle
                    const cycleStart = path.indexOf(neighbor.to_key);
                    if (cycleStart !== -1) {
                        cycles.push([...path.slice(cycleStart), neighbor.to_key]);
                    }
                    else {
                        // The cycle doesn't include the current path, which means we found the target node
                        // but it's not in our current path. This can happen when the cycle was entered
                        // from a different starting point.
                        cycles.push([neighbor.to_key, key, neighbor.to_key]);
                    }
                }
                else if (!visited.has(neighbor.to_key)) {
                    detectCycle(neighbor.to_key, [...path, neighbor.to_key]);
                }
            }
            recursionStack.delete(key);
        };
        // Get all unique keys that have relationships
        const allKeys = this.db
            .prepare(`SELECT DISTINCT key FROM (
           SELECT from_key as key FROM context_relationships WHERE session_id = ?
           UNION
           SELECT to_key as key FROM context_relationships WHERE session_id = ?
         )`)
            .all(sessionId, sessionId);
        allKeys.forEach(item => {
            if (!visited.has(item.key)) {
                detectCycle(item.key, [item.key]);
            }
        });
        return cycles;
    }
}
exports.ContextRepository = ContextRepository;
