import { existsSync, mkdirSync } from 'node:fs';
import Database from 'better-sqlite3';
import { j as join, d as dirname } from '../shared/ccjk.bQ7Dh1g4.mjs';

class ContextPersistence {
  db;
  dbPath;
  constructor(dbPath) {
    this.dbPath = dbPath || join(
      process.env.HOME || process.env.USERPROFILE || ".",
      ".ccjk",
      "context",
      "contexts.db"
    );
    const dir = dirname(this.dbPath);
    if (!existsSync(dir)) {
      mkdirSync(dir, { recursive: true });
    }
    this.db = new Database(this.dbPath);
    this.db.pragma("journal_mode = WAL");
    this.initSchema();
  }
  /**
   * Initialize database schema
   */
  initSchema() {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS contexts (
        id TEXT PRIMARY KEY,
        project_hash TEXT NOT NULL,
        content TEXT NOT NULL,
        compressed TEXT NOT NULL,
        algorithm TEXT NOT NULL,
        strategy TEXT NOT NULL,
        original_tokens INTEGER NOT NULL,
        compressed_tokens INTEGER NOT NULL,
        compression_ratio REAL NOT NULL,
        metadata TEXT NOT NULL DEFAULT '{}',
        timestamp INTEGER NOT NULL,
        last_accessed INTEGER NOT NULL,
        access_count INTEGER NOT NULL DEFAULT 1
      );

      CREATE INDEX IF NOT EXISTS idx_contexts_project ON contexts(project_hash);
      CREATE INDEX IF NOT EXISTS idx_contexts_timestamp ON contexts(timestamp);
      CREATE INDEX IF NOT EXISTS idx_contexts_last_accessed ON contexts(last_accessed);
      CREATE INDEX IF NOT EXISTS idx_contexts_access_count ON contexts(access_count);

      -- FTS5 virtual table for full-text search
      CREATE VIRTUAL TABLE IF NOT EXISTS contexts_fts USING fts5(
        id UNINDEXED,
        content,
        compressed,
        metadata,
        tokenize = 'porter unicode61'
      );

      -- Triggers to keep FTS5 in sync with main table
      CREATE TRIGGER IF NOT EXISTS contexts_ai AFTER INSERT ON contexts BEGIN
        INSERT INTO contexts_fts(id, content, compressed, metadata)
        VALUES (new.id, new.content, new.compressed, new.metadata);
      END;

      CREATE TRIGGER IF NOT EXISTS contexts_ad AFTER DELETE ON contexts BEGIN
        DELETE FROM contexts_fts WHERE id = old.id;
      END;

      CREATE TRIGGER IF NOT EXISTS contexts_au AFTER UPDATE ON contexts BEGIN
        UPDATE contexts_fts
        SET content = new.content,
            compressed = new.compressed,
            metadata = new.metadata
        WHERE id = old.id;
      END;

      CREATE TABLE IF NOT EXISTS projects (
        hash TEXT PRIMARY KEY,
        path TEXT NOT NULL,
        name TEXT,
        created_at INTEGER NOT NULL,
        updated_at INTEGER NOT NULL,
        context_count INTEGER NOT NULL DEFAULT 0,
        total_tokens INTEGER NOT NULL DEFAULT 0,
        metadata TEXT NOT NULL DEFAULT '{}'
      );

      CREATE INDEX IF NOT EXISTS idx_projects_created ON projects(created_at);
      CREATE INDEX IF NOT EXISTS idx_projects_updated ON projects(updated_at);

      -- Compression metrics table
      CREATE TABLE IF NOT EXISTS compression_metrics (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        project_hash TEXT NOT NULL,
        context_id TEXT NOT NULL,
        original_tokens INTEGER NOT NULL,
        compressed_tokens INTEGER NOT NULL,
        compression_ratio REAL NOT NULL,
        time_taken_ms INTEGER NOT NULL,
        algorithm TEXT NOT NULL,
        strategy TEXT NOT NULL,
        timestamp INTEGER NOT NULL,
        FOREIGN KEY (project_hash) REFERENCES projects(hash) ON DELETE CASCADE
      );

      CREATE INDEX IF NOT EXISTS idx_metrics_project ON compression_metrics(project_hash);
      CREATE INDEX IF NOT EXISTS idx_metrics_timestamp ON compression_metrics(timestamp);
      CREATE INDEX IF NOT EXISTS idx_metrics_context ON compression_metrics(context_id);

      -- Composite indexes for hot/warm/cold queries
      CREATE INDEX IF NOT EXISTS idx_contexts_hot ON contexts(project_hash, last_accessed DESC, access_count DESC);
      CREATE INDEX IF NOT EXISTS idx_contexts_warm ON contexts(project_hash, timestamp DESC) WHERE access_count > 1;
      CREATE INDEX IF NOT EXISTS idx_contexts_cold ON contexts(project_hash, timestamp ASC) WHERE access_count = 1;
    `);
  }
  /**
   * Save a compressed context
   */
  saveContext(context, projectHash, originalContent, timeTakenMs) {
    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO contexts (
        id, project_hash, content, compressed, algorithm, strategy,
        original_tokens, compressed_tokens, compression_ratio,
        metadata, timestamp, last_accessed, access_count
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    const now = Date.now();
    const existingContext = this.getContext(context.id);
    const accessCount = existingContext ? existingContext.accessCount + 1 : 1;
    stmt.run(
      context.id,
      projectHash,
      originalContent || "",
      // Store original content if provided
      context.compressed,
      context.algorithm,
      context.strategy,
      context.originalTokens,
      context.compressedTokens,
      context.compressionRatio,
      JSON.stringify(context.metadata || {}),
      context.compressedAt || now,
      now,
      accessCount
    );
    if (timeTakenMs !== void 0) {
      this.saveCompressionMetric({
        projectHash,
        contextId: context.id,
        originalTokens: context.originalTokens,
        compressedTokens: context.compressedTokens,
        compressionRatio: context.compressionRatio,
        timeTakenMs,
        algorithm: context.algorithm,
        strategy: context.strategy,
        timestamp: now
      });
    }
    this.updateProjectStats(projectHash);
  }
  /**
   * Get context by ID
   */
  getContext(contextId) {
    const stmt = this.db.prepare(`
      SELECT * FROM contexts WHERE id = ?
    `);
    const row = stmt.get(contextId);
    if (!row)
      return null;
    this.updateLastAccessed(contextId);
    return this.rowToContext(row);
  }
  /**
   * Get contexts for a project
   */
  getProjectContexts(projectHash, options) {
    let query = "SELECT * FROM contexts WHERE project_hash = ?";
    const params = [projectHash];
    if (options?.startTime) {
      query += " AND timestamp >= ?";
      params.push(options.startTime);
    }
    if (options?.endTime) {
      query += " AND timestamp <= ?";
      params.push(options.endTime);
    }
    const sortBy = options?.sortBy || "timestamp";
    const sortOrder = options?.sortOrder || "desc";
    query += ` ORDER BY ${sortBy} ${sortOrder.toUpperCase()}`;
    if (options?.limit) {
      query += " LIMIT ?";
      params.push(options.limit);
    }
    const stmt = this.db.prepare(query);
    const rows = stmt.all(...params);
    return rows.map((row) => this.rowToContext(row));
  }
  /**
   * Query contexts with filters
   */
  queryContexts(options) {
    let query = "SELECT * FROM contexts WHERE 1=1";
    const params = [];
    if (options?.projectHash) {
      query += " AND project_hash = ?";
      params.push(options.projectHash);
    }
    if (options?.startTime) {
      query += " AND timestamp >= ?";
      params.push(options.startTime);
    }
    if (options?.endTime) {
      query += " AND timestamp <= ?";
      params.push(options.endTime);
    }
    const sortBy = options?.sortBy || "timestamp";
    const sortOrder = options?.sortOrder || "desc";
    query += ` ORDER BY ${sortBy} ${sortOrder.toUpperCase()}`;
    if (options?.limit) {
      query += " LIMIT ?";
      params.push(options.limit);
    }
    const stmt = this.db.prepare(query);
    const rows = stmt.all(...params);
    return rows.map((row) => this.rowToContext(row));
  }
  /**
   * Search contexts using FTS5 full-text search
   * @param searchQuery - Search query (supports FTS5 syntax: AND, OR, NOT, NEAR, "phrases")
   * @param options - Query options
   * @returns Search results with ranking
   */
  searchContexts(searchQuery, options) {
    if (!searchQuery || searchQuery.trim().length === 0) {
      return [];
    }
    let query = `
      SELECT
        c.*,
        bm25(contexts_fts) as rank,
        snippet(contexts_fts, 1, '<mark>', '</mark>', '...', 32) as snippet
      FROM contexts c
      INNER JOIN contexts_fts ON contexts_fts.id = c.id
      WHERE contexts_fts MATCH ?
    `;
    const params = [searchQuery];
    if (options?.projectHash) {
      query += " AND c.project_hash = ?";
      params.push(options.projectHash);
    }
    if (options?.startTime) {
      query += " AND c.timestamp >= ?";
      params.push(options.startTime);
    }
    if (options?.endTime) {
      query += " AND c.timestamp <= ?";
      params.push(options.endTime);
    }
    const sortBy = options?.sortBy || "relevance";
    const sortOrder = options?.sortOrder || "desc";
    if (sortBy === "relevance") {
      query += ` ORDER BY rank ${sortOrder === "asc" ? "DESC" : "ASC"}`;
    } else {
      query += ` ORDER BY c.${sortBy} ${sortOrder.toUpperCase()}`;
    }
    if (options?.limit) {
      query += " LIMIT ?";
      params.push(options.limit);
    }
    const stmt = this.db.prepare(query);
    const rows = stmt.all(...params);
    return rows.map((row) => ({
      ...this.rowToContext(row),
      rank: row.rank,
      snippet: row.snippet
    }));
  }
  /**
   * Get hot contexts (frequently accessed, recently used)
   * @param projectHash - Project hash
   * @param limit - Maximum number of results
   */
  getHotContexts(projectHash, limit = 10) {
    const stmt = this.db.prepare(`
      SELECT * FROM contexts
      WHERE project_hash = ?
      ORDER BY last_accessed DESC, access_count DESC
      LIMIT ?
    `);
    const rows = stmt.all(projectHash, limit);
    return rows.map((row) => this.rowToContext(row));
  }
  /**
   * Get warm contexts (accessed multiple times but not recently)
   * @param projectHash - Project hash
   * @param limit - Maximum number of results
   */
  getWarmContexts(projectHash, limit = 10) {
    const stmt = this.db.prepare(`
      SELECT * FROM contexts
      WHERE project_hash = ? AND access_count > 1
      ORDER BY timestamp DESC
      LIMIT ?
    `);
    const rows = stmt.all(projectHash, limit);
    return rows.map((row) => this.rowToContext(row));
  }
  /**
   * Get cold contexts (rarely accessed, older)
   * @param projectHash - Project hash
   * @param limit - Maximum number of results
   */
  getColdContexts(projectHash, limit = 10) {
    const stmt = this.db.prepare(`
      SELECT * FROM contexts
      WHERE project_hash = ? AND access_count = 1
      ORDER BY timestamp ASC
      LIMIT ?
    `);
    const rows = stmt.all(projectHash, limit);
    return rows.map((row) => this.rowToContext(row));
  }
  /**
   * Delete context by ID
   */
  deleteContext(contextId) {
    const context = this.getContext(contextId);
    if (!context)
      return false;
    const stmt = this.db.prepare("DELETE FROM contexts WHERE id = ?");
    const result = stmt.run(contextId);
    if (context.projectHash) {
      this.updateProjectStats(context.projectHash);
    }
    return result.changes > 0;
  }
  /**
   * Delete all contexts for a project
   */
  deleteProjectContexts(projectHash) {
    const stmt = this.db.prepare("DELETE FROM contexts WHERE project_hash = ?");
    const result = stmt.run(projectHash);
    this.updateProjectStats(projectHash);
    return result.changes;
  }
  /**
   * Register or update project
   */
  registerProject(projectHash, projectPath, projectName) {
    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO projects (hash, path, name, created_at, updated_at, context_count, total_tokens, metadata)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `);
    const now = Date.now();
    const existing = this.getProject(projectHash);
    const createdAt = existing?.created_at || now;
    stmt.run(
      projectHash,
      projectPath,
      projectName || null,
      createdAt,
      now,
      existing?.context_count || 0,
      existing?.total_tokens || 0,
      JSON.stringify(existing?.metadata || {})
    );
  }
  /**
   * Get project info
   */
  getProject(projectHash) {
    const stmt = this.db.prepare("SELECT * FROM projects WHERE hash = ?");
    return stmt.get(projectHash);
  }
  /**
   * List all projects
   */
  listProjects() {
    const stmt = this.db.prepare("SELECT * FROM projects ORDER BY updated_at DESC");
    return stmt.all();
  }
  /**
   * Get context statistics
   */
  getStats(projectHash) {
    let query = "SELECT COUNT(*) as count, SUM(original_tokens) as orig, SUM(compressed_tokens) as comp, MIN(timestamp) as oldest, MAX(timestamp) as newest FROM contexts";
    const params = [];
    if (projectHash) {
      query += " WHERE project_hash = ?";
      params.push(projectHash);
    }
    const stmt = this.db.prepare(query);
    const row = stmt.get(...params);
    const totalProjects = projectHash ? 1 : this.db.prepare("SELECT COUNT(*) as count FROM projects").get().count;
    const avgRatio = row.orig > 0 ? (row.orig - row.comp) / row.orig : 0;
    return {
      totalContexts: row.count || 0,
      totalProjects,
      totalOriginalTokens: row.orig || 0,
      totalCompressedTokens: row.comp || 0,
      averageCompressionRatio: avgRatio,
      totalSize: this.getDatabaseSize(),
      oldestContext: row.oldest || void 0,
      newestContext: row.newest || void 0
    };
  }
  /**
   * Clean up old contexts
   */
  cleanup(maxAge) {
    const cutoff = Date.now() - maxAge;
    const stmt = this.db.prepare("DELETE FROM contexts WHERE timestamp < ?");
    const result = stmt.run(cutoff);
    const projects = this.listProjects();
    for (const project of projects) {
      this.updateProjectStats(project.hash);
    }
    return result.changes;
  }
  /**
   * Vacuum database to reclaim space
   */
  vacuum() {
    this.db.exec("VACUUM");
  }
  /**
   * Close database
   */
  close() {
    this.db.close();
  }
  /**
   * Get database size in bytes
   */
  getDatabaseSize() {
    try {
      const fs = require("node:fs");
      const stats = fs.statSync(this.dbPath);
      return stats.size;
    } catch {
      return 0;
    }
  }
  /**
   * Export contexts to JSON
   */
  exportContexts(projectHash) {
    return this.queryContexts({ projectHash });
  }
  /**
   * Import contexts from JSON
   */
  importContexts(contexts) {
    let imported = 0;
    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO contexts (
        id, project_hash, content, compressed, algorithm, strategy,
        original_tokens, compressed_tokens, compression_ratio,
        metadata, timestamp, last_accessed, access_count
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    for (const context of contexts) {
      try {
        stmt.run(
          context.id,
          context.projectHash,
          context.content,
          context.compressed,
          context.algorithm,
          context.strategy,
          context.originalTokens,
          context.compressedTokens,
          context.compressionRatio,
          context.metadata,
          context.timestamp,
          context.lastAccessed,
          context.accessCount
        );
        imported++;
      } catch {
      }
    }
    return imported;
  }
  /**
   * Update last accessed time
   */
  updateLastAccessed(contextId) {
    const stmt = this.db.prepare(`
      UPDATE contexts
      SET last_accessed = ?, access_count = access_count + 1
      WHERE id = ?
    `);
    stmt.run(Date.now(), contextId);
  }
  /**
   * Update project statistics
   */
  updateProjectStats(projectHash) {
    const stmt = this.db.prepare(`
      UPDATE projects
      SET
        context_count = (SELECT COUNT(*) FROM contexts WHERE project_hash = ?),
        total_tokens = (SELECT COALESCE(SUM(original_tokens), 0) FROM contexts WHERE project_hash = ?),
        updated_at = ?
      WHERE hash = ?
    `);
    stmt.run(projectHash, projectHash, Date.now(), projectHash);
  }
  /**
   * Convert database row to PersistedContext
   */
  rowToContext(row) {
    return {
      id: row.id,
      projectHash: row.project_hash,
      content: row.content,
      compressed: row.compressed,
      algorithm: row.algorithm,
      strategy: row.strategy,
      originalTokens: row.original_tokens,
      compressedTokens: row.compressed_tokens,
      compressionRatio: row.compression_ratio,
      metadata: row.metadata,
      timestamp: row.timestamp,
      lastAccessed: row.last_accessed,
      accessCount: row.access_count
    };
  }
  /**
   * Save compression metric
   */
  saveCompressionMetric(metric) {
    const stmt = this.db.prepare(`
      INSERT INTO compression_metrics (
        project_hash, context_id, original_tokens, compressed_tokens,
        compression_ratio, time_taken_ms, algorithm, strategy, timestamp
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    stmt.run(
      metric.projectHash,
      metric.contextId,
      metric.originalTokens,
      metric.compressedTokens,
      metric.compressionRatio,
      metric.timeTakenMs,
      metric.algorithm,
      metric.strategy,
      metric.timestamp
    );
  }
  /**
   * Get compression metrics statistics
   */
  getCompressionMetricsStats(projectHash, options) {
    const now = Date.now();
    const sessionStart = now - 24 * 60 * 60 * 1e3;
    const weekStart = now - 7 * 24 * 60 * 60 * 1e3;
    const monthStart = now - 30 * 24 * 60 * 60 * 1e3;
    let baseQuery = "SELECT * FROM compression_metrics WHERE 1=1";
    const params = [];
    if (projectHash) {
      baseQuery += " AND project_hash = ?";
      params.push(projectHash);
    }
    if (options?.startTime) {
      baseQuery += " AND timestamp >= ?";
      params.push(options.startTime);
    }
    if (options?.endTime) {
      baseQuery += " AND timestamp <= ?";
      params.push(options.endTime);
    }
    const stmt = this.db.prepare(baseQuery);
    const metrics = stmt.all(...params);
    const totalCompressions = metrics.length;
    const totalOriginalTokens = metrics.reduce((sum, m) => sum + m.original_tokens, 0);
    const totalCompressedTokens = metrics.reduce((sum, m) => sum + m.compressed_tokens, 0);
    const totalTokensSaved = totalOriginalTokens - totalCompressedTokens;
    const averageCompressionRatio = totalCompressions > 0 ? metrics.reduce((sum, m) => sum + m.compression_ratio, 0) / totalCompressions : 0;
    const averageTimeTakenMs = totalCompressions > 0 ? metrics.reduce((sum, m) => sum + m.time_taken_ms, 0) / totalCompressions : 0;
    const COST_PER_1K_TOKENS = 0.015;
    const estimatedCostSavings = totalTokensSaved / 1e3 * COST_PER_1K_TOKENS;
    const sessionMetrics = metrics.filter((m) => m.timestamp >= sessionStart);
    const sessionTokensSaved = sessionMetrics.reduce(
      (sum, m) => sum + (m.original_tokens - m.compressed_tokens),
      0
    );
    const weeklyMetrics = metrics.filter((m) => m.timestamp >= weekStart);
    const weeklyTokensSaved = weeklyMetrics.reduce(
      (sum, m) => sum + (m.original_tokens - m.compressed_tokens),
      0
    );
    const monthlyMetrics = metrics.filter((m) => m.timestamp >= monthStart);
    const monthlyTokensSaved = monthlyMetrics.reduce(
      (sum, m) => sum + (m.original_tokens - m.compressed_tokens),
      0
    );
    return {
      totalCompressions,
      totalOriginalTokens,
      totalCompressedTokens,
      totalTokensSaved,
      averageCompressionRatio,
      averageTimeTakenMs,
      estimatedCostSavings,
      sessionStats: {
        compressions: sessionMetrics.length,
        tokensSaved: sessionTokensSaved,
        costSavings: sessionTokensSaved / 1e3 * COST_PER_1K_TOKENS
      },
      weeklyStats: {
        compressions: weeklyMetrics.length,
        tokensSaved: weeklyTokensSaved,
        costSavings: weeklyTokensSaved / 1e3 * COST_PER_1K_TOKENS
      },
      monthlyStats: {
        compressions: monthlyMetrics.length,
        tokensSaved: monthlyTokensSaved,
        costSavings: monthlyTokensSaved / 1e3 * COST_PER_1K_TOKENS
      }
    };
  }
  /**
   * Get recent compression metrics
   */
  getRecentCompressionMetrics(projectHash, limit = 10) {
    let query = "SELECT * FROM compression_metrics";
    const params = [];
    if (projectHash) {
      query += " WHERE project_hash = ?";
      params.push(projectHash);
    }
    query += " ORDER BY timestamp DESC LIMIT ?";
    params.push(limit);
    const stmt = this.db.prepare(query);
    const rows = stmt.all(...params);
    return rows.map((row) => ({
      id: row.id,
      projectHash: row.project_hash,
      contextId: row.context_id,
      originalTokens: row.original_tokens,
      compressedTokens: row.compressed_tokens,
      compressionRatio: row.compression_ratio,
      timeTakenMs: row.time_taken_ms,
      algorithm: row.algorithm,
      strategy: row.strategy,
      timestamp: row.timestamp
    }));
  }
  /**
   * Query compression metrics with optional filters
   */
  getCompressionMetrics(projectHash, options) {
    let query = "SELECT * FROM compression_metrics WHERE 1=1";
    const params = [];
    if (projectHash) {
      query += " AND project_hash = ?";
      params.push(projectHash);
    }
    if (options?.startTime) {
      query += " AND timestamp >= ?";
      params.push(options.startTime);
    }
    if (options?.endTime) {
      query += " AND timestamp <= ?";
      params.push(options.endTime);
    }
    query += ` ORDER BY timestamp ${(options?.sortOrder || "desc").toUpperCase()}`;
    if (options?.limit) {
      query += " LIMIT ?";
      params.push(options.limit);
    }
    const stmt = this.db.prepare(query);
    const rows = stmt.all(...params);
    return rows.map((row) => ({
      id: row.id,
      projectHash: row.project_hash,
      contextId: row.context_id,
      originalTokens: row.original_tokens,
      compressedTokens: row.compressed_tokens,
      compressionRatio: row.compression_ratio,
      timeTakenMs: row.time_taken_ms,
      algorithm: row.algorithm,
      strategy: row.strategy,
      timestamp: row.timestamp
    }));
  }
  /**
   * Clean up old compression metrics
   */
  cleanupCompressionMetrics(maxAge) {
    const cutoff = Date.now() - maxAge;
    const stmt = this.db.prepare("DELETE FROM compression_metrics WHERE timestamp < ?");
    const result = stmt.run(cutoff);
    return result.changes;
  }
}
let globalContextPersistence = null;
function getContextPersistence(dbPath) {
  if (!globalContextPersistence) {
    globalContextPersistence = new ContextPersistence(dbPath);
  }
  return globalContextPersistence;
}

export { ContextPersistence, getContextPersistence };
