/**
 * Formats Codex command responses for safe chat display, including status,
 * lists, account summaries, and user-facing help text.
 */
import type { CodexComputerUseStatus } from "./app-server/computer-use.js";
import type { CodexAppServerModelListResult } from "./app-server/models.js";
import { isJsonObject, type JsonObject, type JsonValue } from "./app-server/protocol.js";
import {
  hasCodexRateLimitSnapshots,
  summarizeCodexAccountRateLimits,
  summarizeCodexRateLimits,
} from "./app-server/rate-limits.js";
import type { CodexAccountAuthOverview } from "./command-account.js";
import type { SafeValue } from "./command-rpc.js";

type CodexStatusProbes = {
  models: SafeValue<CodexAppServerModelListResult>;
  account: SafeValue<JsonValue | undefined>;
  limits: SafeValue<JsonValue | undefined>;
  mcps: SafeValue<JsonValue | undefined>;
  skills: SafeValue<JsonValue | undefined>;
};

/** Formats the combined `/codex status` probe result. */
export function formatCodexStatus(probes: CodexStatusProbes): string {
  const connected =
    probes.models.ok || probes.account.ok || probes.limits.ok || probes.mcps.ok || probes.skills.ok;
  const lines = [`Codex app-server: ${connected ? "connected" : "unavailable"}`];
  if (probes.models.ok) {
    lines.push(
      `Models: ${
        probes.models.value.models
          .map((model) => formatCodexDisplayText(model.id))
          .slice(0, 8)
          .join(", ") || "none"
      }`,
    );
  } else {
    lines.push(`Models: ${formatCodexDisplayText(probes.models.error)}`);
  }
  lines.push(
    `Account: ${
      probes.account.ok
        ? formatCodexAccountSummary(probes.account.value)
        : formatCodexDisplayText(probes.account.error)
    }`,
  );
  lines.push(
    `Rate limits: ${
      probes.limits.ok
        ? formatCodexRateLimitSummary(probes.limits.value)
        : formatCodexDisplayText(probes.limits.error)
    }`,
  );
  lines.push(
    `MCP servers: ${
      probes.mcps.ok
        ? summarizeArrayLike(probes.mcps.value)
        : formatCodexDisplayText(probes.mcps.error)
    }`,
  );
  lines.push(
    `Skills: ${
      probes.skills.ok
        ? summarizeCodexSkills(probes.skills.value)
        : formatCodexDisplayText(probes.skills.error)
    }`,
  );
  return lines.join("\n");
}

/** Formats Codex model-list results for `/codex models`. */
export function formatModels(result: CodexAppServerModelListResult): string {
  if (result.models.length === 0) {
    return "No Codex app-server models returned.";
  }
  const lines = [
    "Codex models:",
    ...result.models.map(
      (model) => `- ${formatCodexDisplayText(model.id)}${model.isDefault ? " (default)" : ""}`,
    ),
  ];
  if (result.truncated) {
    lines.push("- More models available; output truncated.");
  }
  return lines.join("\n");
}

/** Formats Codex thread-list responses with safe resume hints. */
export function formatThreads(response: JsonValue | undefined): string {
  const threads = extractArray(response);
  if (threads.length === 0) {
    return "No Codex threads returned.";
  }
  return [
    "Codex threads:",
    ...threads.slice(0, 10).map((thread) => {
      const record = isJsonObject(thread) ? thread : {};
      const id = readString(record, "threadId") ?? readString(record, "id") ?? "<unknown>";
      const title =
        readString(record, "title") ?? readString(record, "name") ?? readString(record, "summary");
      const details = [
        readString(record, "model"),
        readString(record, "cwd"),
        readString(record, "updatedAt") ?? readString(record, "lastUpdatedAt"),
      ].filter((value): value is string => Boolean(value));
      return `- ${formatCodexDisplayText(id)}${title ? ` - ${formatCodexDisplayText(title)}` : ""}${
        details.length > 0 ? ` (${details.map(formatCodexDisplayText).join(", ")})` : ""
      }\n  Resume: ${formatCodexResumeHint(id)}`;
    }),
  ].join("\n");
}

/** Formats account and rate-limit output for `/codex account`. */
export function formatAccount(
  account: SafeValue<JsonValue | undefined>,
  limits: SafeValue<JsonValue | undefined>,
  authOverview?: CodexAccountAuthOverview,
): string {
  if (authOverview) {
    return formatAccountAuthOverview(authOverview);
  }
  const formattedLimits = limits.ok
    ? formatCodexRateLimitDetails(limits.value)
    : formatCodexDisplayText(limits.error);
  const rateLimitBlock = formattedLimits.startsWith("Codex is ")
    ? formattedLimits
    : formattedLimits.includes("\n")
      ? `Rate limits:\n${formattedLimits}`
      : `Rate limits: ${formattedLimits}`;
  return [
    `Account: ${account.ok ? formatCodexAccountSummary(account.value) : formatCodexDisplayText(account.error)}`,
    rateLimitBlock,
  ].join("\n\n");
}

function formatAccountAuthOverview(overview: CodexAccountAuthOverview): string {
  const lines: string[] = [];
  if (overview.currentLine) {
    lines.push(overview.currentLine, "");
  }
  if (overview.subscriptionLabel) {
    lines.push(`Subscription  ${overview.subscriptionLabel}`);
    if (overview.subscriptionUsage) {
      lines.push(`  ${overview.subscriptionUsage}`);
    }
    lines.push("");
  }
  if (overview.rows.length > 0) {
    lines.push(overview.orderTitle);
    for (const [index, row] of overview.rows.entries()) {
      lines.push(`  ${index + 1}. ${row.label}   ${row.kind}   — ${formatAuthRowStatus(row)}`);
    }
  }
  while (lines.at(-1) === "") {
    lines.pop();
  }
  return lines.map(formatCodexAccountLine).join("\n");
}

function formatAuthRowStatus(row: CodexAccountAuthOverview["rows"][number]): string {
  return row.billingNote ? `${row.status} · ${row.billingNote}` : row.status;
}

/** Formats Codex Computer Use readiness and plugin/MCP availability. */
export function formatComputerUseStatus(status: CodexComputerUseStatus): string {
  const lines = [
    `Computer Use: ${status.ready ? "ready" : status.enabled ? "not ready" : "disabled"}`,
  ];
  lines.push(
    `Plugin: ${formatCodexDisplayText(status.pluginName)} (${computerUsePluginState(status)})`,
  );
  lines.push(
    `MCP server: ${formatCodexDisplayText(status.mcpServerName)}${
      status.mcpServerAvailable ? ` (${status.tools.length} tools)` : " (unavailable)"
    }`,
  );
  if (status.marketplaceName) {
    lines.push(`Marketplace: ${formatCodexDisplayText(status.marketplaceName)}`);
  }
  if (status.tools.length > 0) {
    lines.push(`Tools: ${status.tools.slice(0, 8).map(formatCodexDisplayText).join(", ")}`);
  }
  lines.push(formatCodexDisplayText(status.message));
  return lines.join("\n");
}

function computerUsePluginState(status: CodexComputerUseStatus): string {
  if (!status.installed) {
    return "not installed";
  }
  return status.pluginEnabled ? "installed" : "installed, disabled";
}

/** Formats generic array-like Codex app-server responses. */
export function formatList(response: JsonValue | undefined, label: string): string {
  const entries = extractArray(response);
  if (entries.length === 0) {
    return `${label}: none returned.`;
  }
  return [
    `${label}:`,
    ...entries.slice(0, 25).map((entry) => {
      const record = isJsonObject(entry) ? entry : {};
      return `- ${formatCodexDisplayText(
        readString(record, "name") ?? readString(record, "id") ?? JSON.stringify(entry),
      )}`;
    }),
  ].join("\n");
}

/** Formats Codex skills grouped by scope, omitting disabled entries. */
export function formatSkills(response: JsonValue | undefined): string {
  const groups = isJsonObject(response) && Array.isArray(response.data) ? response.data : [];
  if (groups.length === 0) {
    return "Codex skills: none returned.";
  }
  const lines = ["Codex skills:"];
  let renderedSkills = 0;
  let loadErrors = 0;
  for (const group of groups) {
    const record = isJsonObject(group) ? group : {};
    if (Array.isArray(record.errors)) {
      loadErrors += record.errors.length;
    }
    const skills = Array.isArray(record.skills) ? record.skills : [];
    if (skills.length === 0) {
      continue;
    }
    for (const skill of skills) {
      if (isJsonObject(skill) && skill.enabled === false) {
        continue;
      }
      lines.push(`- ${formatCodexSkillEntry(skill)}`);
      renderedSkills += 1;
    }
  }
  if (renderedSkills === 0) {
    if (loadErrors > 0) {
      return `Codex skills: none returned (${loadErrors} load ${
        loadErrors === 1 ? "error" : "errors"
      }).`;
    }
    return "Codex skills: none returned.";
  }
  return lines.join("\n");
}

function formatCodexSkillEntry(entry: JsonValue): string {
  const record = isJsonObject(entry) ? entry : {};
  const name = readString(record, "name") ?? "<unknown>";
  return `\`${formatCodexDisplayText(name)}\``;
}

const CODEX_RESUME_SAFE_THREAD_ID_PATTERN = /^[A-Za-z0-9._:-]+$/;

function formatCodexResumeHint(threadId: string): string {
  const safe = formatCodexTextForDisplay(threadId);
  if (!CODEX_RESUME_SAFE_THREAD_ID_PATTERN.test(safe)) {
    return "copy the thread id above and run /codex resume <thread-id>";
  }
  return `/codex resume ${safe}`;
}

/** Escapes Codex-originated text so it is safe to render in chat command output. */
export function formatCodexDisplayText(value: string): string {
  return escapeCodexChatText(formatCodexTextForDisplay(value));
}

function formatCodexAccountSummary(value: JsonValue | undefined): string {
  const safe = formatCodexTextForDisplay(summarizeAccount(value));
  return isLikelyEmailAddress(safe)
    ? escapeCodexChatTextPreservingAt(safe)
    : escapeCodexChatText(safe);
}

function formatCodexTextForDisplay(value: string): string {
  const safe = sanitizeCodexTextForDisplay(value).trim();
  return safe || "<unknown>";
}

function sanitizeCodexTextForDisplay(value: string): string {
  let safe = "";
  for (const character of value) {
    const codePoint = character.codePointAt(0);
    safe += codePoint != null && isUnsafeDisplayCodePoint(codePoint) ? "?" : character;
  }
  return safe;
}

function escapeCodexChatText(value: string): string {
  // Command output is public chat text. Escape markdown/control triggers and
  // mention characters so Codex data cannot ping users or inject formatting.
  return value
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll("@", "\uff20")
    .replaceAll("`", "\uff40")
    .replaceAll("[", "\uff3b")
    .replaceAll("]", "\uff3d")
    .replaceAll("(", "\uff08")
    .replaceAll(")", "\uff09")
    .replaceAll("*", "\u2217")
    .replaceAll("_", "\uff3f")
    .replaceAll("~", "\uff5e")
    .replaceAll("|", "\uff5c");
}

function escapeCodexChatTextPreservingAt(value: string): string {
  return escapeCodexChatText(value).replaceAll("\uff20", "@");
}

function formatCodexAccountLine(value: string): string {
  if (value === "") {
    return "";
  }
  const safe = sanitizeCodexTextForDisplay(value).trimEnd();
  if (!safe.trim()) {
    return "";
  }
  const emailPattern = /[^\s@<>()[\]`]+@[^\s@<>()[\]`]+\.[^\s@<>()[\]`]+/gu;
  let formatted = "";
  let lastIndex = 0;
  for (const match of safe.matchAll(emailPattern)) {
    const index = match.index ?? 0;
    formatted += escapeCodexChatText(safe.slice(lastIndex, index));
    formatted += escapeCodexChatTextPreservingAt(match[0]);
    lastIndex = index + match[0].length;
  }
  formatted += escapeCodexChatText(safe.slice(lastIndex));
  return formatted;
}

function isLikelyEmailAddress(value: string): boolean {
  return /^[^\s@<>()[\]`]+@[^\s@<>()[\]`]+\.[^\s@<>()[\]`]+$/.test(value);
}

function isUnsafeDisplayCodePoint(codePoint: number): boolean {
  return (
    codePoint <= 0x001f ||
    (codePoint >= 0x007f && codePoint <= 0x009f) ||
    codePoint === 0x00ad ||
    codePoint === 0x061c ||
    codePoint === 0x180e ||
    (codePoint >= 0x200b && codePoint <= 0x200f) ||
    (codePoint >= 0x202a && codePoint <= 0x202e) ||
    (codePoint >= 0x2060 && codePoint <= 0x206f) ||
    codePoint === 0xfeff ||
    (codePoint >= 0xfff9 && codePoint <= 0xfffb) ||
    (codePoint >= 0xe0000 && codePoint <= 0xe007f)
  );
}

/** Builds the portable `/codex` command help text. */
export function buildHelp(): string {
  return [
    "Codex commands:",
    "- /codex status",
    "- /codex models",
    "- /codex threads [filter]",
    "- /codex sessions --host <node> [filter]",
    "- /codex resume <thread-id>",
    "- /codex resume <session-id> --host <node> --bind here",
    "- /codex bind [thread-id] [--cwd <path>] [--model <model>] [--provider <provider>]",
    "- /codex binding",
    "- /codex stop",
    "- /codex steer <message>",
    "- /codex model [model]",
    "- /codex fast [on|off|status]",
    "- /codex permissions [default|yolo|status]",
    "- /codex detach",
    "- /codex compact",
    "- /codex review",
    "- /codex diagnostics [note]",
    "- /codex computer-use [status|install]",
    "- /codex account",
    "- /codex mcp",
    "- /codex skills",
    "- /codex plugins [list|enable|disable]",
  ].join("\n");
}

function summarizeAccount(value: JsonValue | undefined): string {
  if (!isJsonObject(value)) {
    return "unavailable";
  }
  const account = isJsonObject(value.account) ? value.account : value;
  const accountType = readString(account, "type");
  if (accountType === "amazonBedrock") {
    return "Amazon Bedrock";
  }
  return (
    readString(account, "email") ??
    readString(account, "accountEmail") ??
    readString(account, "planType") ??
    readString(account, "id") ??
    "available"
  );
}

function summarizeArrayLike(value: JsonValue | undefined): string {
  const entries = extractArray(value);
  if (entries.length === 0) {
    return "none returned";
  }
  return `${entries.length}`;
}

function summarizeCodexSkills(value: JsonValue | undefined): string {
  const groups = isJsonObject(value) && Array.isArray(value.data) ? value.data : [];
  if (groups.length === 0) {
    return "none returned";
  }
  let enabledSkills = 0;
  let loadErrors = 0;
  for (const group of groups) {
    if (!isJsonObject(group)) {
      continue;
    }
    if (Array.isArray(group.errors)) {
      loadErrors += group.errors.length;
    }
    if (!Array.isArray(group.skills)) {
      continue;
    }
    enabledSkills += group.skills.filter(
      (skill) => !isJsonObject(skill) || skill.enabled !== false,
    ).length;
  }
  if (enabledSkills > 0) {
    return `${enabledSkills}`;
  }
  if (loadErrors > 0) {
    return `none returned (${loadErrors} load ${loadErrors === 1 ? "error" : "errors"})`;
  }
  return "none returned";
}

function formatCodexRateLimitSummary(value: JsonValue | undefined): string {
  const summary = summarizeCodexRateLimits(value);
  if (summary) {
    return formatCodexDisplayText(summary);
  }
  return formatCodexDisplayText(
    hasCodexRateLimitSnapshots(value) ? "none returned" : summarizeRateLimits(value),
  );
}

function formatCodexRateLimitDetails(value: JsonValue | undefined): string {
  const lines = summarizeCodexAccountRateLimits(value);
  if (!lines) {
    return formatCodexDisplayText(
      hasCodexRateLimitSnapshots(value) ? "none returned" : summarizeRateLimits(value),
    );
  }
  return lines.map(formatCodexDisplayText).join("\n");
}

function summarizeRateLimits(value: JsonValue | undefined): string {
  const entries = extractArray(value);
  if (entries.length > 0) {
    const count = entries.filter(isMeaningfulRateLimitSnapshot).length;
    return count > 0 ? `${count}` : "none returned";
  }
  if (!isJsonObject(value)) {
    return "none returned";
  }
  const keyed = value.rateLimitsByLimitId;
  if (isJsonObject(keyed)) {
    const count = Object.values(keyed).filter(isMeaningfulRateLimitSnapshot).length;
    if (count > 0) {
      return `${count}`;
    }
  }
  return isMeaningfulRateLimitSnapshot(value.rateLimits) ? "1" : "none returned";
}

function isMeaningfulRateLimitSnapshot(value: JsonValue | undefined): boolean {
  if (!isJsonObject(value)) {
    return false;
  }
  const reachedType =
    readString(value, "rateLimitReachedType") ?? readString(value, "rate_limit_reached_type");
  if (reachedType) {
    return true;
  }
  return ["primary", "secondary"].some((key) => {
    const window = value[key];
    return isJsonObject(window) && Object.values(window).some((entry) => entry != null);
  });
}

function extractArray(value: JsonValue | undefined): JsonValue[] {
  if (Array.isArray(value)) {
    return value;
  }
  if (!isJsonObject(value)) {
    return [];
  }
  for (const key of ["data", "items", "threads", "models", "skills", "servers", "rateLimits"]) {
    const child = value[key];
    if (Array.isArray(child)) {
      return child;
    }
  }
  return [];
}

/** Reads and trims a non-empty string field from a JSON object. */
export function readString(record: JsonObject, key: string): string | undefined {
  const value = record[key];
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
