/**
 * /api/search/autocomplete — typo-tolerant prefix search via Meilisearch.
 *
 * Hits two indexes in parallel and returns a small merged result:
 *   - documents   (title-level matches, used to jump to a doc)
 *   - chunks      (passage-level matches, used for in-doc navigation)
 *
 * Target latency: sub-30ms inside the docker network. Falls back to empty
 * results if Meilisearch is unreachable so the chat / hybrid_search aren't
 * blocked. Auth: none — same as /api/search/hybrid; corpus is public.
 */
import { NextResponse } from "next/server";
import { withRequest } from "@/lib/logger";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

const MEILI_URL = process.env.MEILISEARCH_URL || "http://meilisearch:7700";
const MEILI_KEY = process.env.MEILISEARCH_API_KEY || process.env.MEILI_MASTER_KEY || "";

interface DocHit {
  doc_id: string;
  canonical_title: string;
  collection?: string;
}

interface ChunkHit {
  chunk_pk: number;
  doc_id: string;
  chunk_id: string;
  page: number;
  type: string;
  content_pt?: string;
  content_en?: string;
  ufo_anomaly?: boolean;
}

async function meiliSearch(index: string, q: string, limit: number): Promise<unknown[]> {
  const r = await fetch(`${MEILI_URL}/indexes/${index}/search`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${MEILI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ q, limit, attributesToHighlight: ["canonical_title", "content_pt", "content_en"] }),
    signal: AbortSignal.timeout(2000),
  });
  if (!r.ok) throw new Error(`meili ${r.status}`);
  const data = await r.json();
  return data.hits ?? [];
}

export async function GET(request: Request) {
  const log = withRequest(request);
  const url = new URL(request.url);
  const q = (url.searchParams.get("q") || "").trim();
  const limit = Math.min(Number(url.searchParams.get("limit") || 8), 20);

  if (q.length < 2) {
    return NextResponse.json({ q, documents: [], chunks: [] });
  }
  if (!MEILI_KEY) {
    log.warn({ event: "autocomplete_unconfigured" }, "MEILI key not set");
    return NextResponse.json({ q, documents: [], chunks: [], reason: "meili_not_configured" });
  }

  const t0 = Date.now();
  const [docs, chunks] = await Promise.all([
    meiliSearch("documents", q, Math.min(limit, 5)).catch(() => []),
    meiliSearch("chunks", q, limit).catch(() => []),
  ]) as [DocHit[], ChunkHit[]];

  const dt = Date.now() - t0;
  log.info({ event: "autocomplete", q, docs: docs.length, chunks: chunks.length, dt_ms: dt }, "autocomplete done");

  return NextResponse.json({
    q,
    duration_ms: dt,
    documents: docs.map((d) => ({
      doc_id: d.doc_id,
      title: d.canonical_title,
      collection: d.collection,
      href: `/d/${d.doc_id}`,
    })),
    chunks: chunks.map((c) => ({
      chunk_id: c.chunk_id,
      doc_id: c.doc_id,
      page: c.page,
      type: c.type,
      excerpt: (c.content_pt || c.content_en || "").slice(0, 180),
      ufo_anomaly: !!c.ufo_anomaly,
      href: `/d/${c.doc_id}/p${String(c.page).padStart(3, "0")}#${c.chunk_id}`,
    })),
  });
}
