/**
 * FTS5 full-text search database — bun:sqlite.
 *
 * Three virtual tables: search_messages, search_tasks, search_btmsg.
 * Provides indexing and unified search across all tables.
 * DB path: ~/.local/share/agor/search.db
 */

import { Database } from "bun:sqlite";
import { homedir } from "os";
import { join } from "path";
import { openDb } from "./db-utils.ts";

// ── Types ────────────────────────────────────────────────────────────────────

export interface SearchResult {
  resultType: string;
  id: string;
  title: string;
  snippet: string;
  score: number;
}

// ── DB path ──────────────────────────────────────────────────────────────────

const DATA_DIR = join(homedir(), ".local", "share", "agor");
const DB_PATH = join(DATA_DIR, "search.db");

// ── SearchDb class ───────────────────────────────────────────────────────────

export class SearchDb {
  private db: Database;

  constructor(dbPath?: string) {
    this.db = openDb(dbPath ?? DB_PATH, { busyTimeout: 2000 });
    this.createTables();
  }

  private createTables(): void {
    this.db.run(`
      CREATE VIRTUAL TABLE IF NOT EXISTS search_messages USING fts5(
        session_id,
        role,
        content,
        timestamp
      )
    `);
    this.db.run(`
      CREATE VIRTUAL TABLE IF NOT EXISTS search_tasks USING fts5(
        task_id,
        title,
        description,
        status,
        assigned_to
      )
    `);
    this.db.run(`
      CREATE VIRTUAL TABLE IF NOT EXISTS search_btmsg USING fts5(
        message_id,
        from_agent,
        to_agent,
        content,
        channel_name
      )
    `);
  }

  /** Index an agent message. */
  indexMessage(sessionId: string, role: string, content: string): void {
    const ts = new Date().toISOString();
    this.db.run(
      "INSERT INTO search_messages (session_id, role, content, timestamp) VALUES (?, ?, ?, ?)",
      [sessionId, role, content, ts],
    );
  }

  /** Index a task. */
  indexTask(taskId: string, title: string, description: string, status: string, assignedTo: string): void {
    this.db.run(
      "INSERT INTO search_tasks (task_id, title, description, status, assigned_to) VALUES (?, ?, ?, ?, ?)",
      [taskId, title, description, status, assignedTo],
    );
  }

  /** Index a btmsg message. */
  indexBtmsg(msgId: string, fromAgent: string, toAgent: string, content: string, channel: string): void {
    this.db.run(
      "INSERT INTO search_btmsg (message_id, from_agent, to_agent, content, channel_name) VALUES (?, ?, ?, ?, ?)",
      [msgId, fromAgent, toAgent, content, channel],
    );
  }

  /** Search across all FTS5 tables. */
  searchAll(query: string, limit = 20): SearchResult[] {
    if (!query.trim()) return [];

    const results: SearchResult[] = [];

    // Search messages
    try {
      const msgRows = this.db
        .prepare(
          `SELECT session_id, role,
                  snippet(search_messages, 2, '<b>', '</b>', '...', 32) as snip,
                  rank
           FROM search_messages
           WHERE search_messages MATCH ?
           ORDER BY rank
           LIMIT ?`,
        )
        .all(query, limit) as Array<{ session_id: string; role: string; snip: string; rank: number }>;

      for (const row of msgRows) {
        results.push({
          resultType: "message",
          id: row.session_id,
          title: row.role,
          snippet: row.snip ?? "",
          score: Math.abs(row.rank ?? 0),
        });
      }
    } catch {
      // FTS5 syntax error — skip messages
    }

    // Search tasks
    try {
      const taskRows = this.db
        .prepare(
          `SELECT task_id, title,
                  snippet(search_tasks, 2, '<b>', '</b>', '...', 32) as snip,
                  rank
           FROM search_tasks
           WHERE search_tasks MATCH ?
           ORDER BY rank
           LIMIT ?`,
        )
        .all(query, limit) as Array<{ task_id: string; title: string; snip: string; rank: number }>;

      for (const row of taskRows) {
        results.push({
          resultType: "task",
          id: row.task_id,
          title: row.title,
          snippet: row.snip ?? "",
          score: Math.abs(row.rank ?? 0),
        });
      }
    } catch {
      // FTS5 syntax error — skip tasks
    }

    // Search btmsg
    try {
      const btmsgRows = this.db
        .prepare(
          `SELECT message_id, from_agent,
                  snippet(search_btmsg, 3, '<b>', '</b>', '...', 32) as snip,
                  rank
           FROM search_btmsg
           WHERE search_btmsg MATCH ?
           ORDER BY rank
           LIMIT ?`,
        )
        .all(query, limit) as Array<{ message_id: string; from_agent: string; snip: string; rank: number }>;

      for (const row of btmsgRows) {
        results.push({
          resultType: "btmsg",
          id: row.message_id,
          title: row.from_agent,
          snippet: row.snip ?? "",
          score: Math.abs(row.rank ?? 0),
        });
      }
    } catch {
      // FTS5 syntax error — skip btmsg
    }

    // Sort by score (lower = more relevant for FTS5 rank)
    results.sort((a, b) => a.score - b.score);
    return results.slice(0, limit);
  }

  /** Drop and recreate all FTS5 tables. */
  rebuildIndex(): void {
    this.db.run("DROP TABLE IF EXISTS search_messages");
    this.db.run("DROP TABLE IF EXISTS search_tasks");
    this.db.run("DROP TABLE IF EXISTS search_btmsg");
    this.createTables();
  }

  close(): void {
    this.db.close();
  }
}
