import type { ToolInfo } from "@earendil-works/pi-coding-agent";

import {
  CATALOG_VERSION,
  applyCatalogOverrides,
  createFallbackCatalog,
  type CatalogConstraints,
  type GroupCard,
  type ToolCard,
  type ToolCatalog,
} from "./catalog.ts";

interface BundleGroupDefinition {
  id: string;
  title: string;
  summary: string;
  useWhen: string[];
  avoidWhen: string[];
  keywords: string[];
  tools: string[];
}

/** Curated groups for every non-core tool exposed by the my-pi bundle. */
export const BUNDLE_GROUP_DEFINITIONS: BundleGroupDefinition[] = [
  {
    id: "filesystem-navigation",
    title: "Filesystem navigation",
    summary: "Inspect directory layouts and run bounded multi-pattern literal searches.",
    useWhen: ["You need directory entries or several literal searches after narrowing a path"],
    avoidWhen: ["Code relationships or types are the real question"],
    keywords: ["files", "directory", "list", "multi grep", "目录", "文件", "多模式搜索"],
    tools: ["ls", "multi_grep"],
  },
  {
    id: "code-intelligence",
    title: "Code intelligence",
    summary: "Explore code relationships and query language-server definitions, references, types, symbols, and diagnostics.",
    useWhen: ["Understanding architecture, call paths, symbol identity, types, references, or compiler diagnostics"],
    avoidWhen: ["Only an exact literal text match is required"],
    keywords: ["codegraph", "lsp", "definition", "references", "diagnostics", "代码结构", "定义", "引用", "诊断"],
    tools: ["codegraph_explore", "lsp_definition", "lsp_references", "lsp_hover", "lsp_symbols", "lsp_diagnostics"],
  },
  {
    id: "web-tavily",
    title: "Tavily web discovery",
    summary: "Discover broad, current, or news-oriented web sources with Tavily and fetch selected pages.",
    useWhen: ["Keywords are uncertain, many candidate sources help, or current/news coverage is needed"],
    avoidWhen: ["Official technical sources or Chinese site/date constraints are more important"],
    keywords: ["tavily", "web", "news", "discovery", "网页", "新闻", "广泛搜索"],
    tools: ["tavily_web_search", "tavily_web_fetch"],
  },
  {
    id: "web-exa",
    title: "Exa precision research",
    summary: "Find precise official, technical, academic, company, or API sources with Exa and read selected pages.",
    useWhen: ["Source precision, official documentation, papers, releases, or advanced filters matter"],
    avoidWhen: ["Broad news discovery or Chinese policy search is the primary need"],
    keywords: ["exa", "official docs", "paper", "api", "research", "官方文档", "论文", "精确搜索"],
    tools: ["exa_web_search", "exa_web_search_advanced", "exa_web_fetch"],
  },
  {
    id: "web-keenable",
    title: "Keenable focused search",
    summary: "Search Chinese-language, site-restricted, policy, government, or date-filtered sources and fetch selected pages.",
    useWhen: ["Chinese pages, policies, government notices, domain restrictions, or publication dates matter"],
    avoidWhen: ["Broad international discovery or academic semantic search is a better fit"],
    keywords: ["keenable", "Chinese", "policy", "site", "date", "中文", "政策", "站点", "日期"],
    tools: ["keenable_search", "keenable_fetch"],
  },
  {
    id: "context-execution",
    title: "Context-isolated execution",
    summary: "Run commands or analyze large files in a sandbox while returning only compact derived output.",
    useWhen: ["Logs, tests, builds, generated data, commands, or workspace files may produce large output"],
    avoidWhen: ["Exact source lines are needed for an anchored edit"],
    keywords: ["execute", "large file", "logs", "tests", "build", "大文件", "日志", "测试", "构建"],
    tools: ["ctx_execute", "ctx_execute_file", "ctx_batch_execute"],
  },
  {
    id: "context-knowledge",
    title: "Context knowledge base",
    summary: "Index local or web documentation and retrieve focused passages from the persistent context-mode knowledge base.",
    useWhen: ["Documentation or session knowledge should be stored and queried without rereading raw content"],
    avoidWhen: ["A one-shot small source can be read directly"],
    keywords: ["index", "search", "knowledge base", "fetch docs", "索引", "知识库", "文档检索"],
    tools: ["ctx_index", "ctx_search", "ctx_fetch_and_index"],
  },
  {
    id: "context-observability",
    title: "Context observability",
    summary: "Inspect context-mode health, savings statistics, and the hosted Insight dashboard.",
    useWhen: ["Diagnosing context-mode or reviewing context savings and analytics"],
    avoidWhen: ["The task is normal code or data processing"],
    keywords: ["stats", "doctor", "insight", "health", "统计", "诊断", "上下文节省"],
    tools: ["ctx_stats", "ctx_doctor", "ctx_insight"],
  },
  {
    id: "context-administration",
    title: "Context administration",
    summary: "Upgrade context-mode or destructively purge indexed context data.",
    useWhen: ["The user explicitly asks to upgrade context-mode or purge a named scope"],
    avoidWhen: ["Routine searching, indexing, or performance inspection"],
    keywords: ["upgrade", "purge", "delete", "升级", "清除", "删除知识库"],
    tools: ["ctx_upgrade", "ctx_purge"],
  },
  {
    id: "memory-recall",
    title: "Official Hippo memory recall",
    summary: "Recall project memories, inspect Hippo status/context, or recover pruned tool-call output.",
    useWhen: ["Past project lessons, errors, decisions, or condensed outputs may answer the question"],
    avoidWhen: ["The required evidence is already in the current visible context"],
    keywords: ["memory", "recall", "context", "status", "recover", "记忆", "召回", "恢复输出"],
    tools: ["hippo_recall", "hippo_context", "hippo_status", "context_tree_query"],
  },
  {
    id: "memory-management",
    title: "Official Hippo memory management",
    summary: "Store durable project lessons or report whether recalled Hippo memories were useful.",
    useWhen: ["A non-obvious project lesson, error, or decision should persist, or recalled memories need feedback"],
    avoidWhen: ["The information is temporary task progress or derivable from the repository"],
    keywords: ["remember", "outcome", "lesson", "error", "记住", "反馈", "项目经验"],
    tools: ["hippo_remember", "hippo_outcome"],
  },
  {
    id: "mcp-management",
    title: "MCP management",
    summary: "Inspect and manage MCP server connections exposed by Pi's shared adapter.",
    useWhen: ["The user explicitly asks about MCP server status or operations"],
    avoidWhen: ["A mapped first-class MCP tool already handles the task"],
    keywords: ["mcp", "server", "connection", "MCP 服务", "连接"],
    tools: ["mcp"],
  },
];

export interface BundleCatalogResult {
  catalog: ToolCatalog;
  coveredNames: Set<string>;
  unknownTools: ToolInfo[];
}

function compactDescription(tool: ToolInfo): string {
  return tool.description.replace(/\s+/gu, " ").trim().slice(0, 240) || `Use the ${tool.name} tool.`;
}

export function createBundleCatalog(
  tools: ToolInfo[],
  manifestHash: string,
  constraints: CatalogConstraints,
): BundleCatalogResult {
  const available = new Map(tools.map((tool) => [tool.name, tool]));
  const coveredNames = new Set<string>();
  const groups: GroupCard[] = [];
  const cards: ToolCard[] = [];

  for (const definition of BUNDLE_GROUP_DEFINITIONS) {
    const members = definition.tools.flatMap((name) => {
      const tool = available.get(name);
      return tool ? [tool] : [];
    });
    if (members.length === 0) continue;
    for (const tool of members) {
      coveredNames.add(tool.name);
      cards.push({
        name: tool.name,
        summary: compactDescription(tool),
        useWhen: [...definition.useWhen],
        avoidWhen: [...definition.avoidWhen],
        keywords: [...new Set([...definition.keywords, ...tool.name.split(/[_-]/u)])],
        primaryGroup: definition.id,
      });
    }
    groups.push({
      id: definition.id,
      title: definition.title,
      summary: definition.summary,
      useWhen: [...definition.useWhen],
      avoidWhen: [...definition.avoidWhen],
      tools: members.map((tool) => tool.name),
    });
  }

  const unknownTools = tools.filter((tool) => !coveredNames.has(tool.name));
  const fallback = createFallbackCatalog(unknownTools, manifestHash, {
    maxToolsPerGroup: constraints.maxToolsPerGroup,
    groupOverrides: {},
  });
  const reservedIds = new Set(groups.map((group) => group.id));
  const renamedGroups = new Map<string, string>();
  for (const group of fallback.groups) {
    let id = group.id;
    while (reservedIds.has(id)) id = `custom-${id}`;
    reservedIds.add(id);
    renamedGroups.set(group.id, id);
    groups.push({ ...group, id, title: id.replace(/-/gu, " ") });
  }
  for (const card of fallback.tools) {
    cards.push({ ...card, primaryGroup: renamedGroups.get(card.primaryGroup) ?? card.primaryGroup });
  }

  const overridden = applyCatalogOverrides(groups, cards, constraints.groupOverrides);
  return {
    catalog: {
      version: CATALOG_VERSION,
      manifestHash,
      generatedAt: new Date().toISOString(),
      generatedBy: "my-pi bundle seed",
      groups: overridden.groups,
      tools: overridden.tools,
    },
    coveredNames,
    unknownTools,
  };
}

export function catalogPreservesBundleAssignments(
  candidate: ToolCatalog,
  bundleCatalog: ToolCatalog,
  coveredNames: Set<string>,
): boolean {
  const expected = new Map(
    bundleCatalog.tools.filter((tool) => coveredNames.has(tool.name)).map((tool) => [tool.name, tool.primaryGroup]),
  );
  const actual = new Map(candidate.tools.map((tool) => [tool.name, tool.primaryGroup]));
  return [...expected].every(([name, group]) => actual.get(name) === group);
}
