import {
  AttentionPanel,
  DataCoveragePanel,
  DeckTooltipLayer,
  DecisionDialog,
  DistanceToDone,
  RunCommandBar,
  SectionCard,
  SegmentDetailDrawer,
  SettingsTable,
  SwimlaneTrace,
  UndoToast,
  WrapperCapacity,
  type AttentionItem,
  type ConditionBand,
  type DecisionDetail,
  type TaskStateCell,
  type TraceLane,
  type UndoToastHandle,
  type WrapperRateLimit,
} from '@overdeck/deck-ui'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useMemo, useRef, useState } from 'react'
import { CollectorHttpError, fetchHarnessAttempts, fetchHarnessConfig, fetchHarnessDecisions, fetchHarnessEvents, fetchHarnessPlan, fetchHarnessRunDetail, postCollectorAction } from '../../lib/collector-client'
import type { Item } from '../../lib/collector-types'
import { collectorQueryKeys, useCollectorItems, useCollectorState } from '../../lib/collector-queries'
import type { ForensicsPanelData, HarnessPlanRun, HarnessPlansPanelData } from '../../lib/panel-data'
import type { HarnessDecision, HarnessEvent, HarnessJson } from '../../lib/harness-types'
import { CollectorPageApp } from '../shared/CollectorPageApp'
import { CollectorQueryBoundary } from '../shared/CollectorQueryBoundary'
import { AttemptDetailDrawer } from './AttemptDetailDrawer'
import { AttemptTimeline } from './AttemptTimeline'
import { AutopsyStrip } from './AutopsyStrip'
import { extractQuarantine } from './attempt-record'
import { BurnPanel } from './BurnPanel'

type AcceptanceResult = {
  id: string
  text: string
  result: 'passed' | 'failed' | 'pending'
}

const TRUTH_NOTE =
  '“Landed” is verified, never claimed: build & test gate passed, review passed, merged to the integration branch — read from the run journal, not from agent reports.'
const HARNESS_SPEC =
  '/docs/specs/2026-07-18-observability-instrumentation-and-control-api.md'

export function runControlAvailability(status: string) {
  const paused = status === 'paused' || status === 'paused-draining'
  const active = paused || ['queued', 'running', 'gated', 'degraded'].includes(status)
  return {
    pause: active && !paused,
    resume: paused,
    kill: active,
  }
}

function taskState(status: string): TaskStateCell['state'] {
  if (status === 'succeeded') return 'landed'
  if (status === 'running') return 'active'
  if (status === 'failed') return 'failed'
  if (['blocked', 'gated', 'waiting', 'decision'].includes(status)) return 'blocked'
  return 'queued'
}

function median(values: number[]) {
  if (values.length === 0) return null
  const sorted = [...values].sort((left, right) => left - right)
  const middle = Math.floor(sorted.length / 2)
  return sorted.length % 2 === 0
    ? (sorted[middle - 1]! + sorted[middle]!) / 2
    : sorted[middle]!
}

function measuredEta(run: HarnessPlanRun, forensics: ForensicsPanelData | undefined) {
  if (!forensics) return null
  const tasks = run.waves.flatMap((wave) => wave.tasks)
  const completed = new Set(tasks.filter((task) => task.status === 'succeeded').map((task) => task.id))
  const durationByTask = new Map<string, number>()
  for (const segment of forensics.segments) {
    if (!segment.taskId || !completed.has(segment.taskId)) continue
    durationByTask.set(segment.taskId, (durationByTask.get(segment.taskId) ?? 0) + segment.durMs)
  }
  const measured = median([...durationByTask.values()])
  if (measured === null) return null
  return measured * Math.max(0, run.tasksTotal - completed.size)
}

function traceLanes(run: HarnessPlanRun, forensics: ForensicsPanelData | undefined): TraceLane[] {
  if (!forensics) return []
  const taskWave = new Map(
    run.waves.flatMap((wave) => wave.tasks.map((task) => [task.id, wave.wave] as const)),
  )
  const byTask = new Map<string, TraceLane>()
  for (const segment of forensics.segments) {
    const taskId = segment.taskId ?? (segment.cat === 'idle-restart' ? '__run__' : null)
    if (!taskId) continue
    const lane = byTask.get(taskId) ?? {
      taskId,
      name: taskId === '__run__' ? 'Run recovery' : taskId,
      wave: taskWave.get(taskId) ?? 0,
      segments: [],
    }
    lane.segments.push(segment)
    byTask.set(taskId, lane)
  }
  return [...byTask.values()].sort((left, right) => (left.wave ?? 0) - (right.wave ?? 0))
}

function decisionDetail(item: Item): DecisionDetail {
  const decisionAction = item.actions.find((action) => action.args.choice)
  const taskFromContext = /^task\s+(.+)$/.exec(item.decision?.context ?? '')?.[1]
  return {
    taskId: taskFromContext ?? decisionAction?.args.taskId ?? 'not provided by harness (A7)',
    needs: item.decision?.question ?? item.title,
    why: item.detail,
    options: item.actions.map((action) => action.args.choice ?? action.label),
  }
}

function asRecord(value: HarnessJson | undefined): Record<string, HarnessJson> | null {
  return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null
}

function asText(value: HarnessJson | undefined): string | null {
  return typeof value === 'string' ? value : null
}

export function authoritativeDecisionDetail(decision: HarnessDecision): DecisionDetail {
  return {
    taskId: decision.task ?? 'not provided by harness (A7)',
    needs: asText(decision.needs) ?? decision.summary ?? '',
    why: decision.why ?? '',
    blastRadius: asText(decision.blast_radius) ?? '',
    options: decision.options.map((option) => typeof option === 'string'
      ? option
      : option.label ? `${option.label}${option.meaning ? ` — ${option.meaning}` : ''}` : option.meaning ? `${option.value} — ${option.meaning}` : option.value),
  }
}

function isHarnessDecision(value: Item | HarnessDecision): value is HarnessDecision {
  return 'requestedAt' in value && 'options' in value
}

function decisionOptionValue(decision: HarnessDecision, rendered: string): string {
  const option = decision.options.find((candidate) => {
    if (typeof candidate === 'string') return candidate === rendered
    const label = candidate.label ? `${candidate.label}${candidate.meaning ? ` — ${candidate.meaning}` : ''}` : candidate.meaning ? `${candidate.value} — ${candidate.meaning}` : candidate.value
    return label === rendered
  })
  return typeof option === 'string' ? option : option?.value ?? rendered
}

export function eventAcceptance(events: HarnessEvent[]): AcceptanceResult[] {
  const latest = new Map<string, AcceptanceResult>()
  for (const event of events) {
    const payload = asRecord(event.payload.acceptance) ?? event.payload
    const id = asText(payload.id)
    const text = asText(payload.text)
    const result = asText(payload.result)
    if (!id || !text || !['passed', 'failed', 'pending'].includes(result ?? '')) continue
    latest.set(id, { id, text, result: result as AcceptanceResult['result'] })
  }
  return [...latest.values()]
}

export function fixLoopShare(events: HarnessEvent[]): { percent: number } | null {
  const durations = events.reduce((total, event) => total + (typeof event.payload.durationMs === 'number' ? event.payload.durationMs : 0), 0)
  if (durations <= 0) return null
  const fixer = events.reduce((total, event) => total + (event.kind === 'llm-fixer' && typeof event.payload.durationMs === 'number' ? event.payload.durationMs : 0), 0)
  return { percent: fixer / durations * 100 }
}

/** True when at least one dispatch journals a truthful executing account (A9). */
export function hasJournaledExecutingAccount(events: HarnessEvent[]): boolean {
  return events.some((event) => {
    if (event.kind !== 'dispatch.attempt' && event.kind !== 'attempt.prompt' && event.kind !== 'attempt.reply') return false
    const account = event.payload.account
    return typeof account === 'string' && account.length > 0
  })
}

export type TaskFailureReason = {
  taskId: string
  failClass?: string
  cause?: string
  quarantineReason?: string
  failedCommand?: string
  findings: Array<{ severity: string | null; path: string | null; message: string | null }>
}

export function taskFailureReasons(events: HarnessEvent[]): Map<string, TaskFailureReason> {
  const reasons = new Map<string, TaskFailureReason>()
  const reasonFor = (taskId: string) => {
    const existing = reasons.get(taskId)
    if (existing) return existing
    const created: TaskFailureReason = { taskId, findings: [] }
    reasons.set(taskId, created)
    return created
  }
  for (const event of events) {
    const taskId = event.taskId ?? asText(event.payload.task)
    if (!taskId) continue
    if (event.kind === 'task-start') {
      reasons.delete(taskId)
      continue
    }
    if (event.kind === 'quarantine') {
      const text = asText(event.payload.reason)
      if (text) reasonFor(taskId).quarantineReason = text
      continue
    }
    if (event.kind === 'review.verdict' && asText(event.payload.verdict) === 'FAIL') {
      const findings = Array.isArray(event.payload.findings) ? event.payload.findings : []
      reasonFor(taskId).findings = findings.flatMap((finding) => {
        const record = asRecord(finding)
        return record
          ? [{ severity: asText(record.severity), path: asText(record.path), message: asText(record.message) }]
          : []
      })
      continue
    }
    if (event.kind === 'verify.failed') {
      const command = asText(event.payload.command)
      if (command) reasonFor(taskId).failedCommand = command
      continue
    }
    if (event.kind === 'task-end') {
      const failure = asRecord(event.payload.failure)
      if (!failure) continue
      const reason = reasonFor(taskId)
      const failClass = asText(failure.failureClass)
      const cause = asText(failure.cause)
      if (failClass) reason.failClass = failClass
      if (cause) reason.cause = cause
    }
  }
  return reasons
}

export function taskFailureDetail(reason: TaskFailureReason): string {
  const findingLines = reason.findings.map((finding) =>
    [finding.severity, finding.path, finding.message].filter(Boolean).join(' · '))
  return [
    reason.quarantineReason ? `Quarantined: ${reason.quarantineReason}` : null,
    reason.cause,
    reason.failedCommand ? `Verify failed: ${reason.failedCommand}` : null,
    ...findingLines,
  ].filter(Boolean).join('\n')
}

export function ratelimitData(detail: ReturnType<typeof fetchHarnessRunDetail> extends Promise<infer T> ? T : never): { wrappers: string[]; limits: WrapperRateLimit[]; bands: ConditionBand[] } {
  const entries = Object.entries(detail.run.ratelimits ?? {})
  const limits = entries.map(([key, value]) => {
    const resumeAtMs = value.resumeAt ? Date.parse(value.resumeAt) : NaN
    const wrapper = value.wrapper ?? key
    return {
      wrapper,
      active: value.state === 'wait' || value.state === 'limited',
      resumeAtMs: Number.isFinite(resumeAtMs) ? resumeAtMs : undefined,
      parkedTasks: value.task ? [value.task] : undefined,
    }
  })
  return {
    wrappers: limits.map((limit) => limit.wrapper),
    limits,
    bands: limits.flatMap((limit) => limit.active && limit.resumeAtMs && limit.parkedTasks
      ? [{ wrapper: limit.wrapper, fromMs: 0, toMs: limit.resumeAtMs, laneIds: limit.parkedTasks, label: 'Rate limited' }]
      : []),
  }
}

function configValue(value: string): HarnessJson {
  try {
    return JSON.parse(value) as HarnessJson
  } catch {
    return value
  }
}

function taskDispatchBudgetFromMeta(meta: Record<string, HarnessJson> | null | undefined): number {
  if (!meta) return 8
  const value = meta.task_dispatch_budget ?? meta.taskDispatchBudget
  if (typeof value === 'number' && Number.isFinite(value) && value >= 1) return value
  return 8
}

function harnessAttemptArtifactUrl(runId: string, attemptId: string, artifact: 'prompt' | 'reply'): string {
  return `/api/collector/harness/runs/${encodeURIComponent(runId)}/attempts/${encodeURIComponent(attemptId)}/${artifact}`
}

function agentHrefForTask(
  runId: string,
  taskId: string | null,
  forensics: ForensicsPanelData | undefined,
) {
  if (!taskId || !forensics) return undefined
  const segment = [...forensics.segments]
    .reverse()
    .find((candidate) => candidate.taskId === taskId && candidate.agentId)
  return segment?.agentId
    ? `/plans/${encodeURIComponent(runId)}/agents/${encodeURIComponent(segment.agentId)}`
    : undefined
}

function PlanRunContent({ runId }: { runId: string }) {
  const stateQuery = useCollectorState()
  const itemsQuery = useCollectorItems()
  return (
    <CollectorQueryBoundary query={stateQuery}>
      {(state) => (
        <CollectorQueryBoundary query={itemsQuery}>
          {(itemsResponse) => (
            <PlanRunBody runId={runId} state={state} items={itemsResponse.items} />
          )}
        </CollectorQueryBoundary>
      )}
    </CollectorQueryBoundary>
  )
}

function PlanRunBody({
  runId,
  state,
  items,
}: {
  runId: string
  state: import('../../lib/collector-types').StateResponse
  items: Item[]
}) {
  const queryClient = useQueryClient()
  const [zoom, setZoom] = useState<'fit' | 'hour' | 'live'>('fit')
  const [selectedDecision, setSelectedDecision] = useState<Item | HarnessDecision | null>(null)
  const [hiddenDecisionId, setHiddenDecisionId] = useState<string | null>(null)
  const [selectedSegment, setSelectedSegment] = useState<{ laneId: string; index: number } | null>(null)
  const [selectedAttemptId, setSelectedAttemptId] = useState<string | null>(null)
  const undoRef = useRef<UndoToastHandle>(null)
  const configQuery = useQuery({ queryKey: ['harness-config', runId], queryFn: () => fetchHarnessConfig(runId) })
  const eventsQuery = useQuery({ queryKey: ['harness-events', runId], queryFn: () => fetchHarnessEvents(runId) })
  const planQuery = useQuery({ queryKey: ['harness-plan', runId], queryFn: () => fetchHarnessPlan(runId) })
  const decisionsQuery = useQuery({ queryKey: ['harness-decisions', runId], queryFn: () => fetchHarnessDecisions(runId) })
  const detailQuery = useQuery({ queryKey: ['harness-run-detail', runId], queryFn: () => fetchHarnessRunDetail(runId) })
  const attemptsQuery = useQuery({
    queryKey: ['harness-attempts', runId],
    queryFn: () => fetchHarnessAttempts(runId),
    retry: false,
  })

  const plansPanel = state.panels.find((panel) => panel.id === 'plans')
  const run = (plansPanel?.data as HarnessPlansPanelData | undefined)?.runs.find(
    (candidate) => candidate.runId === runId,
  )
  const forensicsPanel = state.panels.find((panel) => panel.id === `forensics:${runId}`)
  const forensics = forensicsPanel?.data as ForensicsPanelData | undefined
  const lanes = useMemo(() => (run ? traceLanes(run, forensics) : []), [run, forensics])
  const attempts = attemptsQuery.data ?? []
  const attemptBudget = taskDispatchBudgetFromMeta(planQuery.data?.meta)
  const selectedAttempt = selectedAttemptId === null
    ? null
    : attempts.find((attempt) => attempt.attemptId === selectedAttemptId) ?? null
  const liveNowMs = !['succeeded', 'failed', 'done', 'cancelled'].includes(run?.status ?? '') ? Date.now() : null

  const toastHandle = useMemo<UndoToastHandle>(
    () => ({
      toast(message) {
        undoRef.current?.toast(message)
      },
      toastUndo(message, options) {
        setHiddenDecisionId(selectedDecision?.id ?? null)
        undoRef.current?.toastUndo(message, {
          ...options,
          onUndo: () => {
            setHiddenDecisionId(null)
            options.onUndo()
          },
          onCommit: () => {
            setHiddenDecisionId(null)
            options.onCommit()
          },
        })
      },
    }),
    [selectedDecision?.id],
  )

  if (!run) {
    return <SectionCard title="Run control room">Run {runId} is not present in collector state.</SectionCard>
  }

  const tasks = run.waves.flatMap((wave) => wave.tasks)
  const cells = tasks.map((task) => ({
    id: task.id,
    name: task.id,
    state: taskState(task.status),
    ...(task.alarmed ? { alarmed: true } : {}),
  }))
  const landed = tasks.filter((task) => task.status === 'succeeded').length
  const runDecisions = items.filter(
    (item) =>
      item.kind === 'decision' && item.actions.some((action) => action.args.runId === runId),
  )
  const visibleDecisions = runDecisions.filter((item) => item.id !== hiddenDecisionId)
  const enrichedDecisions = decisionsQuery.data?.decisions
  const attentionItems: AttentionItem[] = enrichedDecisions
    ? enrichedDecisions.filter((decision) => decision.status !== 'answered').map((decision) => {
      const detail = authoritativeDecisionDetail(decision)
      const agentHref = agentHrefForTask(runId, decision.task, forensics)
      return {
        id: decision.id,
        severity: 'action' as const,
        title: `Decision · ${detail.taskId}`,
        sub: decision.requestedAt ?? undefined,
        detail: `${detail.needs || 'Decision detail needs A7.'}${detail.why ? ` → ${detail.why}` : ''}`,
        actions: [
          { label: 'Decide…', kind: 'primary' as const, onClick: () => setSelectedDecision(decision) },
          ...(agentHref ? [{ label: 'Open agent', kind: 'ghost' as const, href: agentHref }] : []),
        ],
      }
    }) : visibleDecisions.map((item) => {
    const detail = decisionDetail(item)
    const agentHref = agentHrefForTask(runId, detail.taskId, forensics)
    return {
      id: item.id,
      severity: 'action',
      title: `Decision · ${detail.taskId}`,
      sub: item.decision?.waitingSince,
      detail: `${item.title} → Review the recorded context and choose an option.`,
      actions: [
        {
          label: 'Decide…',
          kind: 'primary',
          onClick: () => setSelectedDecision(item),
        },
        ...(agentHref ? [{ label: 'Open agent', kind: 'ghost' as const, href: agentHref }] : []),
      ],
    }
  })
  if (!enrichedDecisions && run.pendingDecisions > runDecisions.length) {
    attentionItems.push({
      id: 'pending-decisions-gap',
      severity: 'action',
      title: `${run.pendingDecisions} pending decision${run.pendingDecisions === 1 ? '' : 's'}`,
      detail: 'Decision detail is not present in the collector response → Check harness A7.',
      actions: [],
    })
  }
  if (run.degradedReason) {
    const agentHref = agentHrefForTask(runId, run.currentTask, forensics)
    attentionItems.push({
      id: 'degraded-run',
      severity: 'critical',
      title: 'Run degraded',
      detail: `${run.degradedReason} → Inspect the failing gate before intervening.`,
      actions: agentHref ? [{ label: 'Open agent', href: agentHref }] : [],
    })
  }
  const failureReasons = taskFailureReasons(eventsQuery.data?.events ?? [])
  for (const task of tasks) {
    if (!['blocked', 'failed'].includes(taskState(task.status))) continue
    const reason = failureReasons.get(task.id)
    if (!reason) continue
    const detail = taskFailureDetail(reason)
    if (!detail) continue
    const agentHref = agentHrefForTask(runId, task.id, forensics)
    attentionItems.push({
      id: `task-failure-${task.id}`,
      severity: 'critical',
      title: `${task.id} · ${reason.failClass ?? (reason.quarantineReason ? 'quality-quarantined' : task.status)}`,
      detail,
      actions: agentHref ? [{ label: 'Open agent', href: agentHref }] : [],
    })
  }

  const selectedLane = selectedSegment
    ? lanes.find((lane) => lane.taskId === selectedSegment.laneId) ?? null
    : null
  const live = !['succeeded', 'failed', 'done', 'cancelled'].includes(run.status)
  const capabilities = eventsQuery.data?.capabilities
  const events = eventsQuery.data?.events ?? []
  const can = (name: string) => capabilities?.[name] === true
  const controls = runControlAvailability(run.status)
  const saveConfig = async (key: string, _layer: 'engine' | 'home' | 'repo' | 'plan' | 'run', value: string) => {
    if (!configQuery.data) return
    const field = configQuery.data.fields[key]
    if (!field || field.redacted || field.immutable || field.mutationClass !== 'mid-run') {
      toastHandle.toast('Configuration field is not mutable during this run.')
      return
    }
    try {
      await postCollectorAction('harness.config.patch', {
        runId,
        revision: configQuery.data.revision,
        patch: JSON.stringify({ [key]: configValue(value) }),
      }, 'overdeck-web')
      await configQuery.refetch()
    } catch (error) {
      if (error instanceof CollectorHttpError && error.status === 409) await configQuery.refetch()
      toastHandle.toast(error instanceof Error ? error.message : String(error))
    }
  }
  const runControl = (verb: 'pause' | 'resume' | 'kill') => async () => {
    try {
      await postCollectorAction(`harness.run.${verb}`, { runId, requestId: crypto.randomUUID() }, 'overdeck-web')
      await queryClient.invalidateQueries({ queryKey: collectorQueryKeys.state })
    } catch (error) {
      if (error instanceof CollectorHttpError && error.status === 409) {
        await queryClient.invalidateQueries({ queryKey: collectorQueryKeys.state })
      }
      toastHandle.toast(error instanceof Error ? error.message : String(error))
    }
  }
  const settings = configQuery.data
    ? Object.entries(configQuery.data.fields).flatMap(([key, field]) => {
        const source = field.source === 'run-override' ? 'run' : field.source
        if (!['engine', 'home', 'repo', 'plan', 'run'].includes(source)) return []
        const value = field.redacted ? 'Redacted' : typeof field.value === 'string' ? field.value : JSON.stringify(field.value)
        return [{
          key,
          label: `${key} · ${field.source}`,
          value,
          decidedBy: source as 'engine' | 'home' | 'repo' | 'plan' | 'run',
          mutation: field.immutable ? 'immutable' as const : field.mutationClass === 'mid-run' ? 'mid-run' as const : 'new-runs' as const,
          layers: [{ layer: source as 'engine' | 'home' | 'repo' | 'plan' | 'run', value, editable: !field.redacted && !field.immutable && field.mutationClass === 'mid-run' }],
        }]
      })
    : null
  const rates = detailQuery.data ? ratelimitData(detailQuery.data) : null
  const acceptance = eventAcceptance(events)
  const failedSurfaces: Array<{ label: string; isError: boolean; refetch: () => Promise<unknown> }> = [
    { label: 'Configuration', isError: configQuery.isError, refetch: configQuery.refetch },
    { label: 'Event history', isError: eventsQuery.isError, refetch: eventsQuery.refetch },
    { label: 'Effective plan', isError: planQuery.isError, refetch: planQuery.refetch },
    { label: 'Decisions', isError: decisionsQuery.isError, refetch: decisionsQuery.refetch },
    { label: 'Run detail', isError: detailQuery.isError, refetch: detailQuery.refetch },
  ].filter((surface) => surface.isError)

  const submitDecision = async (option: string) => {
    if (!selectedDecision) return
    if (isHarnessDecision(selectedDecision)) {
      try {
        await postCollectorAction('decision', {
          runId,
          decisionId: selectedDecision.id,
          choice: decisionOptionValue(selectedDecision, option),
        }, 'overdeck-web')
        await decisionsQuery.refetch()
      } catch (error) {
        toastHandle.toast(error instanceof Error ? error.message : String(error))
      }
      return
    }
    const action = selectedDecision.actions.find(
      (candidate) => (candidate.args.choice ?? candidate.label) === option,
    )
    const decisionId = action?.args.decisionId
    if (!decisionId) {
      toastHandle.toast('Decision id not provided by harness (A7).')
      return
    }
    try {
      await postCollectorAction(
        'decision',
        { runId, decisionId, choice: option },
        selectedDecision.id,
      )
      await Promise.all([
        queryClient.invalidateQueries({ queryKey: collectorQueryKeys.items() }),
        queryClient.invalidateQueries({ queryKey: collectorQueryKeys.state }),
      ])
    } catch (error) {
      toastHandle.toast(error instanceof Error ? error.message : String(error))
    }
  }

  return (
    <div className="flex min-w-0 flex-col gap-4">
      <RunCommandBar
        title={run.title}
        status={run.status}
        state={run.state}
        seq={run.seq}
        updatedAt={run.updatedAt}
        onPause={controls.pause && can('run.pause') ? runControl('pause') : undefined}
        onResume={controls.resume && can('run.resume') ? runControl('resume') : undefined}
        onKill={controls.kill && can('run.kill') ? runControl('kill') : undefined}
        controlReason={capabilities ? 'Run control is not supported by this run.' : 'Run capabilities are not provided by this run.'}
        capacity={
          <WrapperCapacity wrappers={rates?.wrappers ?? []} ratelimits={rates?.limits ?? []} dataAvailable={rates !== null} onOpen={() => {}} />
        }
        coverage={
          <DataCoveragePanel
            gaps={[
              { id: 'A8', label: 'Per-attempt cost', specRef: `${HARNESS_SPEC}#a8` },
              { id: 'A10', label: 'Watchdog idle signal', specRef: `${HARNESS_SPEC}#a10` },
              ...(hasJournaledExecutingAccount(events)
                ? []
                : [{ id: 'A9', label: 'Executing account', specRef: `${HARNESS_SPEC}#a9` }]),
              ...(rates === null ? [{ id: 'B7', label: 'Wrapper rate limits', specRef: `${HARNESS_SPEC}#b7` }] : []),
            ]}
          />
        }
      />
      <DistanceToDone
        landed={landed}
        total={run.tasksTotal}
        cells={cells}
        etaMs={tasks.length === 0 ? null : measuredEta(run, forensics)}
        fixLoop={fixLoopShare(events)}
        truthNote={planQuery.data ? `${TRUTH_NOTE} Effective plan revision ${planQuery.data.revision} is loaded from harness.` : TRUTH_NOTE}
        acceptance={acceptance}
      />
      {failedSurfaces.map((surface) => (
        <SectionCard key={surface.label} title={`${surface.label} unavailable`} action={{ label: 'Retry', onClick: () => void surface.refetch() }}>
          Collector request failed. Existing run data remains visible; retry this surface.
        </SectionCard>
      ))}
      <AttentionPanel items={attentionItems} />
      <SwimlaneTrace
        lanes={lanes}
        nowMs={live ? Date.now() : null}
        bands={rates?.bands ?? []}
        zoom={zoom}
        onZoom={setZoom}
        onSegmentClick={(laneId, index) => setSelectedSegment({ laneId, index })}
      />
      {attemptsQuery.isPending ? (
        <SectionCard title="Attempt history">
          <p className="text-sm text-fg-muted">Loading attempt history…</p>
        </SectionCard>
      ) : attemptsQuery.isError ? (
        <SectionCard
          title="Attempt history"
          action={{ label: 'Retry', onClick: () => void attemptsQuery.refetch() }}
        >
          <p className="text-sm text-fg-muted" data-testid="attempt-history-not-captured">
            Attempt history could not be loaded for this run.
          </p>
        </SectionCard>
      ) : attempts.length === 0 ? (
        <SectionCard title="Attempt history">
          <p className="text-sm text-fg-muted" data-testid="attempt-history-not-captured">
            Attempt history not captured for this run.
          </p>
        </SectionCard>
      ) : (
        <div className="flex flex-col gap-3" data-testid="attempt-history-panel">
          <AutopsyStrip attempts={attempts} runStatus={run.status} nowMs={liveNowMs} quarantine={extractQuarantine(events)} />
          <BurnPanel attempts={attempts} budget={attemptBudget} />
          <AttemptTimeline
            attempts={attempts}
            nowMs={liveNowMs}
            selectedAttemptId={selectedAttemptId}
            onAttemptClick={(attemptId) => setSelectedAttemptId(attemptId)}
          />
        </div>
      )}
      <SettingsTable items={settings} onSave={(key, layer, value) => void saveConfig(key, layer, value)} />
      <SegmentDetailDrawer
        lane={selectedLane}
        segmentIndex={selectedSegment?.index ?? null}
        open={selectedLane !== null}
        onClose={() => setSelectedSegment(null)}
        agentHref={
          selectedLane?.segments[selectedSegment?.index ?? -1]?.agentId
            ? `/plans/${runId}/agents/${selectedLane.segments[selectedSegment?.index ?? -1]?.agentId}`
            : undefined
        }
      />
      <AttemptDetailDrawer
        attempt={selectedAttempt}
        open={selectedAttempt !== null}
        onClose={() => setSelectedAttemptId(null)}
        promptHref={selectedAttempt ? harnessAttemptArtifactUrl(runId, selectedAttempt.attemptId, 'prompt') : undefined}
        replyHref={selectedAttempt ? harnessAttemptArtifactUrl(runId, selectedAttempt.attemptId, 'reply') : undefined}
      />
      <DecisionDialog
        detail={selectedDecision ? isHarnessDecision(selectedDecision) ? authoritativeDecisionDetail(selectedDecision) : decisionDetail(selectedDecision) : null}
        open={selectedDecision !== null}
        onClose={() => setSelectedDecision(null)}
        onDecide={(option) => void submitDecision(option)}
        toastHandle={toastHandle}
      />
      <UndoToast handleRef={undoRef} />
      <DeckTooltipLayer />
    </div>
  )
}

export function PlanRunApp({ runId }: { runId: string }) {
  return (
    <CollectorPageApp>
      <PlanRunContent runId={runId} />
    </CollectorPageApp>
  )
}
