import Database from "better-sqlite3";
import crypto from "node:crypto";
import { mkdirSync } from "node:fs";
import path from "node:path";
import os from "node:os";
let db = null;
let _idCounter = 0;
let _idLastTs = 0;
/** ULID-like server ID: time-sortable (monotonic within same ms), globally unique. */
export function generateServerId() {
    const now = Date.now();
    if (now === _idLastTs) {
        _idCounter += 1;
    }
    else {
        _idLastTs = now;
        _idCounter = 0;
    }
    const ts = now.toString(36).padStart(12, "0");
    const seq = _idCounter.toString(36).padStart(4, "0");
    const rand = crypto.randomBytes(8).toString("hex");
    return `msg_${ts}_${seq}_${rand}`;
}
const SCHEMA = `
CREATE TABLE IF NOT EXISTS sessions (
  session_key   TEXT PRIMARY KEY,
  device_id     TEXT NOT NULL,
  title         TEXT NOT NULL DEFAULT '',
  created_at    TEXT NOT NULL DEFAULT (datetime('now')),
  last_active_at TEXT NOT NULL DEFAULT (datetime('now')),
  message_count INTEGER NOT NULL DEFAULT 0,
  latest_msg_id TEXT
);

CREATE INDEX IF NOT EXISTS idx_sessions_device ON sessions(device_id);

CREATE TABLE IF NOT EXISTS messages (
  server_id            TEXT PRIMARY KEY,
  session_key          TEXT NOT NULL,
  local_id             TEXT,
  sender_type          TEXT NOT NULL CHECK(sender_type IN ('user','assistant')),
  sender_name          TEXT NOT NULL DEFAULT '',
  content              TEXT NOT NULL DEFAULT '',
  timestamp_ms         INTEGER NOT NULL,
  seq                  INTEGER NOT NULL DEFAULT 0,
  status               TEXT NOT NULL DEFAULT 'sent',
  is_from_current_user INTEGER NOT NULL DEFAULT 0,
  message_json         TEXT NOT NULL DEFAULT '{}',
  run_id               TEXT,
  created_at           TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_key, server_id);
CREATE INDEX IF NOT EXISTS idx_messages_session_seq ON messages(session_key, seq);

CREATE TABLE IF NOT EXISTS sync_state (
  device_id     TEXT PRIMARY KEY,
  last_sync_seq INTEGER NOT NULL DEFAULT 0,
  updated_at    TEXT NOT NULL DEFAULT (datetime('now'))
);
`;
const PRAGMAS = `
PRAGMA journal_mode=WAL;
PRAGMA foreign_keys=ON;
`;
/* ---- prepared statements (lazy) ---- */
let _insertMessage = null;
let _selectMessagesAfter = null;
let _selectLatestServerId = null;
let _selectLatestMessage = null;
let _incrementSessionCount = null;
let _selectSession = null;
let _deleteSession = null;
let _deleteSessionMessages = null;
let _selectSyncState = null;
let _upsertSyncState = null;
let _selectMessagesBefore = null;
let _selectSessionsByDevice = null;
function stmt(sql) {
    if (!db)
        throw new Error("DB not initialized");
    return db.prepare(sql);
}
function getInsertMessage() {
    if (!_insertMessage) {
        _insertMessage = stmt(`
      INSERT INTO messages (server_id, session_key, local_id, sender_type, sender_name,
        content, timestamp_ms, seq, status, is_from_current_user, message_json, run_id)
      VALUES (@server_id, @session_key, @local_id, @sender_type, @sender_name,
        @content, @timestamp_ms, @seq, @status, @is_from_current_user, @message_json, @run_id)
    `);
    }
    return _insertMessage;
}
function getSelectMessagesAfter() {
    if (!_selectMessagesAfter) {
        _selectMessagesAfter = stmt(`
      SELECT message_json FROM messages
      WHERE session_key = @session_key AND server_id > @after
      ORDER BY server_id
      LIMIT @limit
    `);
    }
    return _selectMessagesAfter;
}
function getSelectLatestServerId() {
    if (!_selectLatestServerId) {
        _selectLatestServerId = stmt(`
      SELECT server_id FROM messages
      WHERE session_key = @session_key
      ORDER BY server_id DESC
      LIMIT 1
    `);
    }
    return _selectLatestServerId;
}
function getSelectLatestMessage() {
    if (!_selectLatestMessage) {
        _selectLatestMessage = stmt(`
      SELECT message_json FROM messages
      WHERE session_key = @session_key
      ORDER BY server_id DESC
      LIMIT 1
    `);
    }
    return _selectLatestMessage;
}
function getSelectMessagesBefore() {
    if (!_selectMessagesBefore) {
        _selectMessagesBefore = stmt(`
      SELECT message_json FROM messages
      WHERE session_key = @session_key AND (@before = '' OR server_id < @before)
      ORDER BY server_id DESC
      LIMIT @limit
    `);
    }
    return _selectMessagesBefore;
}
let _createSessionIfNotExists = null;
let _updateSessionActivity = null;
function getCreateSessionIfNotExists() {
    if (!_createSessionIfNotExists) {
        _createSessionIfNotExists = stmt(`
      INSERT OR IGNORE INTO sessions (session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id)
      VALUES (@session_key, @device_id, COALESCE(@title, ''), COALESCE(@created_at, datetime('now')), datetime('now'), 0, @latest_msg_id)
    `);
    }
    return _createSessionIfNotExists;
}
function getUpdateSessionActivity() {
    if (!_updateSessionActivity) {
        _updateSessionActivity = stmt(`
      UPDATE sessions SET
        last_active_at = datetime('now'),
        latest_msg_id = @latest_msg_id,
        device_id = CASE WHEN device_id = '' OR device_id IS NULL THEN @device_id ELSE device_id END
      WHERE session_key = @session_key
    `);
    }
    return _updateSessionActivity;
}
function getIncrementSessionCount() {
    if (!_incrementSessionCount) {
        _incrementSessionCount = stmt(`
      UPDATE sessions SET message_count = message_count + 1, last_active_at = datetime('now')
      WHERE session_key = @session_key
    `);
    }
    return _incrementSessionCount;
}
function getSelectSession() {
    if (!_selectSession) {
        _selectSession = stmt(`SELECT * FROM sessions WHERE session_key = @session_key`);
    }
    return _selectSession;
}
function getDeleteSession() {
    if (!_deleteSession) {
        _deleteSession = stmt(`DELETE FROM sessions WHERE session_key = @session_key`);
    }
    return _deleteSession;
}
function getSelectSessionsByDevice() {
    if (!_selectSessionsByDevice) {
        _selectSessionsByDevice = stmt(`
      SELECT session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id
      FROM sessions WHERE device_id = @device_id ORDER BY last_active_at DESC
    `);
    }
    return _selectSessionsByDevice;
}
function getDeleteSessionMessages() {
    if (!_deleteSessionMessages) {
        _deleteSessionMessages = stmt(`DELETE FROM messages WHERE session_key = @session_key`);
    }
    return _deleteSessionMessages;
}
function getSelectSyncState() {
    if (!_selectSyncState) {
        _selectSyncState = stmt(`SELECT * FROM sync_state WHERE device_id = @device_id`);
    }
    return _selectSyncState;
}
function getUpsertSyncState() {
    if (!_upsertSyncState) {
        _upsertSyncState = stmt(`
      INSERT OR REPLACE INTO sync_state (device_id, last_sync_seq, updated_at)
      VALUES (@device_id, @last_sync_seq, datetime('now'))
    `);
    }
    return _upsertSyncState;
}
export function resolveSyncDbPath() {
    return path.join(os.homedir(), ".openclaw", "friday-next", "sync.db");
}
export function initDatabase(dbPath) {
    if (db)
        return;
    const resolved = dbPath ?? resolveSyncDbPath();
    const dir = path.dirname(resolved);
    mkdirSync(dir, { recursive: true });
    db = new Database(resolved);
    db.pragma("journal_mode = WAL");
    db.pragma("foreign_keys = ON");
    db.exec(SCHEMA);
}
/** Test-only: initialize with an in-memory database. */
export function initMemoryDatabase() {
    closeDatabase();
    db = new Database(":memory:");
    db.pragma("foreign_keys = ON");
    db.exec(SCHEMA);
}
export function getDatabase() {
    if (!db)
        throw new Error("DB not initialized — call initDatabase() first");
    return db;
}
export function closeDatabase() {
    if (db) {
        db.close();
        db = null;
        _insertMessage = null;
        _selectMessagesAfter = null;
        _selectLatestServerId = null;
        _selectLatestMessage = null;
        _selectMessagesBefore = null;
        _createSessionIfNotExists = null;
        _updateSessionActivity = null;
        _incrementSessionCount = null;
        _selectSession = null;
        _deleteSession = null;
        _deleteSessionMessages = null;
        _selectSessionsByDevice = null;
        _selectAllSessions = null;
        _selectSyncState = null;
        _findMessageByRunAndSender = null;
        _upsertSyncState = null;
    }
}
export function insertMessage(params) {
    getInsertMessage().run({
        server_id: params.serverId,
        session_key: params.sessionKey,
        local_id: params.localId ?? null,
        sender_type: params.senderType,
        sender_name: params.senderName,
        content: params.content,
        timestamp_ms: params.timestampMs,
        seq: params.seq,
        status: params.status,
        is_from_current_user: params.isFromCurrentUser ? 1 : 0,
        message_json: params.messageJson,
        run_id: params.runId ?? null,
    });
    // auto-create/update session record
    getCreateSessionIfNotExists().run({
        session_key: params.sessionKey,
        device_id: params.deviceId ?? "",
        title: "",
        created_at: null,
        latest_msg_id: params.serverId,
    });
    getUpdateSessionActivity().run({
        session_key: params.sessionKey,
        device_id: params.deviceId ?? "",
        latest_msg_id: params.serverId,
    });
    getIncrementSessionCount().run({ session_key: params.sessionKey });
}
export function getMessagesAfterCursor(sessionKey, afterServerId, limit) {
    const rows = getSelectMessagesAfter().all({
        session_key: sessionKey,
        after: afterServerId,
        limit,
    });
    return rows.map((r) => r.message_json);
}
export function getLatestServerId(sessionKey) {
    const row = getSelectLatestServerId().get({ session_key: sessionKey });
    return row?.server_id ?? null;
}
export function getMessagesBeforeCursor(sessionKey, beforeServerId, limit) {
    const rows = getSelectMessagesBefore().all({
        session_key: sessionKey,
        before: beforeServerId,
        limit,
    });
    // ORDER BY server_id DESC, reverse to chronological
    return rows.map((r) => r.message_json).reverse();
}
export function getLastMessageJson(sessionKey) {
    const row = getSelectLatestMessage().get({ session_key: sessionKey });
    return row?.message_json ?? null;
}
/* ---- sync state ---- */
export function getLastSyncSeq(deviceId) {
    const row = getSelectSyncState().get({ device_id: deviceId.toUpperCase() });
    return row?.last_sync_seq ?? 0;
}
export function setLastSyncSeq(deviceId, seq) {
    getUpsertSyncState().run({ device_id: deviceId.toUpperCase(), last_sync_seq: seq });
}
/* ---- session management ---- */
export function sessionExists(sessionKey) {
    const row = getSelectSession().get({ session_key: sessionKey });
    return row !== undefined;
}
export function getSessionMessageCount(sessionKey) {
    const row = getSelectSession().get({ session_key: sessionKey });
    return row?.message_count ?? 0;
}
let _selectAllSessions = null;
function getSelectAllSessions() {
    if (!_selectAllSessions) {
        _selectAllSessions = stmt(`
      SELECT session_key, device_id, title, created_at, last_active_at, message_count, latest_msg_id
      FROM sessions ORDER BY last_active_at DESC
    `);
    }
    return _selectAllSessions;
}
export function listSessionsByDevice(deviceId) {
    return getSelectSessionsByDevice().all({
        device_id: deviceId.trim().toUpperCase(),
    });
}
export function listAllSessions() {
    return getSelectAllSessions().all();
}
let _findMessageByRunAndSender = null;
export function findServerIdByRunAndSender(runId, senderType) {
    if (!_findMessageByRunAndSender) {
        _findMessageByRunAndSender = stmt(`
      SELECT server_id FROM messages WHERE run_id = @run_id AND sender_type = @sender_type LIMIT 1
    `);
    }
    const row = _findMessageByRunAndSender.get({ run_id: runId, sender_type: senderType });
    return row?.server_id ?? null;
}
export function deleteSessionFromDb(sessionKey) {
    getDeleteSessionMessages().run({ session_key: sessionKey });
    getDeleteSession().run({ session_key: sessionKey });
}
