import { createHash, randomBytes } from "node:crypto";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { rootPath } from "./config";

export interface JsonEnvelope<T> {
  ok: boolean;
  command: string;
  data?: T;
  error?: unknown;
}

export interface RenderedCliResult {
  ok: boolean;
  command: string;
  renderedText: string;
  contentType: "text/plain" | "application/json" | "application/yaml";
  projection?: Record<string, unknown>;
}

export interface CliInputErrorOptions {
  code: string;
  reason?: string;
  mutation?: false;
  argument?: string;
  usage?: string | string[];
  supported?: string[];
  hint?: string;
  correctionCommands?: string[];
}

/**
 * 可预测的 CLI 输入错误。内部异常不能包装成此类型，以免实现故障被静默降级。
 */
export class CliInputError extends Error {
  readonly code: string;
  readonly reason?: string;
  readonly mutation?: false;
  readonly argument?: string;
  readonly usage?: string | string[];
  readonly supported?: string[];
  readonly hint?: string;
  readonly correctionCommands?: string[];

  constructor(message: string, options: CliInputErrorOptions) {
    super(message);
    this.name = "CliInputError";
    this.code = options.code;
    this.reason = options.reason;
    this.mutation = options.mutation;
    this.argument = options.argument;
    this.usage = options.usage;
    this.supported = options.supported;
    this.hint = options.hint;
    this.correctionCommands = options.correctionCommands;
  }
}

const CLI_OUTPUT_CONFIG_RELATIVE_PATH = "config/unidesk-cli.yaml";
const CLI_OUTPUT_CONFIG_PATH = rootPath("config", "unidesk-cli.yaml");
const EMERGENCY_OUTPUT_DUMP_THRESHOLD_BYTES = 10 * 1024;
const EMERGENCY_OUTPUT_DUMP_DIR = join(tmpdir(), "unidesk-cli-output");

export interface CliOutputPolicy {
  maxStdoutBytes: number;
  dumpDir: string;
  includePreview: boolean;
  warning: string;
  configPath: string;
  configError?: string;
}

let cachedOutputPolicy: CliOutputPolicy | null = null;

function isEpipe(error: unknown): boolean {
  return typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === "EPIPE";
}

process.stdout.on("error", (error) => {
  if (isEpipe(error)) process.exit(0);
  throw error;
});

process.stderr.on("error", (error) => {
  if (isEpipe(error)) process.exit(0);
  throw error;
});

export function emitJson<T>(command: string, data: T, ok = true): void {
  const safeCommand = redactSensitiveCommandArgs(command);
  const envelope: JsonEnvelope<T> = { ok, command: safeCommand, data };
  safeStdoutWrite(renderEnvelope(safeCommand, envelope));
}

export function isRenderedCliResult(value: unknown): value is RenderedCliResult {
  return typeof value === "object"
    && value !== null
    && typeof (value as { renderedText?: unknown }).renderedText === "string"
    && typeof (value as { command?: unknown }).command === "string"
    && typeof (value as { ok?: unknown }).ok === "boolean";
}

export function emitText(text: string, command = "text"): void {
  safeStdoutWrite(renderTextOutput(command, text));
}

export function emitError(command: string, error: unknown): void {
  const safeCommand = redactSensitiveCommandArgs(command);
  if (error instanceof CliInputError && !cliDebugEnabled() && !cliMachineOutputRequested()) {
    safeStdoutWrite(renderTextOutput(safeCommand, renderCliInputErrorText(safeCommand, error)));
    return;
  }
  const payload = normalizeErrorPayload(command, error);
  const envelope: JsonEnvelope<never> = { ok: false, command: safeCommand, error: payload };
  safeStdoutWrite(renderEnvelope(safeCommand, envelope));
}

function renderCliInputErrorText(command: string, error: CliInputError): string {
  const usage = Array.isArray(error.usage) ? error.usage.join(" | ") : error.usage;
  return [
    `ERROR  cli-input/${compactErrorLine(error.code)}`,
    `command: ${compactErrorLine(command)}`,
    `message: ${compactErrorLine(error.message)}`,
    ...(error.reason === undefined ? [] : [`reason: ${compactErrorLine(error.reason)}`]),
    ...(error.mutation === undefined ? [] : [`mutation: ${String(error.mutation)}`]),
    ...(error.argument === undefined ? [] : [`argument: ${compactErrorLine(error.argument)}`]),
    ...(usage === undefined ? [] : [`usage: ${compactErrorLine(usage)}`]),
    ...(error.supported === undefined ? [] : [`supported: ${compactErrorLine(error.supported.join(" | "))}`]),
    ...(error.hint === undefined ? [] : [`hint: ${compactErrorLine(error.hint)}`]),
    ...(error.correctionCommands === undefined ? [] : error.correctionCommands.map((item) => `correction: ${compactErrorLine(item)}`)),
    "debug: UNIDESK_CLI_DEBUG=1",
  ].join("\n");
}

function compactErrorLine(value: string): string {
  const compact = value.replace(/\s+/gu, " ").trim();
  return compact.length > 480 ? `${compact.slice(0, 477)}...` : compact;
}

function redactSensitiveCommandArgs(command: string): string {
  return command
    .replace(/(--(?:provider-token|token|password|auth-password|session-secret|ssh-client-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/giu, "$1<redacted>")
    .replace(/\b((?:PROVIDER_TOKEN|UNIDESK_PROVIDER_TOKEN|UNIDESK_SSH_CLIENT_TOKEN|AUTH_PASSWORD|SESSION_SECRET)=)("[^"]*"|'[^']*'|\S+)/gu, "$1<redacted>");
}

export function readCliOutputPolicy(): CliOutputPolicy {
  return cliOutputPolicy();
}

function normalizeErrorPayload(command: string, error: unknown): Record<string, unknown> {
  const message = error instanceof Error ? error.message : String(error);
  if (error instanceof CliInputError) return cliInputErrorPayload(error);
  const parsed = parseStructuredErrorMessage(command, message);
  if (parsed !== null) return parsed;
  if (shouldSuppressStack(command) || shouldSuppressStack(commandPrefixFromMessage(message))) {
    return { message };
  }
  if (error instanceof Error) {
    const structured = structuredCliErrorPayload(error, message);
    if (structured !== null) return structured;
    const fileTransfer = sshFileTransferErrorDetails(error);
    if (fileTransfer?.code === "text-transfer-discouraged") {
      return {
        name: error.name,
        message,
        ...fileTransfer,
        debug: false,
      };
    }
    const debug = cliDebugEnabled();
    return {
      name: error.name,
      message,
      ...(fileTransfer ?? {}),
      stack: error.stack ?? null,
      ...(debug ? { debug: true } : {}),
    };
  }
  return { message };
}

function cliInputErrorPayload(error: CliInputError): Record<string, unknown> {
  const debug = cliDebugEnabled();
  return {
    name: error.name,
    code: error.code,
    level: "error",
    kind: "cli-input",
    message: error.message,
    retryable: false,
    ...(error.reason !== undefined ? { reason: error.reason } : {}),
    ...(error.mutation !== undefined ? { mutation: error.mutation } : {}),
    ...(error.argument !== undefined ? { argument: error.argument } : {}),
    ...(error.usage !== undefined ? { usage: error.usage } : {}),
    ...(error.supported !== undefined ? { supported: error.supported } : {}),
    ...(error.hint !== undefined ? { hint: error.hint } : {}),
    ...(error.correctionCommands !== undefined ? { correctionCommands: error.correctionCommands } : {}),
    ...(debug ? { debug: true, stack: error.stack ?? null } : { debug: false }),
  };
}

function cliMachineOutputRequested(argv = process.argv.slice(2)): boolean {
  for (let index = 0; index < argv.length; index += 1) {
    if (["--json", "--full", "--raw"].includes(argv[index] ?? "")) return true;
    if (argv[index] === "-o" && argv[index + 1] === "json") return true;
  }
  return false;
}

function cliDebugEnabled(): boolean {
  return process.env.UNIDESK_CLI_DEBUG === "1"
    || process.env.UNIDESK_CLI_FULL_ERROR === "1"
    || process.env.UNIDESK_CLI_RAW_ERROR === "1";
}

function structuredCliErrorPayload(error: Error, message: string): Record<string, unknown> | null {
  const record = error as Error & Record<string, unknown>;
  if (!Object.prototype.hasOwnProperty.call(record, "replacementExamples") && !Object.prototype.hasOwnProperty.call(record, "migrationHint")) return null;
  const payload: Record<string, unknown> = {
    name: error.name,
    message,
  };
  for (const key of ["code", "level", "entrypoint", "route", "operation", "replacementExamples", "migrationHint", "note"]) {
    const value = record[key];
    if (value !== undefined) payload[key] = value;
  }
  return payload;
}

function sshFileTransferErrorDetails(error: Error): Record<string, unknown> | null {
  if (error.name !== "SshFileTransferError") return null;
  const details = (error as Error & { details?: unknown }).details;
  if (typeof details !== "object" || details === null || Array.isArray(details)) return null;
  const code = (details as Record<string, unknown>).code;
  return { ...(typeof code === "string" ? { code } : {}), details };
}

function parseStructuredErrorMessage(command: string, message: string): Record<string, unknown> | null {
  if (!shouldSuppressStack(command) && !shouldSuppressStack(commandPrefixFromMessage(message))) return null;
  const jsonStart = message.indexOf("{");
  if (jsonStart === -1) return null;
  try {
    const parsed = JSON.parse(message.slice(jsonStart)) as unknown;
    if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
      const record = parsed as Record<string, unknown>;
      return { message: message.slice(0, jsonStart).trim().replace(/[;:]$/u, ""), ...record };
    }
  } catch {
    return null;
  }
  return null;
}

function commandPrefixFromMessage(message: string): string {
  return message.split(/\s+/u).slice(0, 2).join(" ");
}

function shouldSuppressStack(prefix: string): boolean {
  if (process.env.UNIDESK_CLI_DEBUG === "1" || process.env.UNIDESK_CLI_FULL_ERROR === "1" || process.env.UNIDESK_CLI_RAW_ERROR === "1") return false;
  return prefix === "codex tasks" || prefix.startsWith("codex tasks ") || prefix === "codex unread" || prefix.startsWith("codex unread ");
}

function renderEnvelope<T>(command: string, envelope: JsonEnvelope<T>): string {
  const fullText = `${JSON.stringify(envelope, null, 2)}\n`;
  const policy = cliOutputPolicy();
  const trigger = outputDumpTrigger(fullText, policy, "json");
  if (trigger === null) return fullText;

  const persistDump = explicitOutputDumpRequested();
  const dump = persistDump ? dumpLargeOutput(command, fullText, "json", policy) : null;
  const compactPayload = {
    outputTruncated: true,
    reason: trigger.reason,
    warning: policy.warning,
    message: persistDump
      ? "Explicit full/raw output exceeded the stdout budget and was written to a protected temporary dump."
      : "Default output exceeded the stdout budget; no temporary dump was created. Use the narrow query below, or rerun with explicit --full/--raw only when complete disclosure is required.",
    disclosurePolicy: disclosurePolicy(policy, persistDump),
    trigger,
    ...(dump === null ? {} : { dump }),
    summary: summarizeEnvelope(envelope),
    next: oversizedOutputNext(envelope),
  };
  const compactEnvelope: JsonEnvelope<Record<string, unknown>> = envelope.ok
    ? { ok: envelope.ok, command: envelope.command, data: compactPayload }
    : { ok: envelope.ok, command: envelope.command, error: compactPayload };
  return `${JSON.stringify(compactEnvelope, null, 2)}\n`;
}

function outputDumpTrigger(text: string, policy: CliOutputPolicy, content: "json" | "text"): Record<string, unknown> | null {
  if (process.env.UNIDESK_CLI_OUTPUT_DUMP_DISABLED === "1") return null;
  const bytes = Buffer.byteLength(text, "utf8");
  if (bytes > policy.maxStdoutBytes) {
    return {
      reason: `stdout-${content}-bytes-exceeded-threshold`,
      thresholdBytes: policy.maxStdoutBytes,
      observedBytes: bytes,
    };
  }
  return null;
}

function renderTextOutput(command: string, text: string): string {
  const fullText = text.endsWith("\n") ? text : `${text}\n`;
  const policy = cliOutputPolicy();
  const trigger = outputDumpTrigger(fullText, policy, "text");
  if (trigger === null) return fullText;
  const persistDump = explicitOutputDumpRequested();
  const dump = persistDump ? dumpLargeOutput(command, fullText, "txt", policy) : null;
  const payload: JsonEnvelope<Record<string, unknown>> = {
    ok: true,
    command,
    data: {
      outputTruncated: true,
      reason: trigger.reason,
      warning: policy.warning,
      message: persistDump
        ? "Explicit full/raw text exceeded the stdout budget and was written to a protected temporary dump."
        : "Default text exceeded the stdout budget; no temporary dump was created. Rerun a narrower query, or request explicit --full/--raw disclosure.",
      disclosurePolicy: disclosurePolicy(policy, persistDump),
      trigger,
      ...(dump === null ? {} : { dump }),
      next: [
        "Use the command's semantic summary, page cursor, stable id, or id-specific detail query before requesting complete output.",
        "Use --full or --raw only when the complete one-off payload is intentionally required.",
      ],
    },
  };
  return `${JSON.stringify(payload, null, 2)}\n`;
}

function explicitOutputDumpRequested(argv = process.argv.slice(2)): boolean {
  return argv.some((arg) => arg === "--full" || arg === "--raw");
}

function oversizedOutputNext(envelope: JsonEnvelope<unknown>): string[] {
  const source = typeof envelope.data === "object" && envelope.data !== null
    ? envelope.data as Record<string, unknown>
    : typeof envelope.error === "object" && envelope.error !== null
      ? envelope.error as Record<string, unknown>
      : {};
  const repo = typeof source.repo === "string" ? source.repo : null;
  const issue = recordOrNull(source.issue);
  const pullRequest = recordOrNull(source.pullRequest);
  if (repo !== null && typeof issue?.number === "number") {
    return [
      `trans gh:/${repo}/issue/${issue.number} cat`,
      `bun scripts/cli.ts gh issue comments ${issue.number} --repo ${repo} --limit 8`,
    ];
  }
  if (repo !== null && typeof pullRequest?.number === "number") {
    return [
      `trans gh:/${repo}/pr/${pullRequest.number} cat`,
      `bun scripts/cli.ts gh pr review-plan ${pullRequest.number} --repo ${repo}`,
    ];
  }
  return [
    "Use the command's semantic summary, page cursor, stable id, or id-specific detail query; improve the command when none is available.",
    "Use --full or --raw only when the complete one-off payload is intentionally required.",
  ];
}

function dumpLargeOutput(command: string, text: string, extension: "json" | "txt", policy: CliOutputPolicy): Record<string, unknown> {
  mkdirSync(policy.dumpDir, { recursive: true, mode: 0o700 });
  const timestamp = new Date().toISOString().replace(/[:.]/gu, "-");
  const suffix = randomBytes(4).toString("hex");
  const slug = command.replace(/[^A-Za-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 80) || "command";
  const path = join(policy.dumpDir, `${timestamp}-${process.pid}-${suffix}-${slug}.${extension}`);
  writeFileSync(path, text, { encoding: "utf8", mode: 0o600 });
  const dump: Record<string, unknown> = {
    path,
    configPath: policy.configPath,
    thresholdBytes: policy.maxStdoutBytes,
    bytes: Buffer.byteLength(text, "utf8"),
    chars: text.length,
    lines: countLines(text),
    sha256: createHash("sha256").update(text).digest("hex"),
    contentType: extension === "json" ? "application/json" : "text/plain",
    readCommands: {
      full: `cat ${JSON.stringify(path)}`,
      head: `sed -n '1,80p' ${JSON.stringify(path)}`,
      tail: `tail -80 ${JSON.stringify(path)}`,
    },
  };
  if (policy.configError !== undefined) dump.configWarning = policy.configError;
  if (policy.includePreview) {
    const previewChars = Math.min(1200, Math.max(200, Math.floor(policy.maxStdoutBytes / 8)));
    dump.preview = {
      headChars: previewChars,
      tailChars: previewChars,
      head: text.slice(0, previewChars),
      tail: text.slice(Math.max(0, text.length - previewChars)),
    };
  }
  return dump;
}

function countLines(text: string): number {
  if (text.length === 0) return 0;
  const lineBreaks = text.match(/\r\n|\r|\n/gu)?.length ?? 0;
  return lineBreaks + (/(\r\n|\r|\n)$/u.test(text) ? 0 : 1);
}

function summarizeEnvelope(envelope: JsonEnvelope<unknown>): Record<string, unknown> {
  const source = typeof envelope.data === "object" && envelope.data !== null
    ? envelope.data as Record<string, unknown>
    : typeof envelope.error === "object" && envelope.error !== null
      ? envelope.error as Record<string, unknown>
      : {};
  const summaryKeys = [
    "ok",
    "command",
    "repo",
    "number",
    "state",
    "stateDetail",
    "title",
    "url",
    "count",
    "limit",
    "degradedReason",
    "runnerDisposition",
    "target",
    "path",
    "status",
    "statusCode",
    "lookbackMinutes",
    "candidateTraceCount",
    "scannedTraceCount",
    "matchedTraceCount",
    "traceCount",
    "errorSpanCount",
    "serviceCount",
  ];
  const summary: Record<string, unknown> = {
    ok: envelope.ok,
    command: envelope.command,
  };
  for (const key of summaryKeys) {
    if (key in source) summary[key] = source[key];
  }
  summary.topLevelKeys = Object.keys(source).slice(0, 40);
  const arrayCounts = summarizeArrayCounts(source);
  if (Object.keys(arrayCounts).length > 0) summary.arrayCounts = arrayCounts;
  const debugDispatch = summarizeDebugDispatch(source);
  if (debugDispatch !== null) summary.debugDispatch = debugDispatch;
  const providerTriage = summarizeProviderTriage(source);
  if (providerTriage !== null) summary.providerTriage = providerTriage;
  const issue = source.issue;
  if (typeof issue === "object" && issue !== null) {
    const issueRecord = issue as Record<string, unknown>;
    summary.issue = pickSummary(issueRecord, ["number", "title", "state", "url", "bodyChars", "commentCount"]);
  }
  const issueCommentsMigration = summarizeIssueCommentsMigration(source);
  if (issueCommentsMigration !== null) summary.issueCommentsMigration = issueCommentsMigration;
  const pullRequest = source.pullRequest;
  if (typeof pullRequest === "object" && pullRequest !== null) {
    const prRecord = pullRequest as Record<string, unknown>;
    summary.pullRequest = pickSummary(prRecord, ["number", "title", "state", "stateDetail", "url", "draft", "merged", "mergedAt"]);
  }
  return summary;
}

function disclosurePolicy(policy: CliOutputPolicy, dumpCreated: boolean): Record<string, unknown> {
  return {
    configPath: policy.configPath,
    maxStdoutBytes: policy.maxStdoutBytes,
    dumpDir: policy.dumpDir,
    includePreview: policy.includePreview,
    defaultCreatesDump: false,
    dumpCreated,
    recommendation: "Prefer k8s-style concise summaries/tables by default; expose full data through explicit --full/--raw/id-specific drill-down commands instead of large stdout.",
    ...(policy.configError === undefined ? {} : { configWarning: policy.configError }),
  };
}

function summarizeArrayCounts(source: Record<string, unknown>): Record<string, number> {
  const result: Record<string, number> = {};
  for (const [key, value] of Object.entries(source)) {
    if (Array.isArray(value)) result[key] = value.length;
  }
  return result;
}

function summarizeIssueCommentsMigration(source: Record<string, unknown>): Record<string, unknown> | null {
  const command = typeof source.command === "string" ? source.command : null;
  if (command !== "issue view" && command !== "issue read") return null;
  const jsonFields = Array.isArray(source.jsonFields) ? source.jsonFields.filter((value): value is string => typeof value === "string") : [];
  if (!jsonFields.includes("comments")) return null;
  const compatibility = recordOrNull(source.compatibility);
  const readCommands = recordOrNull(compatibility?.readCommands);
  const disclosure = recordOrNull(source.disclosure);
  const preferredCommand = stringOrNull(compatibility?.preferredCommand)
    ?? stringOrNull(readCommands?.preferred)
    ?? stringOrNull(disclosure?.preferredCommand);
  return {
    legacyCommentsPath: stringOrNull(compatibility?.commentsPath) ?? ".data.json.comments",
    preferredCommand,
    migrationHint: stringOrNull(compatibility?.migrationHint) ?? stringOrNull(disclosure?.migrationHint),
  };
}

function summarizeDebugDispatch(source: Record<string, unknown>): Record<string, unknown> | null {
  const dispatch = recordOrNull(source.dispatch);
  const wait = recordOrNull(source.wait);
  if (dispatch === null && wait === null) return null;
  const dispatchBody = recordOrNull(dispatch?.body);
  const waitTask = recordOrNull(wait?.task);
  const waitPayload = recordOrNull(waitTask?.payload);
  const waitResult = recordOrNull(waitTask?.result);
  const result: Record<string, unknown> = {
    dispatch: dispatch === null ? null : {
      ...pickSummary(dispatch, ["ok", "status", "responseBytesRead", "responseContentLength", "responseTruncated"]),
      body: dispatchBody === null ? null : pickSummary(dispatchBody, ["ok", "providerOnline", "status", "taskId"]),
    },
    wait: wait === null ? null : pickSummary(wait, ["ok", "timeoutMs"]),
    task: waitTask === null ? null : {
      ...pickSummary(waitTask, ["id", "status", "command", "providerId", "createdAt", "updatedAt"]),
      payload: waitPayload === null ? null : pickSummary(waitPayload, ["mode", "source"]),
    },
  };
  const providerUpgrade = summarizeProviderUpgradeResult(waitResult);
  if (providerUpgrade !== null) result.providerUpgrade = providerUpgrade;
  return result;
}

function summarizeProviderUpgradeResult(result: Record<string, unknown> | null): Record<string, unknown> | null {
  if (result === null) return null;
  const schedulerResult = recordOrNull(result.schedulerResult);
  const plan = recordOrNull(result.plan) ?? recordOrNull(schedulerResult?.plan);
  const finalPromotedContainer = recordOrNull(result.finalPromotedContainer)
    ?? recordOrNull(schedulerResult?.finalPromotedContainer)
    ?? recordOrNull(plan?.finalPromotedContainer);
  const heartbeat = recordOrNull(result.heartbeat);
  const heartbeatConfirmation = recordOrNull(plan?.finalPromotedHeartbeatConfirmation)
    ?? recordOrNull(schedulerResult?.finalPromotedHeartbeatConfirmation);
  const summary: Record<string, unknown> = {
    ...pickSummary(result, ["ok", "reason", "taskId", "providerId", "targetProviderGatewayVersion", "candidateValidationWasTerminal"]),
  };
  const mode = result.mode ?? schedulerResult?.mode;
  if (mode !== undefined) summary.mode = mode;
  const message = stringOrNull(result.message) ?? stringOrNull(schedulerResult?.message);
  if (message !== null) summary.message = shortText(message, 240);
  const providerGatewayVersion = result.providerGatewayVersion ?? schedulerResult?.providerGatewayVersion ?? plan?.providerGatewayVersion;
  if (providerGatewayVersion !== undefined) summary.providerGatewayVersion = providerGatewayVersion;
  if (schedulerResult !== null) {
    summary.schedulerResult = {
      ...pickSummary(schedulerResult, [
        "mode",
        "message",
        "providerId",
        "providerName",
        "providerGatewayVersion",
        "targetProviderGatewayVersion",
        "updaterContainerId",
        "candidateValidationIsNotTerminalSuccess",
        "terminalStatusManagedByBackendCore",
      ]),
    };
  }
  if (plan !== null) {
    summary.plan = {
      ...pickSummary(plan, [
        "taskId",
        "providerId",
        "providerName",
        "policy",
        "hostProjectRoot",
        "workspace",
        "workspaceStateMount",
        "providerGatewayName",
        "providerGatewayVersion",
        "targetProviderGatewayName",
        "targetProviderGatewayVersion",
        "runnerImage",
        "updaterName",
        "candidateName",
      ]),
      compose: pickSummary(recordOrNull(plan.compose) ?? {}, ["composeFile", "envFile", "project", "service"]),
      heartbeatConfirmation: heartbeatConfirmation === null ? null : pickSummary(heartbeatConfirmation, [
        "providerId",
        "required",
        "heartbeatTimeoutMs",
        "targetProviderGatewayVersion",
        "finalContainerMustNotEqualCandidate",
      ]),
    };
  }
  if (finalPromotedContainer !== null) {
    summary.finalPromotedContainer = pickSummary(finalPromotedContainer, [
      "containerId",
      "containerName",
      "version",
      "restartPolicy",
      "pidMode",
      "heartbeatTimestamp",
      "containerIdUnavailableReason",
      "versionUnavailableReason",
      "restartPolicyUnavailableReason",
      "pidModeUnavailableReason",
      "heartbeatTimestampUnavailableReason",
    ]);
  }
  if (heartbeat !== null) {
    const heartbeatSummary = pickSummary(heartbeat, ["providerId", "version", "containerId", "containerName", "restartPolicy", "pidMode", "timestamp"]);
    if (Object.keys(heartbeatSummary).length > 0) summary.heartbeat = heartbeatSummary;
  }
  return summary;
}

function summarizeProviderTriage(source: Record<string, unknown>): Record<string, unknown> | null {
  if (!("decision" in source) || !("providerId" in source) || !Array.isArray(source.signals)) return null;
  const signals = source.signals.map(recordOrNull).filter((item): item is Record<string, unknown> => item !== null);
  const issueSignals = signals
    .filter((item) => item.status === "failed" || item.status === "degraded" || item.status === "unknown")
    .sort((left, right) => signalStatusRank(left.status) - signalStatusRank(right.status));
  const previewSignals = (issueSignals.length > 0 ? issueSignals : signals).slice(0, 8);
  return {
    ...pickSummary(source, ["ok", "providerId", "decision", "scope", "retryable", "blockingDisposition", "observedAt"]),
    failedScopes: boundedStringArray(source.failedScopes, 12),
    degradedScopes: boundedStringArray(source.degradedScopes, 12),
    healthyScopes: boundedStringArray(source.healthyScopes, 12),
    failedIndependentScopes: boundedStringArray(source.failedIndependentScopes, 12),
    healthyIndependentScopes: boundedStringArray(source.healthyIndependentScopes, 12),
    signalCounts: providerSignalCounts(signals, previewSignals.length),
    recommendedCrossChecks: boundedStringArray(source.recommendedCrossChecks, 8),
    rationale: boundedStringArray(source.rationale, 8),
    signalsPreview: previewSignals.map((item) => ({
      ...pickSummary(item, ["id", "scope", "status", "independentPath", "observedAt"]),
      summary: shortText(stringOrNull(item.summary) ?? "", 220),
    })),
  };
}

function providerSignalCounts(signals: Record<string, unknown>[], returned: number): Record<string, unknown> {
  const counts = {
    total: signals.length,
    returned,
    ok: 0,
    degraded: 0,
    failed: 0,
    unknown: 0,
  };
  for (const signal of signals) {
    if (signal.status === "ok") counts.ok += 1;
    else if (signal.status === "degraded") counts.degraded += 1;
    else if (signal.status === "failed") counts.failed += 1;
    else counts.unknown += 1;
  }
  return {
    ...counts,
    omittedSignals: Math.max(0, signals.length - returned),
  };
}

function signalStatusRank(status: unknown): number {
  if (status === "failed") return 0;
  if (status === "degraded") return 1;
  if (status === "unknown") return 2;
  if (status === "ok") return 3;
  return 4;
}

function boundedStringArray(value: unknown, limit: number): Record<string, unknown> {
  const items = Array.from(new Set((Array.isArray(value) ? value : []).map((item) => String(item ?? "")).filter(Boolean)));
  return {
    items: items.slice(0, limit),
    count: items.length,
    truncated: items.length > limit,
    omitted: Math.max(0, items.length - limit),
  };
}

function recordOrNull(value: unknown): Record<string, unknown> | null {
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}

function stringOrNull(value: unknown): string | null {
  return typeof value === "string" ? value : null;
}

function shortText(value: string, maxChars: number): string {
  return value.length <= maxChars ? value : `${value.slice(0, Math.max(0, maxChars - 3))}...`;
}

function pickSummary(source: Record<string, unknown>, keys: string[]): Record<string, unknown> {
  const result: Record<string, unknown> = {};
  for (const key of keys) {
    if (key in source) result[key] = source[key];
  }
  return result;
}

function safeStdoutWrite(text: string): void {
  try {
    process.stdout.write(text);
  } catch (error) {
    if (isEpipe(error)) process.exit(0);
    throw error;
  }
}

function cliOutputPolicy(): CliOutputPolicy {
  if (cachedOutputPolicy !== null) return cachedOutputPolicy;
  try {
    const raw = readFileSync(CLI_OUTPUT_CONFIG_PATH, "utf8");
    const parsed = Bun.YAML.parse(raw) as unknown;
    const root = asRecord(parsed, CLI_OUTPUT_CONFIG_RELATIVE_PATH);
    const version = integerField(root, "version", CLI_OUTPUT_CONFIG_RELATIVE_PATH);
    if (version !== 1) throw new Error(`${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.version must be 1`);
    const kind = stringField(root, "kind", CLI_OUTPUT_CONFIG_RELATIVE_PATH);
    if (kind !== "unidesk-cli") throw new Error(`${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.kind must be unidesk-cli`);
    const output = objectField(root, "output", CLI_OUTPUT_CONFIG_RELATIVE_PATH);
    cachedOutputPolicy = {
      maxStdoutBytes: positiveIntegerField(output, "maxStdoutBytes", `${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.output`),
      dumpDir: absolutePathField(output, "dumpDir", `${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.output`),
      includePreview: booleanField(output, "includePreview", `${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.output`),
      warning: stringField(output, "warning", `${CLI_OUTPUT_CONFIG_RELATIVE_PATH}.output`),
      configPath: CLI_OUTPUT_CONFIG_RELATIVE_PATH,
    };
    return cachedOutputPolicy;
  } catch (error) {
    const message = error instanceof Error ? error.message : String(error);
    cachedOutputPolicy = {
      maxStdoutBytes: EMERGENCY_OUTPUT_DUMP_THRESHOLD_BYTES,
      dumpDir: EMERGENCY_OUTPUT_DUMP_DIR,
      includePreview: false,
      warning: "CLI output policy YAML could not be loaded; emergency bounded-output guard is active and creates no default dump. Fix config/unidesk-cli.yaml, then improve the noisy command itself to print concise summaries and id-specific queries; use explicit --full/--raw only for one-off complete disclosure.",
      configPath: CLI_OUTPUT_CONFIG_RELATIVE_PATH,
      configError: message,
    };
    return cachedOutputPolicy;
  }
}

function asRecord(value: unknown, path: string): Record<string, unknown> {
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${path} must be an object`);
  return value as Record<string, unknown>;
}

function objectField(obj: Record<string, unknown>, key: string, path: string): Record<string, unknown> {
  return asRecord(obj[key], `${path}.${key}`);
}

function stringField(obj: Record<string, unknown>, key: string, path: string): string {
  const value = obj[key];
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path}.${key} must be a non-empty string`);
  return value;
}

function integerField(obj: Record<string, unknown>, key: string, path: string): number {
  const value = obj[key];
  if (typeof value !== "number" || !Number.isInteger(value)) throw new Error(`${path}.${key} must be an integer`);
  return value;
}

function positiveIntegerField(obj: Record<string, unknown>, key: string, path: string): number {
  const value = integerField(obj, key, path);
  if (value <= 0) throw new Error(`${path}.${key} must be positive`);
  return value;
}

function booleanField(obj: Record<string, unknown>, key: string, path: string): boolean {
  const value = obj[key];
  if (typeof value !== "boolean") throw new Error(`${path}.${key} must be a boolean`);
  return value;
}

function absolutePathField(obj: Record<string, unknown>, key: string, path: string): string {
  const value = stringField(obj, key, path);
  if (!value.startsWith("/")) throw new Error(`${path}.${key} must be absolute`);
  return value;
}
