<!-- SPEC: PJ2026-010405 云端控制台 / Performance. -->
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->
<script setup lang="ts">
import { computed } from "vue";
import ResourceCollection from "@/components/common/ResourceCollection.vue";
import SectionFrame from "@/components/layout/SectionFrame.vue";
import StatusStrip from "@/components/layout/StatusStrip.vue";
import type { WebPerformanceDashboardPoint, WebPerformanceDashboardTopN, WebPerformanceSummaryResponse } from "@/types";

const props = defineProps<{ payload: WebPerformanceSummaryResponse }>();
const dashboard = computed(() => props.payload.dashboard);
const summary = computed(() => props.payload.summary ?? {});
const health = computed(() => dashboard.value?.collectionHealth ?? props.payload.collectionHealth);
const cards = computed(() => dashboard.value?.cards ?? []);
const trends = computed(() => dashboard.value?.trends ?? []);
const distributions = computed(() => dashboard.value?.distributions ?? []);
const topN = computed(() => dashboard.value?.topN ?? []);
const diagnostics = computed(() => health.value?.diagnostics ?? []);
const statusItems = computed(() => cards.value.length ? cards.value.slice(0, 6).map((card, index) => ({ id: card.id || `card-${index}`, label: card.label || "Metric", value: formatValue(card.value, card.unit), detail: card.detail, tone: tone(card.tone) })) : [
  { id: "samples", label: "Samples", value: formatCount(summary.value.sampleCount) },
  { id: "routes", label: "Routes", value: formatCount(summary.value.routeCount) },
  { id: "series", label: "Series", value: formatCount(summary.value.sampleSeries) },
  { id: "status", label: "Status", value: String(summary.value.status ?? props.payload.status ?? "waiting"), tone: "info" as const }
]);
const contractItems = computed(() => [
  { label: "Window", value: String(props.payload.sampleWindow?.label ?? summary.value.windowLabel ?? summary.value.window ?? "15m") },
  { label: "Generated", value: formatTime(String(summary.value.generatedAt ?? dashboard.value?.generatedAt ?? props.payload.observedAt ?? "")) },
  { label: "Aggregation", value: `${summary.value.aggregationKind ?? "unknown"} / ${summary.value.approximation ?? "unknown"}` },
  { label: "Coverage", value: `${formatCount(summary.value.sampleCount)} samples / ${formatCount(summary.value.routeCount)} routes` }
]);

function tone(value?: string): "neutral" | "ok" | "warn" | "error" | "info" { if (value === "ok") return "ok"; if (value === "blocked") return "error"; if (value === "warn") return "warn"; return "info"; }
function formatCount(value: unknown): string { const number = Number(value ?? 0); return Number.isFinite(number) ? new Intl.NumberFormat("zh-CN").format(number) : "0"; }
function formatValue(value: unknown, unit?: string): string { if (value == null || value === "") return "-"; if (typeof value === "number") return unit === "seconds" ? `${value.toFixed(value < 1 ? 3 : 2)}s` : `${value}${unit ? ` ${unit}` : ""}`; return `${String(value)}${unit ? ` ${unit}` : ""}`; }
function formatTime(value: string): string { if (!value) return "-"; const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString("zh-CN"); }
function points(trend: { points?: WebPerformanceDashboardPoint[] }): WebPerformanceDashboardPoint[] { return trend.points ?? []; }
function chartX(index: number, total: number): number { return total <= 1 ? 150 : 22 + (256 * index) / (total - 1); }
function chartY(value: number, source: WebPerformanceDashboardPoint[]): number { const values = source.map((point) => Number(point.value ?? point.p95 ?? 0)); const max = Math.max(0.001, ...values); return 90 - Math.max(0, Math.min(1, value / max)) * 68; }
function linePoints(trend: { points?: WebPerformanceDashboardPoint[] }): string { const source = points(trend); return source.map((point, index) => `${chartX(index, source.length)},${chartY(Number(point.value ?? point.p95 ?? 0), source)}`).join(" "); }
function maxBucket(values: Array<{ count?: number }>): number { return Math.max(1, ...values.map((value) => Number(value.count ?? 0))); }
function topMax(group: WebPerformanceDashboardTopN): number { return Math.max(1, ...(group.rows ?? []).map((row) => Number(row.value ?? 0))); }
function percent(value: number, max: number): number { return Math.max(2, Math.min(100, value / max * 100)); }
</script>

<template>
  <div class="performance-overview">
    <StatusStrip :items="statusItems" label="性能概览" live="polite" />
    <SectionFrame title="观测契约" description="同一 summary 的窗口、聚合和覆盖事实。">
      <dl class="industrial-fact-row">
        <div v-for="item in contractItems" :key="item.label"><dt>{{ item.label }}</dt><dd>{{ item.value }}</dd></div>
      </dl>
    </SectionFrame>
    <SectionFrame title="采集健康" :description="health?.statusLabel || health?.reasonLabel || '等待采集健康数据'">
      <dl class="industrial-fact-row">
        <div><dt>Samples</dt><dd>{{ formatCount(health?.sampleCount) }}</dd></div>
        <div><dt>Retained</dt><dd>{{ formatCount(health?.retainedSampleCount) }}</dd></div>
        <div><dt>Series</dt><dd>{{ formatCount(health?.sampleSeries) }}</dd></div>
        <div><dt>Last sample</dt><dd>{{ formatTime(health?.lastSampleAt || '') }}</dd></div>
      </dl>
      <ResourceCollection
        :items="diagnostics"
        :item-key="(item) => item.code || item.label || 'diagnostic'"
        :item-title="(item) => item.label || item.code || 'Diagnostic'"
        :item-subtitle="(item) => item.detail || ''"
        :item-status="(item) => item.tone || 'info'"
        view="list"
        density="compact"
        :show-controls="false"
        empty-text="暂无采集诊断。"
        aria-label="采集诊断"
      />
    </SectionFrame>

    <div v-if="dashboard?.schemaVersion" class="performance-chart-grid">
      <SectionFrame v-for="trend in trends" :key="trend.id" :title="trend.title || '性能趋势'" :description="trend.subtitle || ''">
        <svg class="performance-line-chart" viewBox="0 0 300 104" role="img" :aria-label="`${trend.title || '性能'}趋势图`">
          <path d="M22 28 H278 M22 57 H278 M22 86 H278" class="chart-grid-line" />
          <polyline v-if="linePoints(trend)" :points="linePoints(trend)" class="chart-line" />
          <circle
            v-for="(point, index) in points(trend)"
            :key="`${trend.id}-${point.label}-${index}`"
            :cx="chartX(index, points(trend).length)"
            :cy="chartY(Number(point.value ?? point.p95 ?? 0), points(trend))"
            r="4"
            class="chart-point"
          ><title>{{ point.label }}：{{ formatValue(point.value ?? point.p95, trend.unit) }}</title></circle>
        </svg>
      </SectionFrame>
      <SectionFrame v-for="distribution in distributions" :key="distribution.id" :title="distribution.title || '性能分布'" :description="distribution.subtitle || ''">
        <div class="performance-bars">
          <div v-for="bucket in distribution.buckets || []" :key="bucket.label">
            <span>{{ bucket.label }}</span>
            <i :style="{ width: `${percent(Number(bucket.count), maxBucket(distribution.buckets || []))}%` }" />
            <strong>{{ formatCount(bucket.count) }}</strong>
          </div>
        </div>
      </SectionFrame>
      <SectionFrame v-for="group in topN" :key="group.id" :title="group.title || '排行'" :description="group.subtitle || ''">
        <div class="performance-bars">
          <div v-for="row in group.rows || []" :key="`${row.label}:${row.detail}`">
            <span>{{ row.label }}</span>
            <i :style="{ width: `${percent(Number(row.value), topMax(group))}%` }" />
            <strong>{{ formatValue(row.value, row.unit) }}</strong>
          </div>
        </div>
      </SectionFrame>
    </div>
    <SectionFrame v-else title="等待图表数据契约" description="summary 已返回，但缺少 dashboard 字段。" tone="inset"><p>发布包含 chart-ready summary 的 Cloud API 后再观察图表。</p></SectionFrame>
  </div>
</template>

<style scoped>
.performance-overview { display: grid; gap: 12px; min-width: 0; }
.performance-chart-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.performance-line-chart { width: 100%; min-height: 150px; }
.chart-grid-line { fill: none; stroke: #d5dfdd; }.chart-line { fill: none; stroke: #00838a; stroke-width: 3; }.chart-point { fill: #00838a; stroke: #fff; stroke-width: 2; }
.performance-bars { display: grid; gap: 8px; }.performance-bars div { display: grid; grid-template-columns: minmax(100px, 1fr) minmax(100px, 3fr) auto; gap: 8px; align-items: center; }.performance-bars i { display: block; height: 8px; border-radius: 4px; background: #00838a; }
@media (max-width: 900px) { .performance-chart-grid { grid-template-columns: 1fr; } }
</style>
