/**
 * Gemini function-call dedupe — per-round + global (mutating tools only).
 *
 * Global dedupe prevents write_report / write_task hammering across rounds.
 * read_file / grep_files / list_dir skip global dedupe so a failed read can retry next round.
 */

/** Idempotent reads — no cross-round global dedupe. */
export const GEMINI_NO_GLOBAL_DEDUPE_TOOLS = new Set([
  // Only workspace file reads/search skip cross-round dedupe. Ledger/status reads are
  // deduped globally so Gemini patrols summarize instead of looping tools.
  "read_file",
  "grep_files",
]);

/** Patrol/status tools — global dedupe + loop-break when Gemini re-requests. */
export const PATROL_REUSE_TOOLS = new Set([
  "fcop_report",
  "fcop_check",
  "get_team_status",
  "list_tasks",
  "list_reports",
  "list_issues",
]);

export function isPatrolReuseTool(toolName: string): boolean {
  return PATROL_REUSE_TOOLS.has(toolName);
}

function normalizePatrolDedupeKey(toolName: string, args: unknown): string | null {
  if (!PATROL_REUSE_TOOLS.has(toolName)) return null;
  const a = (args ?? {}) as Record<string, unknown>;
  if (toolName === "fcop_report") {
    const scope = String(a.scope ?? "auto").trim().toLowerCase() || "auto";
    return `${toolName}:scope=${scope}`;
  }
  if (
    toolName === "list_tasks" ||
    toolName === "list_reports" ||
    toolName === "list_issues"
  ) {
    const recipient =
      String(a.recipient ?? a.role ?? "").trim().toUpperCase() || "*";
    return `${toolName}:recipient=${recipient}`;
  }
  if (toolName === "get_team_status" || toolName === "fcop_check") {
    return `${toolName}:patrol`;
  }
  return `${toolName}:patrol`;
}

function extractTaskIdToken(args: Record<string, unknown>): string {
  const raw = [args.task_id, args.taskId, args.filename, args.id]
    .map((v) => (typeof v === "string" ? v.trim() : ""))
    .find(Boolean);
  if (!raw) return "";
  const match = raw.match(/TASK-\d{8}-\d{3,}/i);
  return match ? match[0].toUpperCase() : raw.toUpperCase();
}

/** Interim ack vs terminal report — body 不参与去重键。 */
function writeReportStatusBucket(status: unknown): string {
  const s = String(status ?? "")
    .trim()
    .toLowerCase();
  if (!s) return "interim";
  if (
    ["done", "completed", "complete", "finished", "success", "succeeded"].includes(
      s,
    )
  ) {
    return "done";
  }
  if (s === "blocked") return "blocked";
  if (s === "aborted") return "aborted";
  if (
    ["in_progress", "in-progress", "inprogress", "running", "active"].includes(s)
  ) {
    return "interim";
  }
  return s;
}

function normalizeWriteReportDedupeKey(args: unknown): string | null {
  const a = (args ?? {}) as Record<string, unknown>;
  const taskId = extractTaskIdToken(a);
  const reporter = String(a.reporter ?? a.sender ?? "")
    .trim()
    .toUpperCase();
  const recipient = String(a.recipient ?? "")
    .trim()
    .toUpperCase();
  const bucket = writeReportStatusBucket(a.status);
  const clientSubmissionId = String(
    a.client_submission_id ?? a.clientSubmissionId ?? "",
  ).trim();
  const body = String(a.body ?? "")
    .replace(/\r\n?/g, "\n")
    .trim();
  // Keep terminal reports content-sensitive. A rework submission for the same
  // task is a new report, while an exact retry (or explicit client id) reuses it.
  const attempt = clientSubmissionId || (bucket === "done" ? stableTextHash(body) : "");
  if (taskId) {
    return `write_report:${taskId}:${reporter}:${recipient}:${bucket}:${attempt}`;
  }
  // 模型漏传 task_id 时勿退回 JSON.stringify(body)——body 略改会导致去重失效、真实重复落盘
  if (reporter && recipient) {
    return `write_report:*:${reporter}:${recipient}:${bucket}:${attempt}`;
  }
  if (reporter || recipient) {
    return `write_report:*:${reporter || "*"}:${recipient || "*"}:${bucket}:${attempt}`;
  }
  return `write_report:*:*:*:${bucket}:${attempt}`;
}

function stableTextHash(text: string): string {
  let hash = 2166136261;
  for (let i = 0; i < text.length; i += 1) {
    hash ^= text.charCodeAt(i);
    hash = Math.imul(hash, 16777619);
  }
  return (hash >>> 0).toString(16).padStart(8, "0");
}

export function toolDedupeKey(toolName: string, args: unknown): string {
  const patrolKey = normalizePatrolDedupeKey(toolName, args);
  if (patrolKey) return patrolKey;

  const a = (args ?? {}) as Record<string, unknown>;
  if (toolName === "write_report") {
    const reportKey = normalizeWriteReportDedupeKey(args);
    if (reportKey) return reportKey;
  }
  if (toolName === "write_task" || toolName === "create_task") {
    const recipient = String(a.recipient ?? "")
      .trim()
      .toUpperCase();
    const refRaw = [a.references, a.reference, a.parent, a.parent_task, a.parentTask]
      .map((v) => (typeof v === "string" ? v.trim() : ""))
      .find(Boolean);
    const refMatch = refRaw?.match(/TASK-\d{8}-\d{3,}/i);
    const ref = refMatch ? refMatch[0].toUpperCase() : (refRaw ?? "").toUpperCase();
    return `${toolName}:${recipient}:${ref}`;
  }
  return `${toolName}:${JSON.stringify(args ?? {})}`;
}

function isFcopLedgerReadArgs(args: unknown): boolean {
  const a = (args ?? {}) as Record<string, unknown>;
  const rawPath = String(a.path ?? a.file ?? a.filename ?? "").replace(/\\/g, "/");
  return (
    rawPath.startsWith("fcop/") ||
    rawPath.startsWith(".fcop/") ||
    rawPath.includes("/fcop/") ||
    rawPath.includes("/.fcop/")
  );
}

export function shouldGlobalDedupeTool(toolName: string, args?: unknown): boolean {
  if (toolName === "read_file" && isFcopLedgerReadArgs(args)) {
    return true;
  }
  return !GEMINI_NO_GLOBAL_DEDUPE_TOOLS.has(toolName);
}

export type ToolDedupeDecision =
  | { action: "execute" }
  | { action: "reuse"; output: string };

function isPatrolToolFailureOutput(output: string): boolean {
  const trimmed = output.trim();
  if (!trimmed) return true;
  const lower = trimmed.toLowerCase();
  if (lower.includes("unexpected keyword argument")) return true;
  if (trimmed.startsWith("Error:")) return true;
  if (trimmed.startsWith("{") && lower.includes('"error"')) return true;
  return false;
}

export function resolveToolDedupe(params: {
  toolName: string;
  args: unknown;
  seenThisRound: Set<string>;
  seenGlobal: Set<string>;
  cache: Map<string, string>;
}): ToolDedupeDecision {
  const key = toolDedupeKey(params.toolName, params.args);
  const globalBlocked =
    shouldGlobalDedupeTool(params.toolName, params.args) && params.seenGlobal.has(key);
  const roundBlocked = params.seenThisRound.has(key);

  if (!roundBlocked && !globalBlocked) {
    return { action: "execute" };
  }

  const cached = params.cache.get(key);
  if (cached !== undefined) {
    const failureRetry =
      (PATROL_REUSE_TOOLS.has(params.toolName) ||
        params.toolName === "write_report") &&
      isPatrolToolFailureOutput(cached);
    if (failureRetry) {
      params.cache.delete(key);
      params.seenThisRound.delete(key);
      params.seenGlobal.delete(key);
      return { action: "execute" };
    }
    return { action: "reuse", output: cached };
  }

  // Prior attempt recorded in seen* but never cached (legacy bug) — allow retry.
  if (roundBlocked) params.seenThisRound.delete(key);
  if (globalBlocked) params.seenGlobal.delete(key);
  return { action: "execute" };
}

export function recordToolDedupeResult(params: {
  toolName: string;
  args: unknown;
  output: string;
  seenThisRound: Set<string>;
  seenGlobal: Set<string>;
  cache: Map<string, string>;
}): void {
  const key = toolDedupeKey(params.toolName, params.args);
  params.cache.set(key, params.output);
  params.seenThisRound.add(key);
  if (shouldGlobalDedupeTool(params.toolName, params.args)) {
    params.seenGlobal.add(key);
  }
}

const READ_FILE_REUSE_HINT =
  "\n\n[hint] 该文件已在本轮/会话中读取；请勿重复 read_file。请根据上方内容继续 write_task 派发下游。";

const WRITE_TASK_REUSE_HINT =
  "\n\n[hint] 已向同一 recipient 派过 references 相同的 write_task；请勿重复派单。请继续等待下游 REPORT 或 write_report 汇总。";

const WRITE_REPORT_INTERIM_REUSE_HINT =
  "\n\n[hint] 该 task 已向同一 recipient 写过 interim report（ack）；**禁止重复 write_report ack**。请立刻继续：write_task 派发下游、fcop_report/fcop_check 巡检，或工作完成后写 status=done 的最终汇总。";

const PATROL_REUSE_HINT =
  '\n\n{"status":"already_fetched","action":"summarize_now","message":"Patrol data was already returned above. Do NOT call patrol tools again. Reply to ADMIN with a concise summary: what you checked, findings, blockers, next steps."}';

const PATROL_REUSE_HINT_ZH =
  '\n\n{"status":"already_fetched","action":"summarize_now","message":"巡检数据已在上方返回，禁止再次调用相同 patrol 工具。请直接用中文向 ADMIN 总结：已查项、发现、阻塞项、建议下一步。"}';

function hasPatrolCacheEntryForTool(
  cache: Map<string, string>,
  toolName: string,
): boolean {
  for (const key of cache.keys()) {
    if (key === toolName || key.startsWith(`${toolName}:`)) return true;
  }
  return false;
}

export function hasWriteReportDoneInCache(cache: Map<string, string>): boolean {
  for (const key of cache.keys()) {
    if (
      key.startsWith("write_report:") &&
      (key.includes(":done:") || key.endsWith(":done"))
    ) {
      return true;
    }
  }
  return false;
}

export function hasPatrolSessionDataInCache(cache: Map<string, string>): boolean {
  for (const [key, value] of cache) {
    const toolName = key.split(":")[0] ?? "";
    if (PATROL_REUSE_TOOLS.has(toolName)) return true;
    if (
      toolName === "read_file" &&
      (key.includes("fcop") || value.includes("fcop/"))
    ) {
      return true;
    }
  }
  return false;
}

/** @deprecated prefer hasPatrolSessionDataInCache */
export const hasPatrolDataInCache = hasPatrolSessionDataInCache;

/**
 * Stop Gemini tool loops during patrol when the model spams duplicate function calls.
 * Handles both duplicate-only rounds and the common "1 execute + N reuse" batch pattern.
 */
export function shouldBreakPatrolToolLoop(params: {
  functionCallCount: number;
  executedCount: number;
  reusedCount: number;
  /** Current tool-loop round (1-based, after increment). */
  roundIndex: number;
  cache: Map<string, string>;
  /** ADMIN 打回 Hot Path：在 write_report(done) 落盘前放宽 patrol 熔断 */
  adminRejectHotPath?: boolean;
}): boolean {
  if (params.functionCallCount === 0) return false;
  if (!hasPatrolSessionDataInCache(params.cache)) return false;

  const hotPathIncomplete =
    params.adminRejectHotPath === true &&
    !hasWriteReportDoneInCache(params.cache);
  const maxRounds = hotPathIncomplete ? 6 : 3;
  const hasCheck = hasPatrolCacheEntryForTool(params.cache, "fcop_check");

  const { functionCallCount, executedCount, reusedCount, roundIndex } = params;

  // Entire round was cache hits.
  if (executedCount === 0) {
    if (hotPathIncomplete && roundIndex < maxRounds && !hasCheck) return false;
    return true;
  }

  // Typical Gemini patrol spam: parallel batch with mostly duplicates.
  if (
    reusedCount >= 2 &&
    reusedCount + executedCount >= functionCallCount &&
    functionCallCount >= 3
  ) {
    if (hotPathIncomplete && !hasCheck) return false;
    return true;
  }

  // Second+ round still dominated by duplicates — do not call Gemini again.
  if (
    roundIndex >= 2 &&
    reusedCount > 0 &&
    reusedCount >= executedCount &&
    functionCallCount >= 2
  ) {
    if (hotPathIncomplete && !hasCheck) return false;
    return true;
  }

  // Hard cap: normal patrol ≤3 rounds; Hot Path 返工允许更多轮直至 write_report(done)。
  if (roundIndex >= maxRounds) {
    return true;
  }

  return false;
}

export function hasWriteReportInterimInCache(cache: Map<string, string>): boolean {
  for (const key of cache.keys()) {
    if (
      key.startsWith("write_report:") &&
      (key.includes(":interim:") || key.endsWith(":interim"))
    ) {
      return true;
    }
  }
  return false;
}

/**
 * Stop Gemini hammering write_report ack for the same task (body 略改仍会 dedupe，但模型仍可能死循环)。
 */
export function shouldBreakWriteReportAckLoop(params: {
  functionCallCount: number;
  executedCount: number;
  reusedCount: number;
  writeReportReuseCount: number;
  roundIndex: number;
  cache: Map<string, string>;
}): boolean {
  if (params.functionCallCount === 0) return false;
  if (!hasWriteReportInterimInCache(params.cache)) return false;

  if (params.executedCount === 0 && params.writeReportReuseCount >= 1) {
    return true;
  }

  if (
    params.writeReportReuseCount >= 2 &&
    params.reusedCount >= params.executedCount &&
    params.functionCallCount >= 2
  ) {
    return true;
  }

  if (params.roundIndex >= 2 && params.writeReportReuseCount > 0) {
    return true;
  }

  return false;
}

export function buildWriteReportAckBreakSummary(opts?: {
  uiLang?: "zh" | "en";
  toolLog?: string[];
}): string {
  const zh = opts?.uiLang !== "en";
  const header = zh
    ? "## 任务已 ack（运行时熔断）\n\n已向 ADMIN 写过 interim report，模型重复 write_report 已被去重。请继续派发或巡检，勿再 ack：\n"
    : "## Task already acknowledged (runtime break)\n\nInterim report was written; duplicate write_report calls were deduped. Continue dispatch or patrol — do not ack again:\n";
  const footer = zh
    ? "\n\n---\n**下一步**：write_task 派 DEV/QA/OPS，或 fcop_report/fcop_check 完成系统检查后 write_report(status=done) 汇总。"
    : "\n\n---\n**Next**: write_task to workers, or fcop_report/fcop_check then final write_report(status=done).";
  const logLines = opts?.toolLog?.slice(-8) ?? [];
  if (logLines.length === 0) {
    return header + footer;
  }
  return (
    header +
    (zh ? "### 近期工具\n" : "### Recent tools\n") +
    logLines.join("\n") +
    footer
  );
}

/** @deprecated use shouldBreakPatrolToolLoop */
export function shouldBreakPatrolDuplicateRound(params: {
  functionCallCount: number;
  executedCount: number;
  cache: Map<string, string>;
}): boolean {
  return shouldBreakPatrolToolLoop({
    ...params,
    reusedCount: params.functionCallCount - params.executedCount,
    roundIndex: 1,
  });
}

const PATROL_TOOL_LABELS: Record<string, { zh: string; en: string }> = {
  get_team_status: { zh: "团队状态", en: "Team status" },
  fcop_report: { zh: "FCoP 汇报", en: "FCoP report" },
  fcop_check: { zh: "FCoP 轻量检查", en: "FCoP check" },
  list_tasks: { zh: "待办任务", en: "Tasks" },
  list_reports: { zh: "报告", en: "Reports" },
  list_issues: { zh: "问题单", en: "Issues" },
};

export type ToolSummaryVariant = "patrol_break" | "no_model_text";

export function buildPatrolSummaryFromCache(
  cache: Map<string, string>,
  opts?: {
    uiLang?: "zh" | "en";
    toolLog?: string[];
    variant?: ToolSummaryVariant;
    /** Hot Path 打回未完成：提示下一步 fcop_check → write_report(done) */
    adminRejectHotPathNextSteps?: boolean;
  },
): string {
  const zh = opts?.uiLang !== "en";
  const variant = opts?.variant ?? "patrol_break";
  const sections: string[] = [];

  for (const [key, value] of cache) {
    const toolName = key.split(":")[0] ?? "";
    const snippet =
      value.length > 800 ? `${value.slice(0, 800)}…` : value;

    if (PATROL_REUSE_TOOLS.has(toolName)) {
      const label = PATROL_TOOL_LABELS[toolName]?.[zh ? "zh" : "en"] ?? toolName;
      sections.push(`### ${label}\n${snippet}`);
      continue;
    }

    if (
      toolName === "read_file" &&
      (key.includes("fcop") || value.includes("fcop/"))
    ) {
      const pathMatch = key.match(/"path"\s*:\s*"([^"]+)"/);
      const pathLabel = pathMatch?.[1] ?? "fcop ledger";
      const label = zh ? `账本 ${pathLabel}` : `Ledger ${pathLabel}`;
      sections.push(`### ${label}\n${snippet}`);
    }
  }

  const header =
    variant === "no_model_text"
      ? zh
        ? "## 系统检查汇总（运行时整理）\n\n模型在本轮未输出可见正文；以下根据本会话已执行的 FCoP / MCP 工具结果整理：\n"
        : "## System check summary (runtime)\n\nThe model returned no user-visible text; summary from FCoP / MCP tool results this session:\n"
      : zh
        ? "## 巡检结果（运行时汇总）\n\n模型重复请求相同 patrol 工具，已熔断工具循环并基于本会话缓存数据汇总：\n"
        : "## Patrol summary (runtime)\n\nRepeated patrol tool calls were deduped; summary from cached session data:\n";

  const footer = opts?.adminRejectHotPathNextSteps
    ? zh
      ? "\n\n---\n**Hot Path 未完成**：以上为缓存的 patrol 数据，**不等于**任务已验收完成。下一必做：MCP `fcop_check` → read_file/grep_files 探针 → `write_report(status=done)`。PM Hot Path 为治理核查/协调，**不代表**可修改产品代码；若需实现则 `write_task` 派 DEV/OPS/QA/EVAL；**禁止**把本汇总当作最终回执。"
      : "\n\n---\n**Hot Path incomplete**: cached patrol data above is NOT task completion. Next: `fcop_check` → probes → `write_report(status=done)`. PM Hot Path is governance/coordination only — not product code edit; use write_task for DEV/OPS/QA/EVAL when implementation is needed; do not treat this summary as the final report."
    : zh
      ? "\n\n---\n**说明**：以上来自本会话已执行的 FCoP 工具。如需全量刷新，请新开一轮对话。"
      : "\n\n---\nBased on FCoP tool results already fetched this session.";

  if (sections.length === 0) {
    const logLines = opts?.toolLog?.slice(-10) ?? [];
    if (logLines.length > 0) {
      return (
        header +
        (zh ? "### 工具调用记录\n" : "### Tool log\n") +
        logLines.join("\n") +
        footer
      );
    }
    return zh
      ? "巡检已完成，但未缓存可展示的 patrol 数据。请查看门铃中的工具结果。"
      : "Patrol finished but no cached patrol payload to summarize.";
  }

  return header + sections.join("\n\n") + footer;
}

export function formatReusedToolOutput(toolName: string, cached: string): string {
  if (
    (toolName === "read_file" || toolName === "list_dir") &&
    cached.includes("fcop/") &&
    !cached.includes("summarize_now")
  ) {
    const hint = cached.match(/[\u4e00-\u9fff]/)
      ? PATROL_REUSE_HINT_ZH
      : PATROL_REUSE_HINT;
    return `${cached}${hint}`;
  }
  if (toolName === "read_file" && !cached.includes("[hint]")) {
    return `${cached}${READ_FILE_REUSE_HINT}`;
  }
  if (
    (toolName === "write_task" || toolName === "create_task") &&
    !cached.includes("[hint]")
  ) {
    return `${cached}${WRITE_TASK_REUSE_HINT}`;
  }
  if (toolName === "write_report" && !cached.includes("[hint]")) {
    return `${cached}${WRITE_REPORT_INTERIM_REUSE_HINT}`;
  }
  if (PATROL_REUSE_TOOLS.has(toolName) && !cached.includes("summarize_now")) {
    const hint = cached.match(/[\u4e00-\u9fff]/) ? PATROL_REUSE_HINT_ZH : PATROL_REUSE_HINT;
    return `${cached}${hint}`;
  }
  return cached;
}
