// Embed module admin route — backfill existing entries (issue #7 / mira-dissolve T2.4 缺口).
//
// 背景：embed 原本只有「寫入即嵌」（embedOnWrite），對「開 Vectorize binding 之前就寫入」或
// 「embed-on-write 當時漏掉」的既有 entry 沒有回填路徑 → is_embedded=0 且永遠補不回。
// 本 route = 把 embed.ts 既有 embedText/VECTORIZE.upsert 邏輯包成可重複呼叫的批次補嵌端點。
//
// 鐵律對齊：embedding 屬 base optional 模組；模組未開（無 VECTORIZE+AI）→ 誠實回 409，不假裝（mindset §7）。
// base 對內容語意無知：只認通用 metadata.embed===true 旗標，不知 triplet/wiki（解耦）。
import { Hono } from 'hono';
import type { Bindings } from '../types';
import { embedEnabled, backfillEmbeddings, backfillStatus, embedSelfTest, reconcileEmbedGeneration } from '../embed';

export const embedRoutes = new Hono<{ Bindings: Bindings }>();

const OFF_HINT =
  '語義補嵌需先開 embed 模組（Vectorize+AI binding）。叫 CC「幫我開語義查詢」（設 kbdb_embed:true + redeploy 注入 binding）後再呼叫本端點。';

// POST /embed/backfill — batch-embed existing embeddable entries with is_embedded=0.
//   body（皆選填）：{ limit?:1-100（預設25）, owner_id?, source?, library?, since?, until?, reindex?, offset? }。
//   冪等：重跑不會重複嵌（已 is_embedded=1 的不再入選；upsert 同 id 冪等）。
//   分批：單次最多 limit 筆；回傳 remaining>0 表示還有 → 重複呼叫直到 remaining=0。
//   reindex:true（Arcrun#11）：改重推「所有 embeddable」既有向量（含 is_embedded=1），
//     讓事後建立的 Vectorize metadata index 收錄它們（否則帶過濾語意查詢回 0）；配 offset 分頁。
//   library／since／until（Arcrun#85，2026-08-11）：「挑哪一批」從外面指定——時間分層
//     （今天/本週/半年前）與庫分層（有查詢紀錄的庫優先）共用同一套 SelectionCriteria，
//     由呼叫端（工作流）決定這次要補的是哪一批，不是資料層焊死單一排序（見 embed.ts 檔頭說明）。
//   模組未開 → 409 + capability_hint（不假綠）。
embedRoutes.post('/backfill', async (c) => {
  if (!embedEnabled(c.env)) {
    return c.json(
      { success: false, error: 'embed module not enabled (need VECTORIZE + AI bindings)', capability_hint: OFF_HINT },
      409,
    );
  }
  const body = (await c.req.json().catch(() => ({}))) as {
    limit?: number | string;
    owner_id?: string;
    source?: string;
    library?: string;
    since?: number | string;
    until?: number | string;
    reindex?: boolean;
    offset?: number | string;
  };
  const result = await backfillEmbeddings(c.env, {
    limit: body.limit !== undefined ? Number(body.limit) : undefined,
    owner_id: body.owner_id || undefined,
    source: body.source || undefined,
    library: body.library || undefined,
    since: body.since !== undefined ? Number(body.since) : undefined,
    until: body.until !== undefined ? Number(body.until) : undefined,
    // reindex（Arcrun#11）：重推既有向量讓事後建立的 Vectorize metadata index 收錄（見 embed.ts）。
    reindex: body.reindex === true,
    offset: body.offset !== undefined ? Number(body.offset) : undefined,
  });
  return c.json({ success: true, ...result });
});

// GET /embed/backfill/status?owner_id=&source= — 待補嵌 / 已補嵌計數（回報 + 判斷是否清零用）。
embedRoutes.get('/backfill/status', async (c) => {
  const status = await backfillStatus(c.env, {
    owner_id: c.req.query('owner_id') || undefined,
    source: c.req.query('source') || undefined,
  });
  return c.json({ success: true, ...status });
});

// POST /embed/reconcile — 世代核對（D68 配套修復，2026-08-11；D69 額度節流同日補上）：
//   對「is_embedded=1 但 content_hash 非現行模型」的候選，問現行 Vectorize index 是否真的收錄；
//   真的在 → 補標 content_hash（不打 AI）；不在 → 重置 is_embedded=0，回到正常 /embed/backfill 佇列。
//   解「從備份整批灌回、帶著對已退役索引的 is_embedded=1，永遠不被 backfill 碰到」這個坑。
//   body（皆選填）：{ limit?:1-200（預設50）, owner_id?, library?, since?, until? }。重複呼叫直到 remaining=0。
//   D69：每筆候選最多消耗一次 D1 row write，與 POST /entries/backfill-library 共用同一顆每日
//   「背景維護 D1 寫入」額度（見 actions/maintenance-quota.ts）——額度用完會誠實回
//   quota_exceeded:true 並停手，不會把當天 D1 免費額度燒穿（2026-08-11 leo 逐行複核找到的破口）。
embedRoutes.post('/reconcile', async (c) => {
  if (!embedEnabled(c.env)) {
    return c.json(
      { success: false, error: 'embed module not enabled (need VECTORIZE + AI bindings)', capability_hint: OFF_HINT },
      409,
    );
  }
  const body = (await c.req.json().catch(() => ({}))) as {
    limit?: number | string;
    owner_id?: string;
    library?: string;
    since?: number | string;
    until?: number | string;
  };
  const result = await reconcileEmbedGeneration(c.env, {
    limit: body.limit !== undefined ? Number(body.limit) : undefined,
    owner_id: body.owner_id || undefined,
    library: body.library || undefined,
    since: body.since !== undefined ? Number(body.since) : undefined,
    until: body.until !== undefined ? Number(body.until) : undefined,
  });
  return c.json({ success: true, ...result });
});

// GET /embed/selftest?owner_id= — 語義自我檢查（檢修孔，2026-08-07）：
//   挑一筆已嵌入的卡片，拿它自己的內容查自己，只回布林診斷（不回卡片內容、不回 entry id）。
//   計數（backfill/status）看不出「嵌了但查不到」這種故障模式（Arcrun#11 撞過的真實案例），
//   本端點端到端驗證 index 真的可用。模組未開仍誠實回 enabled:false（不 409，讓檢修孔
//   永遠能拿到一個可解讀的結論，不必先判斷該不該打這支）。
embedRoutes.get('/selftest', async (c) => {
  const result = await embedSelfTest(c.env, { owner_id: c.req.query('owner_id') || undefined });
  return c.json({ success: true, ...result });
});

export default embedRoutes;
