<script setup lang="ts">
import { computed, onMounted, ref } from "vue";
import EmptyState from "@/components/common/EmptyState.vue";
import LoadingState from "@/components/common/LoadingState.vue";
import PageHeader from "@/components/common/PageHeader.vue";
import { useHwpodStore } from "@/stores/hwpod";

const hwpod = useHwpodStore();
const showRawJson = ref(false);

const nodeOps = computed(() => asRecord(hwpod.nodeOps));
const specs = computed(() => asRecord(hwpod.specs));
const websocket = computed(() => asRecord(nodeOps.value.websocket));
const supportedOps = computed(() => stringArray(nodeOps.value.supportedOps ?? nodeOps.value.ops));
const nodeRows = computed(() => normalizeNodeRows(nodeOps.value, specs.value));
const blockerSummary = computed(() => collectBlockers([nodeOps.value, specs.value, websocket.value, ...nodeRows.value]).join(" / "));
const rawPayload = computed(() => JSON.stringify({ nodeOps: nodeOps.value, specs: specs.value }, null, 2));

onMounted(() => void hwpod.refresh());

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

function asRecordArray(value: unknown): Record<string, unknown>[] {
  return Array.isArray(value) ? value.map(asRecord).filter((item) => Object.keys(item).length > 0) : [];
}

function stringArray(value: unknown): string[] {
  return Array.isArray(value) ? value.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
}

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

function countText(value: unknown): string {
  const number = Number(value);
  return Number.isFinite(number) ? String(number) : "0";
}

function normalizeNodeRows(ops: Record<string, unknown>, specPayload: Record<string, unknown>): Record<string, unknown>[] {
  const wsNodes: Record<string, unknown>[] = asRecordArray(asRecord(ops.websocket).nodes);
  const directNodes: Record<string, unknown>[] = asRecordArray(ops.nodes);
  const groupNodes: Record<string, unknown>[] = asRecordArray(ops.groups).flatMap((group) => asRecordArray(group.nodes).map((node): Record<string, unknown> => ({ ...node, group: firstText(group.name, group.id) })));
  const specNodes: Record<string, unknown>[] = asRecordArray(specPayload.specs).map((spec): Record<string, unknown> => ({
    ...spec,
    name: firstText(spec.name, spec.hwpodId),
    nodeId: firstText(spec.nodeId, spec.hwpodNodeId, spec.hwpodId),
    status: firstText(asRecord(spec.availability).status, spec.status)
  }));
  const seen = new Set<string>();
  return [...wsNodes, ...directNodes, ...groupNodes, ...specNodes].filter((node) => {
    const key = firstText(node.nodeId, node.id, node.name, node.hwpodId);
    if (key === "-" || seen.has(key)) return false;
    seen.add(key);
    return true;
  });
}

function nodeStatus(node: Record<string, unknown>): string {
  return firstText(node.status, asRecord(node.availability).status, node.ready === true ? "ready" : null, node.connected === true ? "connected" : null);
}

function collectBlockers(values: unknown[]): string[] {
  const out: string[] = [];
  const visit = (value: unknown): void => {
    if (out.length >= 6 || value === null || value === undefined) return;
    if (Array.isArray(value)) {
      value.forEach(visit);
      return;
    }
    if (typeof value !== "object") return;
    const record = value as Record<string, unknown>;
    const blocker = asRecord(record.blocker);
    const summary = firstText(record.blockedReason, record.reason, record.error, blocker.summary, blocker.code);
    if (summary !== "-" && !out.includes(summary)) out.push(summary);
    visit(record.results);
    visit(record.availability);
    visit(record.events);
  };
  values.forEach(visit);
  return out;
}
</script>

<template>
  <section class="route-stack hwpod-groups-page">
    <PageHeader eyebrow="Admin" title="HWPOD 分组" description="HWPOD node-ops 合约、连接状态与节点可用性。" />
    <LoadingState v-if="hwpod.loading" />
    <EmptyState v-else-if="hwpod.error" title="HWPOD 加载失败" :description="hwpod.error" />
    <template v-else>
      <section class="system-summary-grid" aria-label="HWPOD node-ops summary">
        <article class="metric-card"><span>Contract</span><strong>{{ firstText(nodeOps.contractVersion, specs.nodeOpsContractVersion, 'hwpod-node-ops-v1') }}</strong><small>{{ firstText(nodeOps.status, 'unknown') }}</small></article>
        <article class="metric-card"><span>Route</span><strong>{{ firstText(nodeOps.route, '/v1/hwpod-node-ops') }}</strong><small>node-ops endpoint</small></article>
        <article class="metric-card"><span>WebSocket</span><strong>{{ countText(websocket.connectedCount) }} / {{ countText(websocket.pendingCount) }}</strong><small>connected / pending</small></article>
        <article class="metric-card"><span>Ops</span><strong>{{ supportedOps.length }}</strong><small>supported operations</small></article>
      </section>

      <section class="data-panel hwpod-groups-section" aria-label="HWPOD nodes">
        <header class="panel-header table-page-header">
          <div>
            <h2>节点列表</h2>
            <small>{{ nodeRows.length }} nodes / {{ countText(specs.availableCount) }} available specs</small>
          </div>
        </header>
        <div v-if="nodeRows.length" class="hwpod-node-grid">
          <article v-for="node in nodeRows" :key="firstText(node.nodeId, node.id, node.name, node.hwpodId)" class="hwpod-node-row">
            <strong>{{ firstText(node.name, node.hwpodId, node.nodeId, node.id) }}</strong>
            <span class="status-pill" :data-status="nodeStatus(node)">{{ nodeStatus(node) }}</span>
            <small>{{ firstText(node.nodeId, node.id) }}</small>
            <code>{{ firstText(node.workspacePath, node.workspace, node.group) }}</code>
          </article>
        </div>
        <EmptyState v-else title="暂无在线 HWPOD node" :description="blockerSummary || 'node-ops 当前没有返回节点；连接数和 pending 状态见上方摘要。'" />
        <p v-if="blockerSummary" class="muted-box hwpod-blocker">{{ blockerSummary }}</p>
      </section>

      <section class="data-panel hwpod-groups-section" aria-label="HWPOD supported operations">
        <header class="panel-header table-page-header">
          <div>
            <h2>支持操作</h2>
            <small>{{ supportedOps.length }} ops exposed by node-ops</small>
          </div>
        </header>
        <div v-if="supportedOps.length" class="hwpod-op-list">
          <code v-for="op in supportedOps" :key="op">{{ op }}</code>
        </div>
        <EmptyState v-else title="暂无 supportedOps" description="后端尚未返回支持操作列表。" />
      </section>

      <section class="data-panel hwpod-raw-panel" aria-label="HWPOD raw payload">
        <button data-testid="hwpod-raw-json-toggle" class="table-action" type="button" :aria-expanded="showRawJson" @click="showRawJson = !showRawJson">
          {{ showRawJson ? '隐藏原始 JSON' : '查看原始 JSON' }}
        </button>
        <pre v-if="showRawJson" data-testid="hwpod-raw-json" class="json-panel">{{ rawPayload }}</pre>
      </section>
    </template>
  </section>
</template>
