import { computed, ref } from "vue";
import { defineStore } from "pinia";
import { fetchAgentObserverRuns, openAgentObserverStream } from "@/api/agentObserver";
import { agentObserverRuntimeConfig, type AgentObserverRuntimeConfig } from "@/config/agent-observer";
import { asRecord, nonEmptyString } from "@/utils";

export type AgentObserverView = "table" | "cards" | "tree";
export type AgentObserverDensity = "compact" | "standard";
export type AgentObserverPhase = "idle" | "replay" | "handoff" | "live" | "reconnecting" | "stale" | "error";

export interface AgentObserverEvent {
  id: string;
  sourceEventId: string | null;
  runId: string;
  commandId: string | null;
  sessionId: string | null;
  traceId: string | null;
  sourceSeq: number | null;
  createdAt: string | null;
  type: string;
  status: string;
  label: string;
  message: string;
  terminal: boolean;
  raw: Record<string, unknown>;
}

export interface AgentObserverRun {
  id: string;
  title: string | null;
  taskId: string | null;
  taskStatus: string | null;
  commandId: string | null;
  attemptId: string | null;
  attemptStatus: string | null;
  runnerId: string | null;
  runnerCapacityUsed: number | null;
  runnerCapacityTotal: number | null;
  nodeId: string | null;
  lane: string | null;
  sessionId: string | null;
  traceId: string | null;
  projectId: string | null;
  workspace: string | null;
  backend: string | null;
  providerId: string | null;
  runStatus: string;
  commandState: string | null;
  commandType: string | null;
  phase: string;
  result: string | null;
  replyAuthority: boolean | null;
  final: boolean | null;
  errorCode: string | null;
  lastMessage: string | null;
  startedAt: string | null;
  finishedAt: string | null;
  updatedAt: string | null;
  lastSourceSeq: number | null;
  eventIds: string[];
  changedAtMs: number;
}

export type AgentObserverRelationKind = "scope" | "session" | "task" | "run" | "attempt" | "command";

export interface AgentObserverStatusToken {
  value: string;
  label: string;
  tone: "failed" | "completed" | "canceled" | "pending" | "running" | "unknown";
}

export interface AgentObserverRelationRow {
  id: string;
  kind: AgentObserverRelationKind;
  label: string;
  detail: string | null;
  parentId: string | null;
  runId: string | null;
  status: string | null;
  depth: number;
  childCount: number;
  expanded: boolean;
  position: number;
  setSize: number;
}

interface AgentObserverRelationNode {
  id: string;
  kind: AgentObserverRelationKind;
  label: string;
  detail: string | null;
  parentId: string | null;
  runId: string | null;
  status: string | null;
  children: string[];
}

export const useAgentObserverStore = defineStore("agentObserver", () => {
  const entities = ref<Record<string, AgentObserverRun>>({});
  const orderedIds = ref<string[]>([]);
  const events = ref<AgentObserverEvent[]>([]);
  const selectedRunId = ref<string | null>(null);
  const view = ref<AgentObserverView>("table");
  const density = ref<AgentObserverDensity>("compact");
  const search = ref("");
  const statusFilter = ref("all");
  const projectFilter = ref("all");
  const agentFilter = ref("all");
  const timeWindow = ref("all");
  const sort = ref("updated-desc");
  const phase = ref<AgentObserverPhase>("idle");
  const lastEventAt = ref<string | null>(null);
  const connectedAt = ref<string | null>(null);
  const lastConfirmedEventId = ref<string | null>(null);
  const retentionWindowAdvanced = ref(false);
  const error = ref<string | null>(null);
  const page = ref(1);
  const pageSize = ref(25);
  const total = ref(0);
  const totalPages = ref(0);
  const facetProjects = ref<string[]>([]);
  const facetWorkspaces = ref<string[]>([]);
  const facetBackends = ref<string[]>([]);
  const facetProviders = ref<string[]>([]);
  const snapshotLoaded = ref(false);
  const loadingPage = ref(false);
  const nowMs = ref(Date.now());
  const config = ref<AgentObserverRuntimeConfig | null>(null);
  const expandedRelationIds = ref<string[]>([]);
  const collapsedRelationIds = ref<string[]>([]);
  const seenIdentity = new Map<string, object>();
  const identityWindow: Array<{ token: object; keys: string[] }> = [];
  const queue: Array<{ payload: unknown; eventId: string | null }> = [];
  let source: EventSource | null = null;
  let reconnectTimer: number | null = null;
  let staleTimer: number | null = null;
  let snapshotRefreshTimer: number | null = null;
  let snapshotRequest = 0;
  let flushScheduled = false;
  let manuallyClosed = false;

  const runs = computed(() => orderedIds.value.map((id) => entities.value[id]).filter((item): item is AgentObserverRun => Boolean(item)));
  const selectedRun = computed(() => selectedRunId.value ? entities.value[selectedRunId.value] ?? null : null);
  const visibleRuns = computed(() => runs.value);
  const projects = computed(() => unique(runs.value.map((run) => run.projectId)));
  const scopeOptions = computed(() => [
    ...facetProjects.value.map((value) => ({ value: `project:${value}`, label: `项目 · ${value}` })),
    ...facetWorkspaces.value.map((value) => ({ value: `workspace:${value}`, label: `Workspace · ${value}` }))
  ]);
  const agents = computed(() => [
    ...facetBackends.value.map((value) => ({ value: `backend:${value}`, label: `Agent · ${value}` })),
    ...facetProviders.value.map((value) => ({ value: `provider:${value}`, label: `Profile · ${value}` })),
  ]);
  const selectedEvents = computed(() => {
    const run = selectedRun.value;
    if (!run || !config.value) return [];
    const ids = new Set(run.eventIds.slice(-config.value.inspectorEventTailLimit));
    return events.value.filter((event) => ids.has(event.id));
  });
  const selectedLogs = computed(() => selectedEvents.value.filter((event) => ["output", "tool", "error"].includes(event.type) || event.label.includes(":output:")));
  const selectedInEventWindow = computed(() => {
    if (!selectedRunId.value) return true;
    return Boolean(selectedRun.value);
  });
  const stats = computed(() => {
    const values = visibleRuns.value;
    const capacityByRunner = new Map<string, { used: number; total: number }>();
    for (const run of values) {
      if (!run.runnerId || run.runnerCapacityUsed === null || run.runnerCapacityTotal === null || capacityByRunner.has(run.runnerId)) continue;
      capacityByRunner.set(run.runnerId, { used: run.runnerCapacityUsed, total: run.runnerCapacityTotal });
    }
    return {
      total: total.value,
      running: values.filter((run) => statusGroup(run.runStatus) === "running").length,
      waiting: values.filter((run) => statusGroup(run.runStatus) === "waiting").length,
      failed: values.filter((run) => statusGroup(run.runStatus) === "failed").length,
      terminal: values.filter((run) => statusGroup(run.runStatus) === "terminal").length,
      unknown: values.filter((run) => statusGroup(run.runStatus) === "unknown").length,
      stale: values.filter((run) => run.updatedAt && nowMs.value - Date.parse(run.updatedAt) > (config.value?.staleAfterMs ?? Number.MAX_SAFE_INTEGER)).length,
      runners: new Set(values.map((run) => run.runnerId).filter(Boolean)).size,
      runnerCapacityUsed: [...capacityByRunner.values()].reduce((sum, capacity) => sum + capacity.used, 0),
      runnerCapacityTotal: [...capacityByRunner.values()].reduce((sum, capacity) => sum + capacity.total, 0),
      runnerCapacityKnown: capacityByRunner.size > 0
    };
  });
  const dataAgeMs = computed(() => lastEventAt.value ? Math.max(0, nowMs.value - Date.parse(lastEventAt.value)) : null);
  const relationWindow = computed(() => buildRelationWindow(
    visibleRuns.value,
    selectedRunId.value,
    new Set(expandedRelationIds.value),
    new Set(collapsedRelationIds.value),
    config.value?.treeNodeLimit ?? 1
  ));
  const relationRows = computed(() => relationWindow.value.rows);

  function connect(): void {
    disconnect(false);
    config.value = agentObserverRuntimeConfig();
    if (!config.value) {
      phase.value = "error";
      error.value = "Agent observer runtime config 未由 owning YAML 渲染。";
      return;
    }
    manuallyClosed = false;
    phase.value = "reconnecting";
    error.value = null;
    void loadPage();
    source = openAgentObserverStream({
      afterId: lastConfirmedEventId.value,
      liveOnly: true,
      onEnvelope: enqueueEnvelope,
      onHandoff: () => {
        phase.value = "handoff";
        error.value = null;
      },
      onConnected: (payload) => {
        const replay = asRecord(asRecord(payload)?.replay);
        const resume = asRecord(replay?.resume);
        retentionWindowAdvanced.value = resume?.windowAdvanced === true;
        connectedAt.value = new Date().toISOString();
        phase.value = "live";
        error.value = null;
      },
      onHeartbeat: () => {
        if (phase.value === "stale") phase.value = "live";
      },
      onStreamError: (payload) => {
        const record = asRecord(payload);
        error.value = nonEmptyString(record?.message) ?? "Kafka observer stream failed.";
        phase.value = "error";
      },
      onTransportError: scheduleReconnect
    });
    if (staleTimer === null) {
      staleTimer = window.setInterval(() => {
        nowMs.value = Date.now();
        if (phase.value === "live" && dataAgeMs.value !== null && dataAgeMs.value > (config.value?.staleAfterMs ?? Number.MAX_SAFE_INTEGER)) phase.value = "stale";
      }, 1000);
    }
  }

  function disconnect(manual = true): void {
    manuallyClosed = manual;
    source?.close();
    source = null;
    if (reconnectTimer !== null) window.clearTimeout(reconnectTimer);
    reconnectTimer = null;
    if (manual && staleTimer !== null) window.clearInterval(staleTimer);
    if (manual) staleTimer = null;
    if (manual && snapshotRefreshTimer !== null) window.clearTimeout(snapshotRefreshTimer);
    if (manual) snapshotRefreshTimer = null;
  }

  async function loadPage(): Promise<void> {
    const request = ++snapshotRequest;
    loadingPage.value = true;
    if (statusFilter.value === "unknown") {
      entities.value = {};
      orderedIds.value = [];
      total.value = 0;
      totalPages.value = 0;
      snapshotLoaded.value = true;
      loadingPage.value = false;
      return;
    }
    const scope = splitFilter(projectFilter.value);
    const agent = splitFilter(agentFilter.value);
    const result = await fetchAgentObserverRuns({
      page: page.value,
      pageSize: pageSize.value,
      status: statusQuery(statusFilter.value),
      projectId: scope.kind === "project" ? scope.value : undefined,
      workspace: scope.kind === "workspace" ? scope.value : undefined,
      backendProfile: agent.kind === "backend" ? agent.value : undefined,
      providerId: agent.kind === "provider" ? agent.value : undefined,
      search: search.value.trim() || undefined,
      updatedAfter: updatedAfterQuery(timeWindow.value, nowMs.value),
      sort: sort.value,
    });
    if (request !== snapshotRequest) return;
    loadingPage.value = false;
    if (!result.ok || !result.data) {
      error.value = result.error ?? "AgentRun 分页快照加载失败。";
      snapshotLoaded.value = true;
      return;
    }
    const incoming = result.data.items
      .map((item) => snapshotRun(item, nonEmptyString(item.id) ? entities.value[nonEmptyString(item.id) as string] : undefined))
      .filter((item): item is AgentObserverRun => Boolean(item));
    const next: Record<string, AgentObserverRun> = {};
    for (const item of incoming) next[item.id] = item;
    entities.value = next;
    orderedIds.value = incoming.map((item) => item.id);
    total.value = result.data.total;
    totalPages.value = result.data.totalPages;
    const facets = asRecord(result.data.facets);
    facetProjects.value = stringArray(facets?.projects);
    facetWorkspaces.value = stringArray(facets?.workspaces);
    facetBackends.value = stringArray(facets?.backends);
    facetProviders.value = stringArray(facets?.providers);
    snapshotLoaded.value = true;
    lastEventAt.value = incoming.reduce<string | null>((latest, item) => latestTimestamp(latest, item.updatedAt), lastEventAt.value);
    error.value = null;
  }

  function refreshSnapshotSoon(): void {
    if (snapshotRefreshTimer !== null) return;
    snapshotRefreshTimer = window.setTimeout(() => {
      snapshotRefreshTimer = null;
      void loadPage();
    }, 250);
  }

  function reconnect(): void {
    manuallyClosed = false;
    connect();
  }

  function scheduleReconnect(): void {
    source?.close();
    source = null;
    if (manuallyClosed || reconnectTimer !== null || !config.value) return;
    phase.value = "reconnecting";
    reconnectTimer = window.setTimeout(() => {
      reconnectTimer = null;
      connect();
    }, config.value.reconnectDelayMs);
  }

  function enqueueEnvelope(payload: unknown, eventId: string | null): void {
    if (!config.value) return;
    if (queue.length >= config.value.liveBufferLimit) {
      phase.value = "error";
      error.value = "客户端 Kafka 事件缓冲达到 YAML 上限。";
      source?.close();
      return;
    }
    queue.push({ payload, eventId });
    if (flushScheduled) return;
    flushScheduled = true;
    queueMicrotask(flushQueue);
  }

  function flushQueue(): void {
    flushScheduled = false;
    if (!config.value) return;
    const batch = queue.splice(0, config.value.batchMaxEvents);
    for (const item of batch) {
      if (reduceEnvelope(item.payload)) lastConfirmedEventId.value = item.eventId ?? stableEnvelopeIdentity(item.payload) ?? lastConfirmedEventId.value;
    }
    if (queue.length > 0) {
      flushScheduled = true;
      queueMicrotask(flushQueue);
    }
  }

  function reduceEnvelope(payload: unknown): boolean {
    if (!config.value) return false;
    const envelope = asRecord(payload);
    const body = asRecord(envelope?.event);
    const context = asRecord(envelope?.context);
    const runId = nonEmptyString(envelope?.runId) ?? nonEmptyString(body?.runId);
    const outerEventId = nonEmptyString(envelope?.eventId);
    const sourceEventId = nonEmptyString(envelope?.sourceEventId);
    const eventId = outerEventId ?? sourceEventId;
    if (!runId || !eventId || rememberIdentity(outerEventId, sourceEventId)) return false;
    const createdAt = nonEmptyString(body?.createdAt) ?? nonEmptyString(envelope?.producedAt);
    const sourceSeq = integer(body?.sourceSeq) ?? integer(context?.sourceSeq);
    const event: AgentObserverEvent = {
      id: eventId,
      sourceEventId,
      runId,
      commandId: nonEmptyString(envelope?.commandId) ?? nonEmptyString(body?.commandId),
      sessionId: nonEmptyString(envelope?.sessionId) ?? nonEmptyString(envelope?.hwlabSessionId),
      traceId: nonEmptyString(envelope?.traceId),
      sourceSeq,
      createdAt,
      type: nonEmptyString(body?.type) ?? "event",
      status: nonEmptyString(body?.status) ?? "running",
      label: nonEmptyString(body?.label) ?? "agentrun:event",
      message: nonEmptyString(body?.message) ?? nonEmptyString(body?.text) ?? "",
      terminal: body?.terminal === true,
      raw: body ?? {}
    };
    events.value.push(event);
    while (events.value.length > config.value.reducerEventLimit) {
      events.value.shift();
    }

    const previous = entities.value[runId];
    if (!previous && page.value !== 1) return true;
    const sequenceIsCurrent = !previous
      || (sourceSeq !== null && (previous.lastSourceSeq === null || sourceSeq > previous.lastSourceSeq));
    lastEventAt.value = latestTimestamp(lastEventAt.value, createdAt) ?? new Date().toISOString();
    nowMs.value = Date.now();
    if (previous && !sequenceIsCurrent) return true;
    const terminalStatus = nonEmptyString(body?.terminalStatus);
    const runStatus = normalizeRunStatus(nonEmptyString(body?.runStatus), terminalStatus, previous?.runStatus);
    const next: AgentObserverRun = {
      id: runId,
      title: nonEmptyString(body?.title) ?? previous?.title ?? null,
      taskId: nonEmptyString(body?.taskId) ?? previous?.taskId ?? null,
      taskStatus: nonEmptyString(body?.taskStatus) ?? nonEmptyString(body?.taskState) ?? previous?.taskStatus ?? null,
      commandId: event.commandId ?? previous?.commandId ?? null,
      attemptId: nonEmptyString(body?.attemptId) ?? previous?.attemptId ?? null,
      attemptStatus: nonEmptyString(body?.attemptStatus) ?? nonEmptyString(body?.attemptState) ?? previous?.attemptStatus ?? null,
      runnerId: nonEmptyString(body?.runnerId) ?? previous?.runnerId ?? null,
      runnerCapacityUsed: integer(body?.runnerCapacityUsed) ?? integer(asRecord(body?.runnerCapacity)?.used) ?? previous?.runnerCapacityUsed ?? null,
      runnerCapacityTotal: integer(body?.runnerCapacityTotal) ?? integer(asRecord(body?.runnerCapacity)?.total) ?? previous?.runnerCapacityTotal ?? null,
      nodeId: nonEmptyString(body?.nodeId) ?? previous?.nodeId ?? null,
      lane: nonEmptyString(body?.lane) ?? previous?.lane ?? null,
      sessionId: event.sessionId ?? previous?.sessionId ?? null,
      traceId: event.traceId ?? previous?.traceId ?? null,
      projectId: nonEmptyString(body?.projectId) ?? nonEmptyString(context?.projectId) ?? previous?.projectId ?? null,
      workspace: nonEmptyString(body?.workspace) ?? previous?.workspace ?? null,
      backend: nonEmptyString(body?.backend) ?? previous?.backend ?? null,
      providerId: nonEmptyString(body?.providerId) ?? previous?.providerId ?? null,
      runStatus,
      commandState: nonEmptyString(body?.commandState) ?? previous?.commandState ?? null,
      commandType: nonEmptyString(body?.commandType) ?? previous?.commandType ?? null,
      phase: event.label.replace(/^agentrun:/u, ""),
      result: nonEmptyString(body?.result) ?? previous?.result ?? null,
      replyAuthority: mergeMonotonicBoolean(previous?.replyAuthority, explicitBoolean(body?.replyAuthority)),
      final: mergeMonotonicBoolean(previous?.final, explicitBoolean(body?.final)),
      errorCode: nonEmptyString(body?.errorCode) ?? nonEmptyString(body?.failureKind) ?? previous?.errorCode ?? null,
      lastMessage: event.message || (previous?.lastMessage ?? null),
      startedAt: nonEmptyString(body?.startedAt) ?? previous?.startedAt ?? null,
      finishedAt: nonEmptyString(body?.finishedAt) ?? (event.terminal ? (createdAt ?? previous?.finishedAt ?? null) : (previous?.finishedAt ?? null)),
      updatedAt: createdAt ?? previous?.updatedAt ?? null,
      lastSourceSeq: sourceSeq ?? previous?.lastSourceSeq ?? null,
      eventIds: [...(previous?.eventIds ?? []), eventId].slice(-config.value.inspectorEventTailLimit),
      changedAtMs: nowMs.value
    };
    entities.value = { ...entities.value, [runId]: next };
    orderedIds.value = [runId, ...orderedIds.value.filter((id) => id !== runId)];
    trimEntities();
    refreshSnapshotSoon();
    return true;
  }

  function rememberIdentity(eventId: string | null, sourceEventId: string | null): boolean {
    if (!config.value) return true;
    const keys = [eventId ? `event:${eventId}` : null, sourceEventId ? `source:${sourceEventId}` : null].filter((key): key is string => Boolean(key));
    if (keys.some((key) => seenIdentity.has(key))) return true;
    const token = {};
    for (const key of keys) seenIdentity.set(key, token);
    identityWindow.push({ token, keys });
    while (identityWindow.length > config.value.identityWindowLimit) {
      const expired = identityWindow.shift();
      if (!expired) break;
      for (const key of expired.keys) if (seenIdentity.get(key) === expired.token) seenIdentity.delete(key);
    }
    return false;
  }

  function trimEntities(): void {
    const limit = Math.min(config.value?.reducerEntityLimit ?? pageSize.value, pageSize.value);
    if (orderedIds.value.length <= limit) return;
    const keep = orderedIds.value.slice(0, limit);
    if (selectedRunId.value && !keep.includes(selectedRunId.value) && entities.value[selectedRunId.value]) keep[keep.length - 1] = selectedRunId.value;
    const next: Record<string, AgentObserverRun> = {};
    for (const id of keep) if (entities.value[id]) next[id] = entities.value[id];
    entities.value = next;
    orderedIds.value = keep;
  }

  function selectRun(runId: string | null): void {
    selectedRunId.value = runId;
  }

  function toggleRelation(relationId: string, expanded: boolean): void {
    if (expanded) {
      collapsedRelationIds.value = collapsedRelationIds.value.filter((id) => id !== relationId);
      if (!expandedRelationIds.value.includes(relationId)) expandedRelationIds.value = [...expandedRelationIds.value, relationId];
      return;
    }
    expandedRelationIds.value = expandedRelationIds.value.filter((id) => id !== relationId);
    if (!collapsedRelationIds.value.includes(relationId)) collapsedRelationIds.value = [...collapsedRelationIds.value, relationId];
  }

  return {
    entities, orderedIds, events, runs, visibleRuns, selectedRun, selectedEvents, selectedLogs, selectedInEventWindow, selectedRunId,
    view, density, search, statusFilter, projectFilter, agentFilter, timeWindow, sort, phase, lastEventAt,
    page, pageSize, total, totalPages, snapshotLoaded, loadingPage,
    connectedAt, lastConfirmedEventId, retentionWindowAdvanced, error, nowMs, config, projects, scopeOptions, agents, stats, dataAgeMs, relationRows,
    relationWindowLimited: computed(() => relationWindow.value.limited), relationNodeCount: computed(() => relationWindow.value.nodeCount),
    relationOmittedRunCount: computed(() => relationWindow.value.omittedRunCount),
    connect, disconnect, reconnect, loadPage, selectRun, toggleRelation
  };
});

function snapshotRun(value: Record<string, unknown>, previous?: AgentObserverRun): AgentObserverRun | null {
  const id = nonEmptyString(value.id);
  if (!id) return null;
  const runStatus = nonEmptyString(value.runStatus) ?? "unknown";
  const updatedAt = nonEmptyString(value.updatedAt);
  return {
    id,
    title: nonEmptyString(value.title),
    taskId: nonEmptyString(value.taskId),
    taskStatus: nonEmptyString(value.taskStatus) ?? runStatus,
    commandId: nonEmptyString(value.commandId),
    attemptId: nonEmptyString(value.attemptId),
    attemptStatus: nonEmptyString(value.attemptStatus) ?? runStatus,
    runnerId: nonEmptyString(value.runnerId),
    runnerCapacityUsed: integer(value.runnerCapacityUsed),
    runnerCapacityTotal: integer(value.runnerCapacityTotal),
    nodeId: nonEmptyString(value.nodeId),
    lane: nonEmptyString(value.lane),
    sessionId: nonEmptyString(value.sessionId),
    traceId: nonEmptyString(value.traceId),
    projectId: nonEmptyString(value.projectId),
    workspace: nonEmptyString(value.workspace),
    backend: nonEmptyString(value.backend),
    providerId: nonEmptyString(value.providerId),
    runStatus,
    commandState: nonEmptyString(value.commandState),
    commandType: nonEmptyString(value.commandType),
    phase: nonEmptyString(value.phase) ?? runStatus,
    result: nonEmptyString(value.result) ?? nonEmptyString(value.terminalStatus),
    replyAuthority: null,
    final: ["completed", "failed", "blocked", "cancelled"].includes(runStatus),
    errorCode: nonEmptyString(value.errorCode),
    lastMessage: nonEmptyString(value.lastMessage),
    startedAt: nonEmptyString(value.startedAt),
    finishedAt: nonEmptyString(value.finishedAt),
    updatedAt,
    lastSourceSeq: previous?.lastSourceSeq ?? null,
    eventIds: previous?.eventIds ?? [],
    changedAtMs: previous?.updatedAt === updatedAt ? previous.changedAtMs : Date.now(),
  };
}

function splitFilter(value: string): { kind: string | null; value: string | null } {
  const separator = value.indexOf(":");
  if (value === "all" || separator < 1) return { kind: null, value: null };
  return { kind: value.slice(0, separator), value: value.slice(separator + 1) || null };
}

function statusQuery(value: string): string | undefined {
  if (value === "running") return "claimed,running";
  if (value === "waiting") return "pending";
  if (value === "failed") return "failed,blocked";
  if (value === "terminal") return "completed,cancelled";
  if (value === "unknown") return "unknown";
  return undefined;
}

function updatedAfterQuery(value: string, now: number): string | undefined {
  const duration = value === "15m" ? 15 * 60_000 : value === "1h" ? 60 * 60_000 : value === "6h" ? 6 * 60 * 60_000 : 0;
  return duration > 0 ? new Date(now - duration).toISOString() : undefined;
}

function stringArray(value: unknown): string[] {
  return Array.isArray(value) ? unique(value.map(nonEmptyString)) : [];
}

export function buildRelationWindow(
  runs: AgentObserverRun[],
  selectedRunId: string | null,
  expandedIds: Set<string>,
  collapsedIds: Set<string>,
  nodeLimit: number
): { rows: AgentObserverRelationRow[]; limited: boolean; nodeCount: number; omittedRunCount: number } {
  const nodes = new Map<string, AgentObserverRelationNode>();
  const rootIds: string[] = [];
  const selectedPath = new Set<string>();
  const activePath = new Set<string>();
  const unassociatedId = "scope:unassociated";

  const ensureNode = (node: Omit<AgentObserverRelationNode, "children">): AgentObserverRelationNode => {
    const existing = nodes.get(node.id);
    if (existing) return existing;
    const created = { ...node, children: [] };
    nodes.set(node.id, created);
    if (node.parentId) {
      const parent = nodes.get(node.parentId);
      if (parent && !parent.children.includes(node.id)) parent.children.push(node.id);
    } else if (!rootIds.includes(node.id)) rootIds.push(node.id);
    return created;
  };

  const ensureUnassociated = (): AgentObserverRelationNode => ensureNode({
    id: unassociatedId,
    kind: "scope",
    label: "未关联",
    detail: "Kafka 事件未提供完整 Project / Workspace → Session → Task lineage",
    parentId: null,
    runId: null,
    status: null
  });

  let omittedRunCount = 0;
  const stableRuns = [...runs].sort((left, right) => compareText(relationRunOrderKey(left), relationRunOrderKey(right)));
  for (const [runIndex, run] of stableRuns.entries()) {
    const hasCompleteContainment = Boolean((run.projectId || run.workspace) && run.sessionId && run.taskId);
    const scopeId = hasCompleteContainment ? relationId("scope", `${run.projectId ?? ""}\u0000${run.workspace ?? ""}`) : unassociatedId;
    const sessionId = hasCompleteContainment && run.sessionId ? relationId("session", `${scopeId}\u0000${run.sessionId}`) : null;
    const taskId = sessionId && run.taskId ? relationId("task", `${sessionId}\u0000${run.taskId}`) : null;
    const runParentId = taskId ?? scopeId;
    const runNodeId = relationId("run", `${runParentId}\u0000${run.id}`);
    const attemptId = run.attemptId ? relationId("attempt", `${runNodeId}\u0000${run.attemptId}`) : null;
    const commandParentId = attemptId ?? runNodeId;
    const commandId = run.commandId ? relationId("command", `${commandParentId}\u0000${run.commandId}`) : null;
    const missingNodeCount = [scopeId, sessionId, taskId, runNodeId, attemptId, commandId].filter((id): id is string => typeof id === "string" && !nodes.has(id)).length;
    if (nodes.size + missingNodeCount > nodeLimit) {
      omittedRunCount = stableRuns.length - runIndex;
      break;
    }
    let parent = hasCompleteContainment
      ? ensureNode({
        id: relationId("scope", `${run.projectId ?? ""}\u0000${run.workspace ?? ""}`),
        kind: "scope",
        label: `${run.projectId ?? "项目未提供"} / ${run.workspace ?? "Workspace 未提供"}`,
        detail: null,
        parentId: null,
        runId: null,
        status: null
      })
      : ensureUnassociated();
    const path = [parent.id];

    if (hasCompleteContainment && run.sessionId && run.taskId) {
      parent = ensureNode({
        id: relationId("session", `${parent.id}\u0000${run.sessionId}`),
        kind: "session",
        label: `Session ${run.sessionId}`,
        detail: run.traceId ? `Trace ${run.traceId}` : null,
        parentId: parent.id,
        runId: null,
        status: null
      });
      path.push(parent.id);
      parent = ensureNode({
        id: relationId("task", `${parent.id}\u0000${run.taskId}`),
        kind: "task",
        label: `Task ${run.taskId}`,
        detail: null,
        parentId: parent.id,
        runId: null,
        status: run.taskStatus
      });
      path.push(parent.id);
    }

    parent = ensureNode({
      id: relationId("run", `${parent.id}\u0000${run.id}`),
      kind: "run",
      label: `Run ${run.id}`,
      detail: run.phase,
      parentId: parent.id,
      runId: run.id,
      status: run.runStatus
    });
    path.push(parent.id);

    if (run.attemptId) {
      parent = ensureNode({
        id: relationId("attempt", `${parent.id}\u0000${run.attemptId}`),
        kind: "attempt",
        label: `Attempt ${run.attemptId}`,
        detail: null,
        parentId: parent.id,
        runId: run.id,
        status: run.attemptStatus
      });
      path.push(parent.id);
    }

    if (run.commandId) {
      parent = ensureNode({
        id: relationId("command", `${parent.id}\u0000${run.commandId}`),
        kind: "command",
        label: `Command ${run.commandId}`,
        detail: [run.commandType, run.commandState].filter(Boolean).join(" · ") || null,
        parentId: parent.id,
        runId: run.id,
        status: run.commandState
      });
      path.push(parent.id);
    }

    if (run.id === selectedRunId) for (const id of path) selectedPath.add(id);
    if (["running", "waiting"].includes(statusGroup(run.runStatus))) for (const id of path) activePath.add(id);
  }

  const rows: AgentObserverRelationRow[] = [];
  const append = (id: string, depth: number, position: number, setSize: number): void => {
    const node = nodes.get(id);
    if (!node) return;
    const defaultExpanded = depth <= 2 || selectedPath.has(id) || activePath.has(id);
    const expanded = node.children.length > 0 && !collapsedIds.has(id) && (expandedIds.has(id) || defaultExpanded);
    rows.push({
      id: node.id,
      kind: node.kind,
      label: node.label,
      detail: node.detail,
      parentId: node.parentId,
      runId: node.runId,
      status: node.status,
      depth,
      childCount: node.children.length,
      expanded,
      position,
      setSize
    });
    if (expanded) node.children.forEach((childId, index) => append(childId, depth + 1, index + 1, node.children.length));
  };
  rootIds.forEach((id, index) => append(id, 0, index + 1, rootIds.length));
  return { rows, limited: omittedRunCount > 0, nodeCount: nodes.size, omittedRunCount };
}

function relationId(kind: AgentObserverRelationKind, value: string): string {
  return `${kind}:${encodeURIComponent(value)}`;
}

function relationRunOrderKey(run: AgentObserverRun): string {
  return [run.projectId, run.workspace, run.sessionId, run.taskId, run.id].map((value) => value ?? "").join("\u0000");
}

function compareText(left: string, right: string): number {
  return left < right ? -1 : left > right ? 1 : 0;
}

function latestTimestamp(previous: string | null, next: string | null): string | null {
  if (!previous) return next;
  if (!next) return previous;
  const previousMs = Date.parse(previous);
  const nextMs = Date.parse(next);
  if (!Number.isFinite(previousMs)) return next;
  if (!Number.isFinite(nextMs)) return previous;
  return nextMs >= previousMs ? next : previous;
}

export function normalizeRunStatus(runStatus: string | null, terminalStatus: string | null, previous = "unknown"): string {
  if (terminalStatus) return terminalStatus === "cancelled" ? "canceled" : terminalStatus;
  if (["completed", "failed", "blocked", "canceled", "cancelled"].includes(previous)) return previous;
  return runStatus ?? previous ?? "unknown";
}

export function mergeMonotonicBoolean(previous: boolean | null | undefined, next: boolean | null): boolean | null {
  if (previous === true) return true;
  return next ?? previous ?? null;
}

export function agentObserverStatusToken(status: string | null): AgentObserverStatusToken {
  const value = status?.trim().toLowerCase() || "unknown";
  if (["failed", "blocked", "error", "rejected"].includes(value)) return { value, label: status || "未知", tone: "failed" };
  if (["completed", "succeeded"].includes(value)) return { value, label: status || "未知", tone: "completed" };
  if (["canceled", "cancelled"].includes(value)) return { value, label: status || "未知", tone: "canceled" };
  if (["queued", "pending", "waiting", "admitted"].includes(value)) return { value, label: status || "未知", tone: "pending" };
  if (["claimed", "running", "active", "started", "in_progress"].includes(value)) return { value, label: status || "未知", tone: "running" };
  return { value, label: status || "未知", tone: "unknown" };
}

function statusGroup(status: string): string {
  const value = status.toLowerCase();
  if (["failed", "blocked", "error", "rejected"].includes(value)) return "failed";
  if (["completed", "succeeded", "canceled", "cancelled"].includes(value)) return "terminal";
  if (["queued", "pending", "waiting", "admitted"].includes(value)) return "waiting";
  if (["claimed", "running", "active", "started", "in_progress"].includes(value)) return "running";
  return "unknown";
}

function stableEnvelopeIdentity(payload: unknown): string | null {
  const envelope = asRecord(payload);
  return nonEmptyString(envelope?.eventId) ?? nonEmptyString(envelope?.sourceEventId);
}

function unique(values: Array<string | null>): string[] {
  return [...new Set(values.filter((value): value is string => Boolean(value)))].sort();
}

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

function integer(value: unknown): number | null {
  const number = Number(value);
  return Number.isInteger(number) ? number : null;
}
