import type Database from "better-sqlite3";
import { z } from "zod";
import { canonicalize, MAX_DATE_TIMESTAMP, MAX_QUERY_CHARS, type SearchHistory } from "../../shared/contract.js";

export const SearchHistoryEntrySchema = z.object({
  query: z.string().trim().min(1).max(200),
  canonical: z.string().min(1).max(MAX_QUERY_CHARS),
  searchedAt: z.number().int().nonnegative().max(MAX_DATE_TIMESTAMP),
});

export const SearchHistorySchema = z.object({
  schemaVersion: z.literal(1),
  entries: z.array(SearchHistoryEntrySchema).max(20),
}).superRefine((history, ctx) => {
  const seen = new Set<string>();
  for (const [index, entry] of history.entries.entries()) {
    if (canonicalize(entry.query) !== entry.canonical || seen.has(entry.canonical)) {
      ctx.addIssue({ code: "custom", path: ["entries", index], message: "invalid history entry" });
    }
    seen.add(entry.canonical);
    if (index > 0 && history.entries[index - 1]!.searchedAt < entry.searchedAt) {
      ctx.addIssue({ code: "custom", path: ["entries", index], message: "history is not newest first" });
    }
  }
}) satisfies z.ZodType<SearchHistory>;

/** Persistent single-user search history. */
export class SearchHistoryRepository {
  constructor(private readonly db: Database.Database) {
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS search_history (
        id          INTEGER PRIMARY KEY CHECK (id = 1),
        updated_at  INTEGER NOT NULL,
        document    TEXT NOT NULL
      )
    `);
  }

  get(): { history: SearchHistory; updatedAt: number } | null {
    const row = this.db.prepare("SELECT updated_at, document FROM search_history WHERE id = 1")
      .get() as { updated_at: number; document: string } | undefined;
    if (!row) return null;
    let value: unknown;
    try { value = JSON.parse(row.document); } catch { return null; }
    const parsed = SearchHistorySchema.safeParse(value);
    return parsed.success ? { history: parsed.data, updatedAt: row.updated_at } : null;
  }

  set(history: SearchHistory, updatedAt = Date.now()): number {
    this.db.prepare(
      `INSERT INTO search_history (id, updated_at, document) VALUES (1, ?, ?)
       ON CONFLICT(id) DO UPDATE SET updated_at = excluded.updated_at, document = excluded.document`,
    ).run(updatedAt, JSON.stringify(history));
    return updatedAt;
  }
}
