import type { Hono } from "hono";
import { z } from "zod";
import type { SessionStore } from "../session-store.js";
import * as sessionNames from "../session-names.js";
import { createLogger } from "../logger.js";

const log = createLogger("search-routes");

// ─── Zod Schemas ──────────────────────────────────────────────────

const searchQuerySchema = z.object({
  q: z.string().min(1).max(500),
  limit: z.coerce.number().int().min(1).max(200).optional().default(50),
  includeArchived: z.coerce.boolean().optional().default(false),
});

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

export interface SearchMatch {
  messageIndex: number;
  content: string;
  timestamp?: number;
  role: "user" | "assistant";
}

export interface SearchResult {
  sessionId: string;
  sessionName: string;
  matches: SearchMatch[];
}

export interface SearchResponse {
  results: SearchResult[];
  totalMatches: number;
  query: string;
}

// ─── Helpers ──────────────────────────────────────────────────────

const SNIPPET_RADIUS = 80;

/**
 * Extract a text snippet around the first match of `query` in `text`,
 * with ±SNIPPET_RADIUS characters of context and "…" ellipsis markers.
 */
function createSnippet(text: string, query: string): string {
  const lowerText = text.toLowerCase();
  const lowerQuery = query.toLowerCase();
  const idx = lowerText.indexOf(lowerQuery);
  if (idx === -1) return text.slice(0, SNIPPET_RADIUS * 2);

  const start = Math.max(0, idx - SNIPPET_RADIUS);
  const end = Math.min(text.length, idx + query.length + SNIPPET_RADIUS);

  let snippet = text.slice(start, end);
  if (start > 0) snippet = "…" + snippet;
  if (end < text.length) snippet = snippet + "…";

  return snippet;
}

/**
 * Extract displayable text content from a message object.
 * Handles both string content and structured message formats.
 */
function extractMessageContent(msg: Record<string, unknown>): string {
  if (msg.type === "user_message") {
    return typeof msg.content === "string" ? msg.content : "";
  }
  if (msg.type === "assistant") {
    const message = msg.message as Record<string, unknown> | undefined;
    if (!message) return "";
    const content = message.content;
    if (typeof content === "string") return content;
    if (Array.isArray(content)) {
      return content
        .filter((block: unknown) => {
          const b = block as Record<string, unknown>;
          return b.type === "text" && typeof b.text === "string";
        })
        .map((block: unknown) => (block as Record<string, unknown>).text as string)
        .join("\n");
    }
    return "";
  }
  return "";
}

/**
 * Determine the role of a message for search results display.
 */
function extractRole(msg: Record<string, unknown>): "user" | "assistant" {
  return msg.type === "user_message" ? "user" : "assistant";
}

// ─── Routes ───────────────────────────────────────────────────────

export function registerSearchRoutes(api: Hono, sessionStore: SessionStore): void {
  api.get("/search", async (c) => {
    try {
      const raw = {
        q: c.req.query("q"),
        limit: c.req.query("limit"),
        includeArchived: c.req.query("includeArchived"),
      };
      const parsed = searchQuerySchema.safeParse(raw);
      if (!parsed.success) {
        return c.json({ error: "Invalid query parameters", details: parsed.error.flatten() }, 400);
      }
      const { q, limit, includeArchived } = parsed.data;
      const lowerQuery = q.toLowerCase();

      const allSessions = await sessionStore.loadAll();
      const results: SearchResult[] = [];
      let totalMatches = 0;
      let reachedLimit = false;

      for (const session of allSessions) {
        if (reachedLimit) break;
        if (session.archived && !includeArchived) continue;
        if (!session.messageHistory || session.messageHistory.length === 0) continue;

        const sessionMatches: SearchMatch[] = [];

        for (let i = 0; i < session.messageHistory.length; i++) {
          if (totalMatches >= limit) {
            reachedLimit = true;
            break;
          }

          const msg = session.messageHistory[i] as unknown as Record<string, unknown>;
          const content = extractMessageContent(msg);
          if (!content || !content.toLowerCase().includes(lowerQuery)) continue;

          sessionMatches.push({
            messageIndex: i,
            content: createSnippet(content, q),
            timestamp: typeof msg.timestamp === "number" ? msg.timestamp : undefined,
            role: extractRole(msg),
          });
          totalMatches++;
        }

        if (sessionMatches.length > 0) {
          results.push({
            sessionId: session.id,
            sessionName: sessionNames.getName(session.id) ?? session.id,
            matches: sessionMatches,
          });
        }
      }

      return c.json({ results, totalMatches, query: q } satisfies SearchResponse);
    } catch (err) {
      log.error({ err }, "Failed to search sessions");
      return c.json({ error: "Failed to search sessions" }, 500);
    }
  });
}
