<!-- SPEC: PJ2026-010405 云端控制台. -->
<!-- Implementation reference: MDTODO hwlab-web-product-experience/R3. -->

<script setup lang="ts">
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import {
  Activity,
  Columns3,
  HelpCircle,
  LayoutGrid,
  List,
  Network,
  RefreshCw,
  Search,
  SlidersHorizontal,
  X,
} from "lucide-vue-next";
import AgentRunInspector from "@/components/agents/AgentRunInspector.vue";
import BaseDrawer from "@/components/common/BaseDrawer.vue";
import Pagination from "@/components/common/Pagination.vue";
import StatusBadge from "@/components/common/StatusBadge.vue";
import VirtualGrid from "@/components/common/VirtualGrid.vue";
import VirtualList from "@/components/common/VirtualList.vue";
import PageCommandBar from "@/components/layout/PageCommandBar.vue";
import StatusStrip, { type ConsoleStatusItem } from "@/components/layout/StatusStrip.vue";
import {
  agentObserverStatusToken,
  useAgentObserverStore,
  type AgentObserverRun,
  type AgentObserverView,
} from "@/stores/agent-observer";

type InspectorTab = "overview" | "relations" | "events" | "logs" | "raw";
const AgentRunMindMap = defineAsyncComponent(() => import("@/components/agents/AgentRunMindMap.vue"));
const route = useRoute();
const router = useRouter();
const observer = useAgentObserverStore();
const inspectorTab = ref<InspectorTab>("overview");
const urlApplying = ref(false);
const filtersOpen = ref(false);
const helpOpen = ref(false);
const compactInspector = ref(false);
const treeRows = computed(() => observer.relationRows);
const ageLabel = computed(() => formatAge(observer.dataAgeMs));
const streamLabel = computed(() => ({ idle: "未连接", replay: "回放", handoff: "交接", live: "Live", reconnecting: "重连中", stale: "陈旧", error: "传输错误" }[observer.phase]));
const selectedMissing = computed(() => Boolean(observer.selectedRunId && !observer.selectedRun));
const loading = computed(() => observer.loadingPage && !observer.snapshotLoaded);
const statusItems = computed<ConsoleStatusItem[]>(() => [
  { id: "running", label: "运行中", value: observer.stats.running, tone: "info" },
  { id: "waiting", label: "等待", value: observer.stats.waiting, tone: "warn" },
  { id: "failed", label: "失败", value: observer.stats.failed, tone: "error" },
  { id: "stale", label: "陈旧", value: observer.stats.stale, tone: observer.stats.stale ? "warn" : "neutral" },
  { id: "terminal", label: "终态", value: observer.stats.terminal, tone: "ok" },
  { id: "unknown", label: "未知", value: observer.stats.unknown },
  { id: "runners", label: "Runner", value: observer.stats.runners },
  {
    id: "capacity",
    label: "Runner 容量",
    value: observer.stats.runnerCapacityKnown ? `${observer.stats.runnerCapacityUsed}/${observer.stats.runnerCapacityTotal}` : "未知",
  },
  { id: "total", label: "筛选结果", value: observer.stats.total },
]);
let inspectorMedia: MediaQueryList | null = null;
let filterTimer: number | null = null;

onMounted(() => {
  applyRouteState();
  observer.connect();
  inspectorMedia = window.matchMedia("(max-width: 1199px)");
  updateInspectorMode();
  inspectorMedia.addEventListener("change", updateInspectorMode);
});

onBeforeUnmount(() => {
  observer.disconnect();
  if (filterTimer !== null) window.clearTimeout(filterTimer);
  inspectorMedia?.removeEventListener("change", updateInspectorMode);
});

watch(() => route.fullPath, applyRouteState);
watch(
  () => [observer.view, observer.density, observer.search, observer.statusFilter, observer.projectFilter, observer.agentFilter, observer.timeWindow, observer.sort, observer.page, observer.selectedRunId],
  () => { if (!urlApplying.value) void syncUrl(); },
);
watch(
  () => [observer.search, observer.statusFilter, observer.projectFilter, observer.agentFilter, observer.timeWindow, observer.sort],
  () => {
    if (urlApplying.value) return;
    const pageChanged = observer.page !== 1;
    observer.page = 1;
    if (pageChanged) return;
    if (filterTimer !== null) window.clearTimeout(filterTimer);
    filterTimer = window.setTimeout(() => { filterTimer = null; void observer.loadPage(); }, 250);
  },
);
watch(() => observer.page, () => { if (!urlApplying.value) void observer.loadPage(); });

function updateInspectorMode(): void {
  compactInspector.value = inspectorMedia?.matches ?? false;
}

function applyRouteState(): void {
  urlApplying.value = true;
  observer.view = parseView(route.query.view);
  observer.density = route.query.density === "standard" ? "standard" : "compact";
  observer.search = textQuery(route.query.q);
  observer.statusFilter = textQuery(route.query.status) || "all";
  observer.projectFilter = textQuery(route.query.project) || "all";
  observer.agentFilter = textQuery(route.query.agent) || "all";
  observer.timeWindow = textQuery(route.query.window) || "all";
  observer.sort = textQuery(route.query.sort) || "updated-desc";
  observer.page = positivePage(route.query.page);
  observer.selectRun(typeof route.params.runId === "string" ? route.params.runId : null);
  queueMicrotask(() => { urlApplying.value = false; });
}

async function syncUrl(): Promise<void> {
  const query = compactQuery({
    view: observer.view,
    density: observer.density,
    q: observer.search,
    status: observer.statusFilter === "all" ? "" : observer.statusFilter,
    project: observer.projectFilter === "all" ? "" : observer.projectFilter,
    agent: observer.agentFilter === "all" ? "" : observer.agentFilter,
    window: observer.timeWindow === "all" ? "" : observer.timeWindow,
    sort: observer.sort === "updated-desc" ? "" : observer.sort,
    page: observer.page > 1 ? String(observer.page) : "",
  });
  await router.replace(observer.selectedRunId
    ? { name: "AgentRunDetail", params: { runId: observer.selectedRunId }, query }
    : { name: "AgentRuns", query });
}

function selectRun(run: AgentObserverRun): void {
  observer.selectRun(run.id);
}

function openRun(run: AgentObserverRun): void {
  observer.selectRun(run.id);
  void syncUrl();
}

function closeInspector(): void {
  observer.selectRun(null);
}

function clearFilters(): void {
  observer.search = "";
  observer.statusFilter = "all";
  observer.projectFilter = "all";
  observer.agentFilter = "all";
  observer.timeWindow = "all";
}

function status(value: string | null) {
  return agentObserverStatusToken(value);
}

function rowKey(run: AgentObserverRun): string { return run.id; }
function shortId(value: string | null, fallback = "未知"): string { return !value ? fallback : value.length > 28 ? `${value.slice(0, 13)}…${value.slice(-10)}` : value; }
function formatTime(value: string | null): string { const time = value ? Date.parse(value) : Number.NaN; return Number.isFinite(time) ? new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", second: "2-digit" }).format(time) : "未知"; }
function duration(run: AgentObserverRun): string { const start = run.startedAt ? Date.parse(run.startedAt) : Number.NaN; const end = run.finishedAt ? Date.parse(run.finishedAt) : observer.nowMs; return Number.isFinite(start) && Number.isFinite(end) ? formatAge(Math.max(0, end - start)) : "未知"; }
function formatAge(value: number | null): string { if (value === null || !Number.isFinite(value)) return "无数据"; if (value < 1000) return "刚刚"; if (value < 60_000) return `${Math.floor(value / 1000)} 秒`; if (value < 3_600_000) return `${Math.floor(value / 60_000)} 分钟`; return `${Math.floor(value / 3_600_000)} 小时`; }
function parseView(value: unknown): AgentObserverView { return value === "cards" || value === "tree" ? value : "table"; }
function textQuery(value: unknown): string { return typeof value === "string" ? value : ""; }
function compactQuery(value: Record<string, string>): Record<string, string> { return Object.fromEntries(Object.entries(value).filter(([, item]) => item)); }
function positivePage(value: unknown): number { const page = Number(value); return Number.isInteger(page) && page > 0 ? page : 1; }
</script>

<template>
  <section class="agent-observer-page" :data-density="observer.density">
    <PageCommandBar eyebrow="智能体管理" title="AgentRun 运行观察" description="AgentRun 权威分页快照 → Kafka live；三视图与 Inspector 共享同一筛选、排序和选择。" sticky>
      <template #actions>
        <span class="agent-observer-stream" :data-phase="observer.phase" aria-live="polite"><Activity :size="15" aria-hidden="true" /><strong>{{ streamLabel }}</strong><span>{{ ageLabel }}</span></span>
        <span v-if="observer.retentionWindowAdvanced" class="agent-observer-target">Retention 窗口已前移</span>
        <span v-if="observer.config" class="agent-observer-target mono">{{ observer.config.nodeId }}/{{ observer.config.lane }}</span>
        <button class="btn btn-secondary agent-observer-help" type="button" aria-label="打开 AgentRun 观察帮助" @click="helpOpen = true"><HelpCircle :size="15" aria-hidden="true" />帮助</button>
        <button class="btn btn-secondary agent-observer-reconnect" type="button" @click="observer.reconnect"><RefreshCw :size="15" aria-hidden="true" />重连</button>
      </template>
      <template #filters>
        <label class="agent-observer-search"><Search :size="16" aria-hidden="true" /><span class="sr-only">搜索 run、task 或 session</span><input v-model="observer.search" type="search" placeholder="搜索 run / task / session / error" /><button v-if="observer.search" type="button" title="清除搜索" aria-label="清除搜索" @click="observer.search = ''"><X :size="14" aria-hidden="true" /></button></label>
        <select v-model="observer.statusFilter" class="agent-observer-primary-status" aria-label="运行状态"><option value="all">全部状态</option><option value="running">运行中</option><option value="waiting">等待</option><option value="failed">失败</option><option value="terminal">终态</option><option value="unknown">未知</option></select>
        <div class="agent-observer-secondary-filters">
          <select v-model="observer.projectFilter" aria-label="项目或 Workspace"><option value="all">全部 Project / Workspace</option><option v-for="item in observer.scopeOptions" :key="item.value" :value="item.value">{{ item.label }}</option></select>
          <select v-model="observer.agentFilter" aria-label="Agent 或 Profile"><option value="all">全部 Agent / Profile</option><option v-for="item in observer.agents" :key="item.value" :value="item.value">{{ item.label }}</option></select>
          <select v-model="observer.timeWindow" aria-label="时间窗口"><option value="all">全部窗口</option><option value="15m">15 分钟</option><option value="1h">1 小时</option><option value="6h">6 小时</option></select>
          <select v-model="observer.sort" aria-label="排序"><option value="updated-desc">最近活动</option><option value="updated-asc">最早活动</option><option value="status-asc">状态</option><option value="id-asc">ID</option></select>
        </div>
        <button class="agent-observer-filter-trigger" type="button" aria-label="打开次级筛选" @click="filtersOpen = true"><SlidersHorizontal :size="15" aria-hidden="true" />更多</button>
        <div class="agent-observer-segment" role="group" aria-label="视图切换"><button type="button" :aria-pressed="observer.view === 'table'" @click="observer.view = 'table'"><List :size="15" aria-hidden="true" />表格</button><button type="button" :aria-pressed="observer.view === 'cards'" @click="observer.view = 'cards'"><LayoutGrid :size="15" aria-hidden="true" />卡片</button><button type="button" :aria-pressed="observer.view === 'tree'" @click="observer.view = 'tree'"><Network :size="15" aria-hidden="true" />树</button></div>
        <button class="agent-observer-density" type="button" :aria-pressed="observer.density === 'compact'" @click="observer.density = observer.density === 'compact' ? 'standard' : 'compact'"><Columns3 :size="15" aria-hidden="true" />{{ observer.density === "compact" ? "紧凑" : "标准" }}</button>
      </template>
    </PageCommandBar>

    <BaseDrawer :open="filtersOpen" title="次级筛选与显示" description="Project、Agent、时间、排序和密度与桌面命令栏共享同一 URL 状态。" surface-class="agent-observer-filter-drawer" @close="filtersOpen = false">
      <div class="agent-observer-drawer-fields">
        <label>项目 / Workspace<select v-model="observer.projectFilter"><option value="all">全部</option><option v-for="item in observer.scopeOptions" :key="item.value" :value="item.value">{{ item.label }}</option></select></label>
        <label>Agent / Profile<select v-model="observer.agentFilter"><option value="all">全部</option><option v-for="item in observer.agents" :key="item.value" :value="item.value">{{ item.label }}</option></select></label>
        <label>时间窗口<select v-model="observer.timeWindow"><option value="all">全部</option><option value="15m">15 分钟</option><option value="1h">1 小时</option><option value="6h">6 小时</option></select></label>
        <label>排序<select v-model="observer.sort"><option value="updated-desc">最近活动</option><option value="updated-asc">最早活动</option><option value="status-asc">状态</option><option value="id-asc">ID</option></select></label>
        <label>密度<select v-model="observer.density"><option value="compact">紧凑</option><option value="standard">标准</option></select></label>
      </div>
    </BaseDrawer>

    <BaseDrawer :open="helpOpen" title="AgentRun 观察帮助" description="列表读取 AgentRun 权威快照，实时活动来自 Kafka。" surface-class="agent-observer-help-drawer" @close="helpOpen = false">
      <div class="agent-observer-help-content">
        <h3>事件流</h3>
        <p>列表先读取 AgentRun durable store 的分页快照，再连接 Kafka live 流；实时事件会触发当前页轻量刷新。</p>
        <h3>三种视图</h3>
        <p>表格用于高密度扫描，卡片用于并行状态辨识，关系树用于 Task → Run → Command/Attempt 追踪。三者共享筛选、排序、选择、URL 和 Inspector 页签。</p>
        <h3>窗口提示</h3>
        <p>Retention advanced、tree limited 或深链实体暂时离开有界窗口时，页面保留明确提示，不静默改选，也不制造第二事实源。</p>
      </div>
    </BaseDrawer>

    <StatusStrip :items="statusItems" label="AgentRun 运行状态摘要" live="polite" />
    <div v-if="observer.error" class="agent-observer-inline-error" role="status">{{ observer.error }}</div>
    <div v-if="selectedMissing" class="agent-observer-window-warning" role="status">深链所选实体不在当前 reducer 实体窗口；选择保持不变，等待正式 Kafka 事件重新进入窗口。</div>

    <div class="agent-observer-workspace" :class="{ 'has-inspector': observer.selectedRun && !compactInspector }">
      <main class="agent-observer-content">
        <div v-if="loading" class="agent-observer-skeleton" :aria-label="`正在${streamLabel} Kafka retention`"><span v-for="index in 8" :key="index" /></div>
        <div v-else-if="observer.visibleRuns.length === 0" class="agent-observer-empty"><Activity :size="28" aria-hidden="true" /><strong>当前事件窗口没有匹配运行</strong><span>保留最后确认投影；可清除筛选或等待 Kafka live 事件。</span><button class="btn btn-secondary" type="button" @click="clearFilters">清除筛选</button></div>

        <section v-else-if="observer.view === 'table'" class="agent-observer-table" role="grid" aria-label="AgentRun 运行表格">
          <div class="agent-observer-table-head" role="row"><span role="columnheader">状态</span><span role="columnheader">Run / Task / Command identity</span><span role="columnheader">Project / Workspace</span><span role="columnheader">Agent / Profile</span><span role="columnheader">阶段</span><span role="columnheader">活动 / 耗时</span><span role="columnheader">Runner / Node / Lane</span><span role="columnheader">结果 / 错误</span></div>
          <VirtualList :items="observer.visibleRuns" :item-key="rowKey" :item-height="observer.density === 'compact' ? 62 : 76" height="100%" :overscan="8" :window-limit="observer.config?.virtualListWindowLimit" root-role="presentation" item-role="presentation" aria-label="AgentRun 虚拟表格行">
            <template #default="{ item: run }"><div class="agent-observer-table-row" role="row" tabindex="0" :aria-selected="observer.selectedRunId === run.id" :data-selected="observer.selectedRunId === run.id" :data-recent="observer.nowMs - run.changedAtMs < (observer.config?.changeHighlightMs ?? 0)" @click="selectRun(run)" @dblclick="openRun(run)" @keydown.enter="openRun(run)"><span role="gridcell" class="agent-observer-status-stack"><StatusBadge :status="status(run.taskStatus).tone" :label="`T ${status(run.taskStatus).label}`" /><StatusBadge :status="status(run.runStatus).tone" :label="`R ${status(run.runStatus).label}`" /><StatusBadge :status="status(run.commandState).tone" :label="`C ${status(run.commandState).label}`" /></span><span role="gridcell"><strong :title="run.title || run.id">{{ run.title || shortId(run.id) }}</strong><small class="mono">R {{ shortId(run.id) }} · T {{ shortId(run.taskId) }} · C {{ shortId(run.commandId) }}</small></span><span role="gridcell"><strong>{{ run.projectId || "未知" }}</strong><small class="mono" :title="run.workspace || ''">{{ shortId(run.workspace) }}</small></span><span role="gridcell"><strong>{{ run.backend || "Agent 未知" }}</strong><small>{{ run.providerId || "Profile 未知" }}</small></span><span role="gridcell"><strong>{{ run.phase || "未知" }}</strong><small class="mono">Attempt {{ shortId(run.attemptId) }}</small></span><span role="gridcell"><strong>{{ duration(run) }}</strong><small>{{ formatTime(run.updatedAt) }}</small></span><span role="gridcell"><strong class="mono">{{ shortId(run.runnerId) }}</strong><small>{{ run.nodeId || "未知" }} / {{ run.lane || "未知" }}</small></span><span role="gridcell"><strong>{{ run.errorCode || run.result || "未知" }}</strong><small :title="run.lastMessage || ''">{{ shortId(run.lastMessage) }}</small></span></div></template>
          </VirtualList>
        </section>

        <section v-else-if="observer.view === 'cards'" class="agent-observer-card-grid" aria-label="AgentRun 响应式虚拟卡片网格">
          <VirtualGrid :items="observer.visibleRuns" :item-key="rowKey" :row-height="observer.density === 'compact' ? 206 : 230" height="100%" :minimum-column-width="360" :maximum-columns="3" :gap="10" :overscan="4" :window-limit="observer.config?.virtualListWindowLimit" aria-label="所有 AgentRun 卡片结果">
            <template #default="{ item: run }"><article class="agent-observer-card" tabindex="0" :data-status="status(run.runStatus).tone" :data-selected="observer.selectedRunId === run.id" @click="selectRun(run)" @dblclick="openRun(run)" @keydown.enter="openRun(run)"><header><span class="agent-observer-status-stack"><StatusBadge :status="status(run.taskStatus).tone" :label="`Task ${status(run.taskStatus).label}`" /><StatusBadge :status="status(run.runStatus).tone" :label="`Run ${status(run.runStatus).label}`" /><StatusBadge :status="status(run.commandState).tone" :label="`Command ${status(run.commandState).label}`" /></span><span>{{ duration(run) }}</span></header><div class="agent-observer-card-identity"><h2 :title="run.title || run.id">{{ run.title || shortId(run.id) }}</h2><p class="mono" :title="[run.id, run.taskId, run.commandId, run.sessionId].filter(Boolean).join(' · ')">R {{ shortId(run.id) }} · T {{ shortId(run.taskId) }} · C {{ shortId(run.commandId) }}</p></div><dl><div><dt>Agent / Profile</dt><dd>{{ run.backend || "未知" }} · {{ run.providerId || "未知" }}</dd></div><div><dt>Runner / Node</dt><dd class="mono">{{ shortId(run.runnerId) }} · {{ run.nodeId || "未知" }}</dd></div><div><dt>阶段</dt><dd>{{ run.phase || "未知" }}</dd></div><div><dt>最近活动</dt><dd>{{ formatTime(run.updatedAt) }}</dd></div></dl><footer><strong>{{ run.errorCode || run.result || "无终态结果" }}</strong><span :title="run.lastMessage || ''">{{ shortId(run.lastMessage, "暂无结果摘要") }}</span></footer></article></template>
          </VirtualGrid>
        </section>

        <section v-else class="agent-observer-tree" aria-label="AgentRun 思维导图">
          <p v-if="observer.relationWindowLimited" class="agent-observer-window-note">当前事件窗口：关系树按 YAML 上限显示 {{ observer.relationNodeCount }} 个节点，另有 {{ observer.relationOmittedRunCount }} 个运行未展开。</p>
          <AgentRunMindMap :rows="treeRows" :runs="observer.visibleRuns" :selected-run-id="observer.selectedRunId" @select="observer.selectRun" @toggle="observer.toggleRelation" />
        </section>
        <Pagination v-if="observer.total > 0" v-model:page="observer.page" :page-size="observer.pageSize" :total="observer.total" />
      </main>

      <AgentRunInspector v-if="observer.selectedRun && !compactInspector" class="agent-observer-inspector" :run="observer.selectedRun" :events="observer.selectedEvents" :logs="observer.selectedLogs" :in-event-window="observer.selectedInEventWindow" :now-ms="observer.nowMs" :tab="inspectorTab" @update:tab="inspectorTab = $event" @close="closeInspector" />
    </div>

    <BaseDrawer :open="Boolean(observer.selectedRun && compactInspector)" :title="observer.selectedRun ? shortId(observer.selectedRun.id) : 'AgentRun Inspector'" :description="observer.selectedRun ? `${status(observer.selectedRun.runStatus).label} · ${observer.selectedRun.phase || '阶段未知'}` : 'AgentRun Inspector'" surface-class="agent-observer-inspector-drawer" @close="closeInspector">
      <AgentRunInspector v-if="observer.selectedRun" embedded :run="observer.selectedRun" :events="observer.selectedEvents" :logs="observer.selectedLogs" :in-event-window="observer.selectedInEventWindow" :now-ms="observer.nowMs" :tab="inspectorTab" @update:tab="inspectorTab = $event" @close="closeInspector" />
    </BaseDrawer>
  </section>
</template>
