<script setup lang="ts">
import { computed, ref } from "vue";
import { useClipboard } from "@/composables/useClipboard";
import type { ApiError, ErrorDiagnostic } from "@/types";

const props = withDefaults(defineProps<{
  title?: string;
  error?: string | null;
  apiError?: ApiError | null;
  diagnostic?: ErrorDiagnostic | null;
  compact?: boolean;
  showMessage?: boolean;
}>(), {
  title: "",
  compact: false,
  showMessage: true
});

const { copied, copy } = useClipboard();
const expanded = ref(!props.compact);
const resolvedDiagnostic = computed(() => props.diagnostic ?? props.apiError?.diagnostic ?? null);
const message = computed(() => firstString(props.error, props.apiError?.userMessage, props.apiError?.message));
const facts = computed(() => {
  const diagnostic = resolvedDiagnostic.value;
  const valuesPrinted = diagnostic?.valuesPrinted === false || props.apiError?.valuesPrinted === false ? "false" : null;
  return [
    fact("trace_id", firstString(diagnostic?.traceId, props.apiError?.traceId)),
    fact("requestId", firstString(diagnostic?.requestId, props.apiError?.requestId)),
    fact("code", firstValue(props.apiError?.code, diagnostic?.code)),
    fact("layer", layerText(diagnostic, props.apiError)),
    fact("route", firstString(diagnostic?.route, props.apiError?.route)),
    fact("source", firstString(diagnostic?.source, props.apiError?.source)),
    fact("status", firstValue(diagnostic?.httpStatus)),
    fact("retryable", firstBooleanText(diagnostic?.retryable, props.apiError?.retryable)),
    fact("retry", retryProgressText(diagnostic, props.apiError)),
    fact("retryDelayMs", firstValue(diagnostic?.retryDelayMs, props.apiError?.retryDelayMs)),
    fact("nextRetryAt", firstString(diagnostic?.nextRetryAt, props.apiError?.nextRetryAt)),
    fact("retryExhausted", firstBooleanText(diagnostic?.retryExhausted, props.apiError?.retryExhausted)),
    fact("valuesPrinted", valuesPrinted)
  ].filter((item): item is { label: string; value: string } => Boolean(item));
});
const primaryTraceFact = computed(() => facts.value.find((item) => item.label === "trace_id") ?? null);
const summaryText = computed(() => {
  const parts = [props.showMessage && message.value ? message.value : null, primaryTraceFact.value ? `trace_id=${primaryTraceFact.value.value}` : null].filter(Boolean);
  return parts.join(" ") || (props.title ? "" : "诊断详情");
});
const hasContent = computed(() => Boolean(message.value || facts.value.length));

function fact(label: string, value: string | number | null | undefined): { label: string; value: string } | null {
  if (value === null || value === undefined) return null;
  const text = String(value).trim();
  return text ? { label, value: text } : null;
}

function firstString(...values: unknown[]): string | null {
  for (const value of values) {
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return null;
}

function firstValue(...values: unknown[]): string | number | null {
  for (const value of values) {
    if (typeof value === "number" && Number.isFinite(value)) return value;
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return null;
}

function firstBooleanText(...values: unknown[]): string | null {
  for (const value of values) if (typeof value === "boolean") return value ? "true" : "false";
  return null;
}

function retryProgressText(diagnostic: ErrorDiagnostic | null, apiError?: ApiError | null): string | null {
  const attempt = firstValue(diagnostic?.retryAttempt, diagnostic?.retryAttempts, apiError?.retryAttempt, apiError?.retryAttempts);
  const max = firstValue(diagnostic?.retryMax, apiError?.retryMax);
  if (attempt !== null && max !== null) return `${attempt}/${max}`;
  if (attempt !== null) return String(attempt);
  if (max !== null) return `max ${max}`;
  return null;
}

function layerText(diagnostic: ErrorDiagnostic | null, apiError?: ApiError | null): string | null {
  const layer = firstString(diagnostic?.layer, apiError?.layer);
  const category = firstString(diagnostic?.category, apiError?.category);
  return [layer, category].filter(Boolean).join(" / ") || null;
}

function copyDiagnostic(): void {
  const lines = [message.value ? `message=${message.value}` : null, ...facts.value.map((item) => `${item.label}=${item.value}`)].filter(Boolean);
  void copy(lines.join("\n"));
}
</script>

<template>
  <section v-if="hasContent" class="api-error-diagnostic" :data-compact="compact ? 'true' : 'false'" :data-expanded="expanded ? 'true' : 'false'">
    <header v-if="title || summaryText || facts.length" class="api-error-diagnostic-summary">
      <div class="api-error-diagnostic-summary-text">
        <strong v-if="title">{{ title }}</strong>
        <p v-if="summaryText">{{ summaryText }}</p>
      </div>
      <button v-if="facts.length" class="api-error-diagnostic-toggle" type="button" :aria-expanded="expanded ? 'true' : 'false'" aria-label="诊断详情" title="诊断详情" @click="expanded = !expanded">!</button>
    </header>
    <div v-if="facts.length && expanded" class="api-error-diagnostic-details">
      <button class="table-action" type="button" @click="copyDiagnostic">{{ copied ? "已复制" : "复制诊断" }}</button>
      <dl class="api-error-diagnostic-grid">
        <div v-for="item in facts" :key="item.label">
          <dt>{{ item.label }}</dt>
          <dd><code>{{ item.value }}</code></dd>
        </div>
      </dl>
    </div>
  </section>
</template>
