/**
 * 藏書地圖 MCP 薄殼（library-map SDD M4；源頭 Arcrun#39）
 *
 * rule 07 §5（薄殼鐵律）：地圖的聚合 SQL 全住 kbdb base（actions/library-map.ts，D6 裁定），
 * MCP 只做介面轉換——經既有 KBDB service binding（kbdbFetch）打 GET /map／/map/:library，
 * 不碰 D1、不新增 binding。與 #68 kbdb_graph_neighbors 同族（D17 KBDB MCP 面，kbdb_* 前綴）。
 *
 * 端點契約（kbdb/src/routes/map.ts，M2 已 merge）：
 *   GET /map           → { success, libraries:[{library, narrative, top_entities(名字 top3),
 *                          triplet_count, updated_at}], count }
 *   GET /map/:library  → { success, map:{record_id, library, narrative, content, top_entities,
 *                          relation_profile, bridges, triplet_count, commit_hash, status, updated_at} }
 *   404 → { success:false, error:'not found' }（該庫從未 recompute）
 *
 * slot 值防禦：top_entities/relation_profile/bridges 底層存 JSON 字串，正常 base 已 parse；
 * 但仍容錯「字串形直出」（live 曾觀測）——字串就 parse、失敗當空陣列，絕不 crash（鐵律）。
 */

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import type { Env } from "../types.js";
import { kbdbFetch } from "../lib/kbdb-client.js";
import { errorResponse, successResponse } from "../lib/cypher-client.js";
import { entityNames, parseSlotArray, type LibraryMapRow } from "../lib/library-map.js";

/** 空庫/404 時的 backfill 指引（誠實回報＋給下一步，鐵律：不假綠）。 */
const RECOMPUTE_HINTS = [
  "地圖由 ingest 尾端自動重算（M3）；尚未接鏈的庫要手動 backfill：對 kbdb 呼 POST /map/recompute?library=<庫名>（可帶 body {narrative, source_prefix}）",
  "backfill 過渡期（triplet 還沒有 library slot 值）用 source_prefix 以 source_uri 前綴歸庫，如 {\"source_prefix\":\"gitea:Leo/kb@\"}",
];

/** 註冊全部藏書地圖工具（library-map M4）。 */
export function registerAllKbdbMapTools(server: McpServer, env: Env) {
  registerGetMap(server, env);
}

/** 單庫詳圖回傳形狀（GET /map/:library 的 map，slot 陣列已 parse 成物件）。 */
interface LibraryMapDetail {
  record_id?: string;
  library?: string;
  narrative?: string | null;
  content?: string | null;
  top_entities?: unknown;
  relation_profile?: unknown;
  bridges?: unknown;
  triplet_count?: number | string;
  commit_hash?: string | null;
  status?: string;
  updated_at?: number;
}

/**
 * kbdb_get_map — 藏書地圖。無參數＝全館（每庫一行）；帶 library＝該庫詳圖。
 * design §6 retrieval 流程的第一站：地圖 → get_map(library) 細節 → graph/search 進庫。
 */
export function registerGetMap(server: McpServer, env: Env) {
  server.tool(
    "kbdb_get_map",
    "藏書地圖：KBDB 全館導覽。不帶參數＝全館地圖（每庫一行：庫名＋narrative＋核心 top 3 entities＋" +
      "triplet 數），帶 library 參數＝該庫詳圖（完整 top_entities/relation_profile/跨庫 bridges）。" +
      "不確定該查什麼時，先呼叫此工具——先看地圖定位該進哪個庫，再用 kbdb_search（關鍵字/語義）或 " +
      "kbdb_graph_neighbors（關係遍歷）進庫查細節。",
    {
      library: z.string().min(1).optional().describe(
        "庫名（如 'kb'／'notes'）。帶了回該庫詳圖；不帶回全館地圖（先看全館再挑庫）",
      ),
      owner_id: z.string().optional().describe("限定某資料歸屬範圍（選填，與其他 kbdb_* 工具同義）"),
    },
    async ({ library, owner_id }) => {
      try {
        const qs = owner_id ? `?owner_id=${encodeURIComponent(owner_id)}` : "";

        if (!library) {
          // 全館地圖：每庫一行（library＋narrative＋top 3 entities＋triplet_count）。
          const res = await kbdbFetch(env, `/map${qs}`);
          if (!res.ok) {
            return errorResponse(
              "map_fetch_failed",
              `取全館地圖失敗 HTTP ${res.status}`,
              ["稍後重試", ...RECOMPUTE_HINTS],
              await res.text().catch(() => ""),
            );
          }
          const data = (await res.json()) as { libraries?: LibraryMapRow[]; count?: number };
          const libraries = (Array.isArray(data.libraries) ? data.libraries : []).map((l) => ({
            ...l,
            // 防禦：top_entities 若是 JSON 字串形就 parse 成名字清單（失敗當空，誠實不 crash）。
            top_entities: entityNames(l.top_entities, 3),
            triplet_count: Number(l.triplet_count ?? 0) || 0,
          }));
          if (libraries.length === 0) {
            // 空庫誠實回報：不是錯誤（端點正常、就是還沒有地圖），給 backfill 指引。
            return successResponse({ libraries: [], count: 0 }, [
              "全館地圖是空的：還沒有任何庫跑過 recompute",
              ...RECOMPUTE_HINTS,
            ]);
          }
          return successResponse({ libraries, count: libraries.length }, [
            "要看某庫細節：kbdb_get_map(library='庫名')",
            "進庫查內容：kbdb_search（關鍵字/語義）；查關係：kbdb_graph_neighbors",
          ]);
        }

        // 單庫詳圖：完整 slots（slot 陣列 parse 成物件再回）。
        const res = await kbdbFetch(env, `/map/${encodeURIComponent(library)}${qs}`);
        if (res.status === 404) {
          return errorResponse(
            "map_not_found",
            `庫「${library}」還沒有地圖（從未 recompute，或庫名打錯）`,
            ["kbdb_get_map 不帶參數看全館有哪些庫（確認庫名）", ...RECOMPUTE_HINTS],
          );
        }
        if (!res.ok) {
          return errorResponse(
            "map_fetch_failed",
            `取庫「${library}」詳圖失敗 HTTP ${res.status}`,
            ["稍後重試", ...RECOMPUTE_HINTS],
            await res.text().catch(() => ""),
          );
        }
        const data = (await res.json()) as { map?: LibraryMapDetail };
        const raw = data.map ?? {};
        const map = {
          ...raw,
          // slot 字串容錯 parse（任務規格：JSON 字串形要 parse 成物件再回，失敗當空陣列）。
          top_entities: parseSlotArray<{ name: string; degree: number }>(raw.top_entities),
          relation_profile: parseSlotArray<{ predicate: string; count: number }>(raw.relation_profile),
          bridges: parseSlotArray<{ entity: string; libraries: string[] }>(raw.bridges),
          triplet_count: Number(raw.triplet_count ?? 0) || 0,
        };
        return successResponse({ map }, [
          "bridges＝此庫 entity 同時出現在哪些其他庫（M3 backfill 前會偏稀疏，是誠實現況不是 bug）",
          "沿核心 entity 挖關係：kbdb_graph_neighbors(subject=entity 名)",
        ]);
      } catch (e) {
        return errorResponse("internal_error", e instanceof Error ? e.message : String(e), ["稍後重試"]);
      }
    },
  );
}
