/**
 * Feishu interactive card builder for agent run output.
 *
 * Produces card JSON with:
 *  1. A collapsible tool-use panel (tool steps with status, params, results)
 *  2. A collapsible reasoning panel (thinking text)
 *  3. The streaming/final answer text (markdown)
 *
 * Adapted from openclaw-lark's builder.ts, simplified for our
 * message.patch-based approach (no CardKit 2.0 streaming_mode).
 * Uses JSON 1.0 card format with `collapsible_panel` and `markdown` tags.
 */

import type { ToolUseTraceStep } from "./trace-store.js";
import type { CardContentSegment } from "../outboundImages.js";

// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------

export interface FeishuCard {
  readonly config: Record<string, unknown>;
  readonly elements: ReadonlyArray<unknown>;
}

export type CardPhase = "thinking" | "streaming" | "complete";

const STEP_INDENT = "0px 0px 0px 22px";
const MAX_TEXT_LENGTH = 7500; // Feishu card content limit ~30k; leave room for panels

// ---------------------------------------------------------------------------
// Tool name → icon mapping
// ---------------------------------------------------------------------------

const TOOL_ICONS: Record<string, string> = {
  read: "doc-filled",
  write: "edit-filled",
  bash: "terminal-filled",
  glob: "search-filled",
  grep: "search-filled",
  edit: "edit-filled",
  send_file: "send-filled",
  request_approval: "thumb-up-filled",
  feishu_read_context: "search-filled",
  feishu_download_resource: "download-filled",
  webfetch: "link-copy-filled",
  websearch: "search-filled",
};

function toolIcon(toolName: string): string {
  const normalized = toolName.toLowerCase().replace(/^mcp_/, "");
  return TOOL_ICONS[normalized] ?? "tool-filled";
}

// ---------------------------------------------------------------------------
// Card builder
// ---------------------------------------------------------------------------

export function buildAgentCard(params: {
  phase: CardPhase;
  text: string;
  contentSegments?: readonly CardContentSegment[] | undefined;
  reasoningText: string | undefined;
  toolUseSteps: ToolUseTraceStep[];
  toolUseElapsedMs: number | undefined;
  isError: boolean | undefined;
  interrupted: boolean | undefined;
  runId: string | undefined;
}): Record<string, unknown> {
  const { phase, text, contentSegments, reasoningText, toolUseSteps, toolUseElapsedMs, isError, interrupted } = params;
  const elements: unknown[] = [];

  // Tool-use panel (always present if there are steps)
  if (toolUseSteps.length > 0) {
    elements.push(buildToolUsePanel(toolUseSteps, toolUseElapsedMs, phase !== "complete"));
  } else if (phase === "thinking" || (phase === "streaming" && text === "" && (contentSegments === undefined || contentSegments.length === 0))) {
    elements.push(buildPendingToolUsePanel());
  }

  // Reasoning panel
  if (reasoningText !== undefined && reasoningText !== "") {
    if (phase === "streaming" && text === "" && (contentSegments === undefined || contentSegments.length === 0)) {
      // Still thinking: show reasoning inline
      elements.push({
        tag: "markdown",
        content: `\u{1F4AD} \u601d\u8003\u4E2D...\n\n${reasoningText}`,
        text_size: "notation",
      });
    } else if (phase === "complete") {
      // Collapsed reasoning panel in complete state
      elements.push(buildReasoningPanel(reasoningText));
    }
  }

  // Main answer: either materialized segments (markdown + Feishu-hosted images)
  // or a single markdown block.
  const answerElements = buildAnswerElements(text, contentSegments);
  if (answerElements.length > 0) {
    elements.push(...answerElements);
  } else if (phase === "thinking" && toolUseSteps.length === 0 && (reasoningText === undefined || reasoningText === "")) {
    elements.push({
      tag: "markdown",
      content: "\u{1F4AD} \u601d\u8003\u4E2D...",
      text_size: "notation",
    });
  }

  // Interrupt button while the run is live. The button carries the run id in
  // its action value; the card action handler aborts the run. Feishu's native
  // `confirm` dialog is the "are you sure" step — the action only fires after
  // the user confirms, so no second round-trip is needed.
  if (phase !== "complete" && params.runId !== undefined) {
    elements.push(buildInterruptAction(params.runId));
  }

  // Footer for complete state
  if (phase === "complete") {
    const footerParts: string[] = [];
    if (interrupted === true) footerParts.push("\u{1F6D1} \u5DF2\u4E2D\u65AD");
    else if (isError === true) footerParts.push("\u274C \u5931\u8D25");
    else footerParts.push("\u2705 \u5B8C\u6210");
    if (toolUseElapsedMs !== undefined) {
      footerParts.push(formatElapsed(toolUseElapsedMs));
    }
    elements.push({
      tag: "div",
      text: {
        tag: "plain_text",
        content: footerParts.join(" \u00B7 "),
        text_size: "notation",
        text_color: "grey",
      },
    });
  }

  return {
    config: { wide_screen_mode: true, update_multi: true },
    elements,
  };
}

/** Action row with a single danger "interrupt" button for a live run. */
function buildInterruptAction(runId: string): unknown {
  return {
    tag: "action",
    actions: [
      {
        tag: "button",
        text: { tag: "plain_text", content: "\u{1F6D1} \u4E2D\u65AD" },
        type: "danger",
        value: { interrupt_run: runId },
        confirm: {
          title: { tag: "plain_text", content: "\u786E\u8BA4\u4E2D\u65AD" },
          text: {
            tag: "plain_text",
            content: "\u786E\u5B9A\u8981\u4E2D\u65AD\u5F53\u524D\u8FD0\u884C\u5417\uFF1F\u4E2D\u65AD\u540E\u65E0\u6CD5\u6062\u590D\uFF0C\u5DF2\u4EA7\u51FA\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559\u3002",
          },
        },
      },
    ],
  };
}

// ---------------------------------------------------------------------------
// Tool-use panel
// ---------------------------------------------------------------------------

function buildPendingToolUsePanel(): unknown {
  return {
    tag: "collapsible_panel",
    expanded: false,
    header: {
      title: {
        tag: "plain_text",
        content: "\u{1F6E0}\uFE0F \u7B49\u5F85\u5DE5\u5177\u6267\u884C",
        text_color: "grey",
        text_size: "notation",
      },
      vertical_align: "center",
      icon: {
        tag: "standard_icon",
        token: "down-small-ccm_outlined",
        color: "grey",
        size: "16px 16px",
      },
      icon_position: "right",
      icon_expanded_angle: -180,
    },
    border: { color: "grey", corner_radius: "5px" },
    vertical_spacing: "4px",
    padding: "8px 8px 8px 8px",
    elements: [],
  };
}

function buildToolUsePanel(steps: ToolUseTraceStep[], elapsedMs?: number, expanded = false): unknown {
  const parts: string[] = ["\u{1F6E0}\uFE0F \u5DE5\u5177\u6267\u884C"];
  if (steps.length > 0) {
    parts.push(`${steps.length} \u6B65`);
  }
  if (elapsedMs !== undefined && elapsedMs > 0) {
    parts.push(`(${formatElapsed(elapsedMs)})`);
  }

  const stepElements = steps.flatMap((step) => buildToolStepElements(step));

  return {
    tag: "collapsible_panel",
    expanded,
    header: {
      title: {
        tag: "plain_text",
        content: parts.join(" \u00B7 "),
        text_color: "grey",
        text_size: "notation",
      },
      vertical_align: "center",
      icon: {
        tag: "standard_icon",
        token: "down-small-ccm_outlined",
        color: "grey",
        size: "16px 16px",
      },
      icon_position: "right",
      icon_expanded_angle: -180,
    },
    border: { color: "grey", corner_radius: "5px" },
    vertical_spacing: "4px",
    padding: "8px 8px 8px 8px",
    elements: stepElements,
  };
}

function buildToolStepElements(step: ToolUseTraceStep): unknown[] {
  const elements: unknown[] = [buildToolStepTitle(step)];
  const detailElement = buildToolStepDetail(step);
  if (detailElement !== undefined) {
    elements.push(detailElement);
  }
  const outputElement = buildToolStepOutput(step);
  if (outputElement !== undefined) {
    elements.push(outputElement);
  }
  return elements;
}

function buildToolStepTitle(step: ToolUseTraceStep): unknown {
  const status = formatStepStatus(step.status);
  return {
    tag: "div",
    icon: {
      tag: "standard_icon",
      token: toolIcon(step.toolName),
      color: "grey",
    },
    text: {
      tag: "lark_md",
      content: `**${escapeMarkdown(step.toolName)}** \u00B7 <font color='${status.color}'>${status.label}</font>`,
      text_size: "notation",
    },
  };
}

function buildToolStepDetail(step: ToolUseTraceStep): unknown | undefined {
  const detail = extractStepDetail(step);
  if (detail === undefined) return undefined;
  return {
    tag: "div",
    margin: STEP_INDENT,
    text: {
      tag: "plain_text",
      content: detail,
      text_color: "grey",
      text_size: "notation",
    },
  };
}

function buildToolStepOutput(step: ToolUseTraceStep): unknown | undefined {
  const content = buildStepOutputMarkdown(step);
  if (content === undefined) return undefined;
  return {
    tag: "div",
    margin: STEP_INDENT,
    text: {
      tag: "lark_md",
      content,
      text_size: "notation",
    },
  };
}

function buildStepOutputMarkdown(step: ToolUseTraceStep): string | undefined {
  const lines: string[] = [];
  if (step.error !== undefined) {
    lines.push("**\u9519\u8BEF**");
    lines.push(formatCodeBlock(step.error, "text"));
  } else if (step.result !== undefined && step.result !== "") {
    const resultStr = typeof step.result === "string" ? step.result : JSON.stringify(step.result, null, 2);
    lines.push("**\u7ED3\u679C**");
    lines.push(formatCodeBlock(resultStr, typeof step.result === "string" ? "text" : "json"));
  }
  if (lines.length === 0) return undefined;
  return lines.join("\n");
}

function extractStepDetail(step: ToolUseTraceStep): string | undefined {
  if (step.input === undefined || step.input === null) return undefined;
  if (typeof step.input === "string") return truncateText(step.input, 200);
  const input = step.input as Record<string, unknown>;
  // Extract the most relevant field based on tool name
  const toolLower = step.toolName.toLowerCase().replace(/^mcp_/, "");
  if (toolLower === "bash" && typeof input.command === "string") {
    return truncateText(input.command, 200);
  }
  if (toolLower === "read" && typeof input.file_path === "string") {
    return input.file_path;
  }
  if (toolLower === "write" && typeof input.file_path === "string") {
    return input.file_path;
  }
  if (toolLower === "edit" && typeof input.file_path === "string") {
    return input.file_path;
  }
  if (toolLower === "glob" && typeof input.pattern === "string") {
    return input.pattern;
  }
  if (toolLower === "grep" && typeof input.pattern === "string") {
    return input.pattern;
  }
  // Fallback: show keys
  const keys = Object.keys(input);
  if (keys.length === 0) return undefined;
  return keys.slice(0, 4).join(", ");
}

// ---------------------------------------------------------------------------
// Reasoning panel
// ---------------------------------------------------------------------------

function buildReasoningPanel(reasoningText: string): unknown {
  return {
    tag: "collapsible_panel",
    expanded: false,
    header: {
      title: {
        tag: "markdown",
        content: "\u{1F4AD} \u601D\u8003",
        text_color: "grey",
      },
      vertical_align: "center",
      icon: {
        tag: "standard_icon",
        token: "down-small-ccm_outlined",
        size: "16px 16px",
      },
      icon_position: "follow_text",
      icon_expanded_angle: -180,
    },
    border: { color: "grey", corner_radius: "5px" },
    vertical_spacing: "8px",
    padding: "8px 8px 8px 8px",
    elements: [
      {
        tag: "markdown",
        content: truncateText(reasoningText, MAX_TEXT_LENGTH),
        text_size: "notation",
      },
    ],
  };
}

// ---------------------------------------------------------------------------
// Formatting helpers
// ---------------------------------------------------------------------------

function formatStepStatus(status: ToolUseTraceStep["status"]): { label: string; color: string } {
  switch (status) {
    case "running":
      return { label: "\u8FD0\u884C\u4E2D", color: "turquoise" };
    case "error":
      return { label: "\u5931\u8D25", color: "red" };
    case "success":
    default:
      return { label: "\u6210\u529F", color: "green" };
  }
}

function formatCodeBlock(content: string, language: "json" | "text"): string {
  const normalized = content.replace(/\r\n/g, "\n").trim();
  const fence = "`".repeat(Math.max(3, longestBacktickRun(normalized) + 1));
  return `${fence}${language}\n${normalized}\n${fence}`;
}

function longestBacktickRun(value: string): number {
  const matches = value.match(/`+/g);
  if (matches === null) return 0;
  return matches.reduce((max, run) => Math.max(max, run.length), 0);
}

function escapeMarkdown(value: string): string {
  return value.replace(/\\/g, "\\\\").replace(/([`*_{}[\]<>])/g, "\\$1");
}

function buildAnswerElements(
  text: string,
  contentSegments: readonly CardContentSegment[] | undefined,
): unknown[] {
  if (contentSegments !== undefined && contentSegments.length > 0) {
    const elements: unknown[] = [];
    let remaining = MAX_TEXT_LENGTH;
    for (const segment of contentSegments) {
      if (segment.type === "image") {
        elements.push({
          tag: "img",
          img_key: segment.imgKey,
          alt: { tag: "plain_text", content: segment.alt },
          mode: "fit_horizontal",
          preview: true,
        });
        continue;
      }
      if (segment.content === "" || remaining <= 0) continue;
      const slice = segment.content.length <= remaining
        ? segment.content
        : truncateText(segment.content, remaining);
      remaining -= slice.length;
      elements.push({
        tag: "markdown",
        content: slice,
      });
    }
    return elements;
  }
  if (text === "") return [];
  return [{
    tag: "markdown",
    content: truncateText(text, MAX_TEXT_LENGTH),
  }];
}

function truncateText(value: string, maxLength: number): string {
  return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
}

function formatElapsed(ms: number): string {
  if (ms < 1000) return `${ms} ms`;
  return `${(ms / 1000).toFixed(1)} s`;
}
