"use strict";
// =============================================================================
// Pure Agent - SQLite Session Store
// Ported from Hermes hermes_state.py (simplified for Phase 1)
// =============================================================================
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionStore = void 0;
exports.getSessionStore = getSessionStore;
exports.resetSessionStore = resetSessionStore;
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const constants_1 = require("./constants");
const logger_1 = require("./logger");
const utils_1 = require("./utils");
// ---------------------------------------------------------------------------
// Schema
// ---------------------------------------------------------------------------
const SCHEMA_SQL = `
CREATE TABLE IF NOT EXISTS sessions (
  id TEXT PRIMARY KEY,
  source TEXT NOT NULL DEFAULT 'pure-agent',
  model TEXT NOT NULL,
  system_prompt TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS messages (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  session_id TEXT NOT NULL,
  role TEXT NOT NULL,
  content TEXT,
  tool_calls TEXT,
  tool_call_id TEXT,
  name TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(session_id, created_at);
`;
// FTS5 for full-text search on message content
const FTS_SQL = `
CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
  content,
  content=messages,
  content_rowid=id,
  tokenize='unicode61'
);

CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
  INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content);
END;

CREATE TRIGGER IF NOT EXISTS messages_ad AFTER DELETE ON messages BEGIN
  INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.id, old.content);
END;

CREATE TRIGGER IF NOT EXISTS messages_au AFTER UPDATE ON messages BEGIN
  INSERT INTO messages_fts(messages_fts, rowid, content) VALUES ('delete', old.id, old.content);
  INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content);
END;
`;
// ---------------------------------------------------------------------------
// SessionStore class
// ---------------------------------------------------------------------------
class SessionStore {
    db;
    constructor(dbPath) {
        (0, constants_1.ensureDirectories)();
        const resolvedPath = dbPath || (0, constants_1.getDBPath)();
        const dir = path.dirname(resolvedPath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true });
        }
        this.db = new better_sqlite3_1.default(resolvedPath);
        // Enable WAL mode for better concurrent read performance
        this.db.pragma('journal_mode = WAL');
        this.db.pragma('foreign_keys = ON');
        // Initialize schema
        this.db.exec(SCHEMA_SQL);
        // Try to create FTS (may fail if already exists with different schema)
        try {
            this.db.exec(FTS_SQL);
        }
        catch (err) {
            // FTS table might already exist - that's fine
            if (!err.message?.includes('already exists')) {
                (0, logger_1.getLogger)().warn('Failed to create FTS table:', err.message);
            }
        }
        (0, logger_1.getLogger)().info(`Session store initialized at ${resolvedPath}`);
    }
    // -------------------------------------------------------------------------
    // Session lifecycle
    // -------------------------------------------------------------------------
    createSession(sessionId, source, model) {
        const stmt = this.db.prepare(`
      INSERT INTO sessions (id, source, model) VALUES (?, ?, ?)
    `);
        stmt.run(sessionId, source, model);
        return this.getSession(sessionId);
    }
    getSession(sessionId) {
        const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?');
        const row = stmt.get(sessionId);
        if (!row)
            return null;
        return this._mapSessionRow(row);
    }
    ensureSession(sessionId, source, model) {
        const existing = this.getSession(sessionId);
        if (existing)
            return existing;
        return this.createSession(sessionId, source, model);
    }
    updateSystemPrompt(sessionId, prompt) {
        const stmt = this.db.prepare(`
      UPDATE sessions SET system_prompt = ?, updated_at = datetime('now') WHERE id = ?
    `);
        stmt.run(prompt, sessionId);
    }
    listSessions() {
        const stmt = this.db.prepare(`
      SELECT * FROM sessions ORDER BY updated_at DESC
    `);
        const rows = stmt.all();
        return rows.map(r => this._mapSessionRow(r));
    }
    deleteSession(sessionId) {
        const deleteMsgs = this.db.prepare('DELETE FROM messages WHERE session_id = ?');
        const deleteSession = this.db.prepare('DELETE FROM sessions WHERE id = ?');
        const txn = this.db.transaction(() => {
            deleteMsgs.run(sessionId);
            const result = deleteSession.run(sessionId);
            return result.changes > 0;
        });
        return txn();
    }
    // -------------------------------------------------------------------------
    // Message storage
    // -------------------------------------------------------------------------
    addMessage(sessionId, msg) {
        const toolCallsJson = msg.tool_calls ? JSON.stringify(msg.tool_calls) : null;
        // Serialize content to string (handles multimodal arrays)
        let contentStr;
        if (msg.content === null || msg.content === undefined) {
            contentStr = null;
        }
        else if (typeof msg.content === 'string') {
            contentStr = msg.content;
        }
        else {
            // Array or object (multimodal content blocks) -> JSON string
            contentStr = JSON.stringify(msg.content);
        }
        const stmt = this.db.prepare(`
      INSERT INTO messages (session_id, role, content, tool_calls, tool_call_id, name)
      VALUES (?, ?, ?, ?, ?, ?)
    `);
        const result = stmt.run(sessionId, msg.role, contentStr, toolCallsJson, msg.tool_call_id || null, msg.name || null);
        // Update session timestamp
        this.db.prepare(`
      UPDATE sessions SET updated_at = datetime('now') WHERE id = ?
    `).run(sessionId);
        return result.lastInsertRowid;
    }
    getMessages(sessionId) {
        const stmt = this.db.prepare(`
      SELECT * FROM messages WHERE session_id = ? ORDER BY id ASC
    `);
        const rows = stmt.all(sessionId);
        return rows.map(r => this._mapMessageRow(r));
    }
    getMessagesSince(sessionId, sinceId) {
        const stmt = this.db.prepare(`
      SELECT * FROM messages WHERE session_id = ? AND id > ? ORDER BY id ASC
    `);
        const rows = stmt.all(sessionId, sinceId);
        return rows.map(r => this._mapMessageRow(r));
    }
    replaceMessages(sessionId, messages) {
        const txn = this.db.transaction(() => {
            this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
            for (const msg of messages) {
                this.addMessage(sessionId, msg);
            }
        });
        txn();
    }
    clearMessages(sessionId) {
        this.db.prepare('DELETE FROM messages WHERE session_id = ?').run(sessionId);
    }
    messageCount(sessionId) {
        const row = this.db.prepare('SELECT COUNT(*) as count FROM messages WHERE session_id = ?').get(sessionId);
        return row?.count || 0;
    }
    // -------------------------------------------------------------------------
    // Search (FTS5)
    // -------------------------------------------------------------------------
    searchMessages(query, limit = 20) {
        if (!query || query.trim().length === 0)
            return [];
        // Sanitize FTS5 query: wrap in quotes if needed
        const sanitized = this._sanitizeFtsQuery(query);
        try {
            const stmt = this.db.prepare(`
        SELECT m.session_id, m.role, m.content
        FROM messages_fts fts
        JOIN messages m ON m.id = fts.rowid
        WHERE messages_fts MATCH ?
        ORDER BY rank
        LIMIT ?
      `);
            return stmt.all(sanitized, limit);
        }
        catch {
            // Fallback to LIKE search if FTS query is malformed
            const likeQuery = `%${query.replace(/%/g, '\\%').replace(/_/g, '\\_')}%`;
            const stmt = this.db.prepare(`
        SELECT session_id, role, content
        FROM messages
        WHERE content LIKE ? ESCAPE '\\'
        ORDER BY id DESC
        LIMIT ?
      `);
            return stmt.all(likeQuery, limit);
        }
    }
    // -------------------------------------------------------------------------
    // Meta KV store
    // -------------------------------------------------------------------------
    getMeta(key) {
        // Use a simple meta table if it doesn't exist, create it
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS state_meta (
        key TEXT PRIMARY KEY,
        value TEXT
      )
    `);
        const row = this.db.prepare('SELECT value FROM state_meta WHERE key = ?').get(key);
        return row?.value || null;
    }
    setMeta(key, value) {
        this.db.exec(`
      CREATE TABLE IF NOT EXISTS state_meta (
        key TEXT PRIMARY KEY,
        value TEXT
      )
    `);
        this.db.prepare(`
      INSERT OR REPLACE INTO state_meta (key, value) VALUES (?, ?)
    `).run(key, value);
    }
    // -------------------------------------------------------------------------
    // Maintenance
    // -------------------------------------------------------------------------
    sessionCount() {
        const row = this.db.prepare('SELECT COUNT(*) as count FROM sessions').get();
        return row?.count || 0;
    }
    optimizeFts() {
        try {
            this.db.exec(`INSERT INTO messages_fts(messages_fts) VALUES('optimize')`);
        }
        catch { /* ignore if FTS not available */ }
    }
    vacuum() {
        this.db.exec('VACUUM');
    }
    close() {
        this.db.close();
    }
    // -------------------------------------------------------------------------
    // Internal helpers
    // -------------------------------------------------------------------------
    _mapSessionRow(row) {
        return {
            sessionId: row.id,
            source: row.source,
            model: row.model,
            systemPrompt: row.system_prompt,
            createdAt: row.created_at,
            updatedAt: row.updated_at,
        };
    }
    _mapMessageRow(row) {
        // Deserialize content: JSON array (multimodal) or plain string
        let content = row.content;
        if (typeof content === 'string' && content.startsWith('[')) {
            try {
                content = JSON.parse(content);
            }
            catch { /* keep as string */ }
        }
        const msg = {
            id: String(row.id),
            role: row.role,
            content,
        };
        if (row.tool_calls) {
            msg.tool_calls = (0, utils_1.safeJsonParse)(row.tool_calls, []);
        }
        if (row.tool_call_id) {
            msg.tool_call_id = row.tool_call_id;
        }
        if (row.name) {
            msg.name = row.name;
        }
        return msg;
    }
    _sanitizeFtsQuery(query) {
        // If already quoted, return as-is
        if (query.startsWith('"') && query.endsWith('"'))
            return query;
        // Escape special FTS5 characters
        const cleaned = query
            .replace(/['"]/g, '')
            .replace(/[^\w\s一-鿿　-〿＀-￯.*-]/g, ' ')
            .trim();
        if (!cleaned)
            return '""';
        // If contains wildcards, keep them
        if (cleaned.includes('*'))
            return cleaned;
        // Wrap in quotes for exact matching
        return `"${cleaned}"`;
    }
}
exports.SessionStore = SessionStore;
// ---------------------------------------------------------------------------
// Singleton
// ---------------------------------------------------------------------------
let _instance = null;
function getSessionStore() {
    if (!_instance) {
        _instance = new SessionStore();
    }
    return _instance;
}
function resetSessionStore() {
    if (_instance) {
        _instance.close();
        _instance = null;
    }
}
//# sourceMappingURL=session-store.js.map