import {
  Button,
  DetailDrawer,
  KvPanel,
  SectionHeading,
  StatusChip,
  UndoToast,
  formatDurationMs,
  type UndoToastHandle,
} from '@overdeck/deck-ui'
import {
  DataTable,
  withColumnResizing,
  withSearch,
  withSorting,
  type DataTableColumn,
} from '@overdeck/deck-ui'
import { useId, useRef, useState, type JSX, type ReactNode } from 'react'
import type {
  FactoryAgentAttemptView,
  FactoryDiffFileView,
  FactoryGateResultView,
  FactoryPhaseDiffView,
  FactoryProcessView,
} from '../../lib/factory-types'
import { displayText, formatAbsoluteTimestamp, formatTokenCount } from './factory-helpers'
import { AgentInputPanel } from './AgentInputPanel'

type FactoryToolCallView = {
  toolCallId: string
  seq: number
  toolName: string | null
  args: Record<string, unknown> | null
  startedAt: string | null
  endedAt: string | null
  durationMs: number | null
  ok: boolean | null
  resultExcerpt: string | null
}

type ObservableFactoryAttempt = FactoryAgentAttemptView & {
  timeoutKind?: 'wall' | 'idle' | null
  toolCalls?: FactoryToolCallView[]
}

export function artifactHref(path: string): string {
  return `/api/collector/factory/artifact?path=${encodeURIComponent(path)}`
}

const ARTIFACT_PAGE_BYTES = 1024 * 1024

type ArtifactContent = {
  text: string
  start: number
  end: number
  total: number
  bytes: number
}

function artifactSize(bytes: number): string {
  return `${bytes.toLocaleString()} bytes`
}

export function ArtifactLink(props: { path: string | null; label?: string }): ReactNode {
  const titleId = useId()
  const [open, setOpen] = useState(false)
  const [content, setContent] = useState<ArtifactContent | null>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  if (!props.path) return '—'

  async function fetchArtifact(range?: { start: number; end: number }, prepend = false): Promise<void> {
    setLoading(true)
    setError(null)
    try {
      const response = await fetch(artifactHref(props.path!), {
        headers: range ? { Range: `bytes=${range.start}-${range.end}` } : undefined,
      })
      if (!response.ok) throw new Error(`Artifact request failed (${response.status})`)
      const text = await response.text()
      const totalHeader = response.headers.get('x-overdeck-artifact-size')
      const lengthHeader = response.headers.get('content-length')
      const total = Number(totalHeader)
      const responseBytes = Number(lengthHeader)
      if (totalHeader === null || lengthHeader === null || !Number.isSafeInteger(total) || !Number.isSafeInteger(responseBytes)) {
        throw new Error('Artifact size metadata is unavailable')
      }
      const start = range?.start ?? Math.max(0, total - responseBytes)
      const end = responseBytes === 0 ? start : start + responseBytes - 1
      setContent((current) => prepend && current
        ? { text: text + current.text, start, end: current.end, total, bytes: responseBytes + current.bytes }
        : { text, start, end, total, bytes: responseBytes })
    } catch (cause) {
      setError(cause instanceof Error ? cause.message : 'Artifact request failed')
    } finally {
      setLoading(false)
    }
  }

  function showArtifact(): void {
    setOpen(true)
    if (!content) void fetchArtifact()
  }

  const shownBytes = content?.bytes ?? 0
  return <>
    <Button size="sm" variant="ghost" tone="neutral" onClick={showArtifact}>{props.label ?? 'Open log'}</Button>
    {open ? (
      <DetailDrawer eyebrow="Factory artifact" title={props.path} titleId={titleId} onClose={() => setOpen(false)}>
        <div className="mt-4 space-y-3">
          {content ? (
            <p className="text-xs tabular-nums text-fg-subtle">
              {content.start > 0
                ? `Showing tail of ${artifactSize(shownBytes)} out of ${artifactSize(content.total)}.`
                : `Showing ${artifactSize(shownBytes)} out of ${artifactSize(content.total)}.`}
            </p>
          ) : null}
          {error ? <p className="text-sm text-danger">{error}</p> : null}
          {content?.start ? (
            <div className="flex flex-wrap gap-2">
              <Button size="sm" variant="outline" tone="neutral" disabled={loading} onClick={() => {
                const start = Math.max(0, content.start - ARTIFACT_PAGE_BYTES)
                void fetchArtifact({ start, end: content.start - 1 }, true)
              }}>Load more</Button>
              <Button size="sm" variant="ghost" tone="neutral" disabled={loading} onClick={() => void fetchArtifact({ start: 0, end: content.total - 1 })}>Load whole file</Button>
            </div>
          ) : null}
          {loading && !content ? <p className="text-sm text-fg-subtle">Loading artifact…</p> : null}
          {content ? <pre className="max-h-96 overflow-auto whitespace-pre-wrap break-words rounded border border-border bg-bg p-3 font-mono text-xs text-fg">{content.text}</pre> : null}
        </div>
      </DetailDrawer>
    ) : null}
  </>
}

function previewLine(text: string): string {
  const flat = text.replace(/\s+/g, ' ').trim()
  return flat.length > 140 ? `${flat.slice(0, 140)}…` : flat
}

export function CollapsibleText(props: { eyebrow: string; label: string; text: string }): JSX.Element {
  const [open, setOpen] = useState(false)
  const titleId = useId()
  const toastRef = useRef<UndoToastHandle>(null)
  const lines = props.text.split('\n').length

  async function copyText(): Promise<void> {
    try {
      await navigator.clipboard.writeText(props.text)
      toastRef.current?.toast('Copied to clipboard')
    } catch {
      toastRef.current?.toast('Clipboard blocked — select the text to copy')
    }
  }

  return (
    <>
      <div className="space-y-1">
        <div className="flex items-center gap-2">
          <p className="text-xs font-semibold text-fg-subtle">{props.label}</p>
          <span className="text-xs text-fg-subtle">{lines} lines · {props.text.length} chars</span>
          <Button size="sm" variant="ghost" tone="neutral" aria-expanded={open} onClick={() => setOpen(!open)}>
            {open ? 'Show less' : 'Read more'}
          </Button>
        </div>
        <p className="truncate rounded border border-border bg-bg px-3 py-2 font-mono text-xs text-fg-subtle">{previewLine(props.text)}</p>
      </div>
      {open ? (
        <DetailDrawer eyebrow={props.eyebrow} title={props.label} titleId={titleId} onClose={() => setOpen(false)}>
          <div className="mt-4 space-y-3">
            <Button size="sm" variant="outline" tone="neutral" onClick={() => void copyText()}>Copy</Button>
            <pre className="whitespace-pre-wrap break-words rounded border border-border bg-bg p-3 font-mono text-xs text-fg">{props.text}</pre>
          </div>
          <UndoToast handleRef={toastRef} />
        </DetailDrawer>
      ) : null}
    </>
  )
}

function timeoutLabel(timeoutKind: ObservableFactoryAttempt['timeoutKind']): string {
  if (timeoutKind === 'wall') return 'timed out (wall clock)'
  if (timeoutKind === 'idle') return 'timed out (no output)'
  return 'timed out'
}

function modelParts(model: string | null): { provider: string | null; modelId: string | null } {
  if (!model || !model.includes('/')) return { provider: null, modelId: model }
  const [provider, ...rest] = model.split('/')
  return { provider, modelId: rest.join('/') || null }
}

function ToolCallDuration(props: { attemptEndedAt: string | null; toolCall: FactoryToolCallView }): JSX.Element {
  if (props.toolCall.endedAt === null) {
    return props.attemptEndedAt === null
      ? <StatusChip status="running" label="still running" />
      : <StatusChip status="killed" label="killed" />
  }
  return <span className="tabular-nums">{props.toolCall.durationMs === null ? '—' : formatDurationMs(props.toolCall.durationMs)}</span>
}

function ToolCallResult(props: { agent: string | null; toolCall: FactoryToolCallView }): JSX.Element {
  const call = props.toolCall
  const eyebrow = `${displayText(props.agent, 'agent')} · ${displayText(call.toolName, 'tool')} · #${call.seq}`
  const status = call.ok === null
    ? <StatusChip status="unknown" label="unknown" />
    : call.ok
      ? <StatusChip status="completed" label="succeeded" />
      : <StatusChip status="failed" label="failed" />

  return (
    <div className="min-w-0 space-y-2">
      {status}
      {call.args === null
        ? <p className="text-xs text-fg-subtle">Arguments: —</p>
        : <CollapsibleText eyebrow={eyebrow} label="Arguments" text={JSON.stringify(call.args, null, 2)} />}
      {call.resultExcerpt === null
        ? <p className="text-xs text-fg-subtle">Result: —</p>
        : <CollapsibleText eyebrow={eyebrow} label="Result" text={call.resultExcerpt} />}
    </div>
  )
}

const TOOL_CALL_CAPABILITIES = [
  withSorting({ defaultSort: { columnId: 'seq', direction: 'asc' } }),
  withSearch({ label: 'Search tool calls' }),
  withColumnResizing(),
]

function ToolCalls({ attempt }: { attempt: ObservableFactoryAttempt }): JSX.Element {
  const rows = [...(attempt.toolCalls ?? [])].sort((left, right) => left.seq - right.seq)
  const columns: DataTableColumn<FactoryToolCallView>[] = [
    { id: 'seq', header: 'Seq', sortable: true, sortValue: (call) => call.seq, minWidth: 70, cell: (call) => <span className="tabular-nums">{call.seq}</span> },
    { id: 'tool', header: 'Tool name', sortable: true, sortValue: (call) => call.toolName ?? '', searchValue: (call) => call.toolName ?? '', minWidth: 140, cell: (call) => <span className="font-mono text-xs">{displayText(call.toolName)}</span> },
    { id: 'started', header: 'Started', sortable: true, sortValue: (call) => call.startedAt ?? '', minWidth: 180, cell: (call) => <span className="tabular-nums">{formatAbsoluteTimestamp(call.startedAt)}</span> },
    { id: 'duration', header: 'Duration', sortable: true, sortValue: (call) => call.durationMs ?? -1, minWidth: 120, cell: (call) => <ToolCallDuration attemptEndedAt={attempt.endedAt} toolCall={call} /> },
    { id: 'result', header: 'Result', searchValue: (call) => `${call.ok === null ? 'unknown' : call.ok ? 'succeeded' : 'failed'} ${call.args === null ? '' : JSON.stringify(call.args)} ${call.resultExcerpt ?? ''}`, minWidth: 280, cell: (call) => <ToolCallResult agent={attempt.agent} toolCall={call} /> },
  ]

  return (
    <div className="pt-2">
      <SectionHeading title="Tool calls" />
      {rows.length === 0
        ? <p className="text-sm text-fg-muted">No tool calls recorded for this attempt.</p>
        : (
          <DataTable
            caption="Tool calls for this agent attempt"
            columns={columns}
            rows={rows}
            getRowId={(call) => call.toolCallId}
            capabilities={TOOL_CALL_CAPABILITIES}
          />
        )}
    </div>
  )
}

export function FactoryAttemptDetails({ attempt, runAttempts }: { attempt: FactoryAgentAttemptView; runAttempts: FactoryAgentAttemptView[] }): JSX.Element {
  const observableAttempt: ObservableFactoryAttempt = attempt
  const usage = attempt.usage
  const failure = attempt.providerFailure
  const { provider, modelId } = modelParts(attempt.model)
  return (
    <div className="mt-4 space-y-3">
      <KvPanel rows={[
        { label: 'agent', value: displayText(attempt.agent) },
        { label: 'provider', value: displayText(provider) },
        { label: 'model id', value: displayText(modelId) },
        { label: 'model', value: displayText(attempt.model) },
        { label: 'account', value: displayText(attempt.account) },
        { label: 'host', value: displayText(attempt.host) },
        { label: 'phase id', value: displayText(attempt.phaseId) },
        { label: 'attempt id', value: displayText(attempt.attemptId) },
        { label: 'session id', value: displayText(attempt.sessionId) },
        { label: 'started', value: formatAbsoluteTimestamp(attempt.startedAt) },
        { label: 'ended', value: formatAbsoluteTimestamp(attempt.endedAt) },
        { label: 'duration', value: attempt.durationMs === null ? '—' : formatDurationMs(attempt.durationMs) },
        { label: 'exit code', value: attempt.returncode === null ? '—' : String(attempt.returncode) },
        { label: 'signal', value: attempt.signal === null ? '—' : String(attempt.signal) },
        { label: 'timed out', value: attempt.timedOut ? (observableAttempt.timeoutKind === null || observableAttempt.timeoutKind === undefined ? 'yes' : timeoutLabel(observableAttempt.timeoutKind)) : 'no' },
        { label: 'tokens', value: formatTokenCount(attempt.tokens) },
        { label: 'context occupancy', value: usage?.usageEstimated ? `${formatTokenCount(usage.totalTokens)} est.` : '—' },
        { label: 'max output', value: usage?.maxTokens === null || usage?.maxTokens === undefined ? '—' : formatTokenCount(usage.maxTokens) },
        { label: 'billing', value: usage?.billingStatus ? `unavailable (${usage.billingStatus})` : '—' },
        { label: 'provider failure', value: failure ? `${failure.kind}: ${failure.detail}` : '—' },
        { label: 'resume at', value: displayText(failure?.resumeAt) },
        { label: 'error', value: displayText(attempt.error) },
      ]} />
      <AgentInputPanel attempt={attempt} runAttempts={runAttempts} />
      {attempt.command !== null ? <CollapsibleText eyebrow={`${displayText(attempt.agent, 'agent')} · ${displayText(attempt.phaseId, 'phase')}`} label="Full command" text={attempt.command} /> : null}
      <div className="flex items-center gap-2">
        <p className="text-xs font-semibold text-fg-subtle">Output log</p>
        <ArtifactLink path={attempt.stderrPath} />
      </div>
      <ToolCalls attempt={observableAttempt} />
    </div>
  )
}

function AttemptOutcome({ attempt }: { attempt: FactoryAgentAttemptView }): JSX.Element {
  if (attempt.timedOut) return <StatusChip status="failed" label={timeoutLabel((attempt as ObservableFactoryAttempt).timeoutKind)} />
  if (attempt.signal !== null) return <StatusChip status="failed" label={`signal ${attempt.signal}`} />
  if (attempt.returncode === null) return <StatusChip status="running" label="running" />
  return attempt.returncode === 0
    ? <StatusChip status="completed" label="exit 0" />
    : <StatusChip status="failed" label={`exit ${attempt.returncode}`} />
}

const ATTEMPT_CAPABILITIES = [
  withSorting({ defaultSort: { columnId: 'started', direction: 'asc' } }),
  withSearch({ label: 'Search attempts' }),
  withColumnResizing(),
]

export function FactoryAttemptTable(props: { attempts: FactoryAgentAttemptView[]; runAttempts: FactoryAgentAttemptView[] }): JSX.Element | null {
  const [selected, setSelected] = useState<FactoryAgentAttemptView | null>(null)
  const titleId = useId()
  if (props.attempts.length === 0) return null

  const columns: DataTableColumn<FactoryAgentAttemptView>[] = [
    {
      id: 'agent',
      header: 'Agent',
      sortable: true,
      sortValue: (attempt) => attempt.agent ?? '',
      searchValue: (attempt) => `${attempt.agent ?? ''} ${attempt.phaseId ?? ''} ${attempt.command ?? ''} ${attempt.userPrompt ?? ''}`,
      minWidth: 140,
      cell: (attempt) => (
        <Button size="sm" variant="ghost" tone="neutral" onClick={() => setSelected(attempt)}>
          <span className="flex items-center gap-1.5">
            <span aria-hidden="true" className="text-fg-subtle">▸</span>
            <span className="font-semibold">{displayText(attempt.agent)}</span>
          </span>
        </Button>
      ),
    },
    { id: 'phase', header: 'Phase', sortable: true, sortValue: (a) => a.phaseId ?? '', minWidth: 160, cell: (a) => <span className="text-fg-muted">{displayText(a.phaseId)}</span> },
    { id: 'model', header: 'Model', sortable: true, sortValue: (a) => a.model ?? '', minWidth: 140, cell: (a) => <span className="font-mono text-[11px]">{displayText(a.model)}</span> },
    { id: 'account', header: 'Account', sortable: true, sortValue: (a) => a.account ?? '', minWidth: 110, cell: (a) => displayText(a.account) },
    { id: 'host', header: 'Where', sortable: true, sortValue: (a) => a.host ?? '', minWidth: 100, cell: (a) => displayText(a.host) },
    { id: 'outcome', header: 'Outcome', sortable: true, sortValue: (a) => a.returncode ?? -1, minWidth: 110, cell: (a) => <AttemptOutcome attempt={a} /> },
    { id: 'started', header: 'Started', sortable: true, sortValue: (a) => a.startedAt ?? '', minWidth: 120, cell: (a) => <span className="tabular-nums">{formatAbsoluteTimestamp(a.startedAt)}</span> },
    { id: 'duration', header: 'Duration', sortable: true, sortValue: (a) => a.durationMs ?? 0, minWidth: 100, cell: (a) => <span className="tabular-nums">{a.durationMs === null ? '—' : formatDurationMs(a.durationMs)}</span> },
    {
      id: 'idle',
      header: 'Idle',
      sortable: true,
      sortValue: (a) => a.idleMs ?? 0,
      minWidth: 90,
      cell: (a) => a.endedAt !== null || a.idleMs === null
        ? <span className="text-fg-subtle">—</span>
        : <StatusChip status={a.idleMs < 120_000 ? 'completed' : 'failed'} label={formatDurationMs(a.idleMs)} />,
    },
    { id: 'tokens', header: 'Tokens', sortable: true, sortValue: (a) => a.tokens ?? 0, minWidth: 90, cell: (a) => <span className="tabular-nums">{formatTokenCount(a.tokens)}</span> },
    { id: 'log', header: 'Output log', minWidth: 110, cell: (a) => <ArtifactLink path={a.stderrPath} /> },
  ]

  return <>
    <DataTable
      caption="Factory agent attempts"
      columns={columns}
      rows={props.attempts}
      getRowId={(attempt) => attempt.attemptId}
      capabilities={ATTEMPT_CAPABILITIES}
    />
    {selected ? (
      <DetailDrawer
        size="wide"
        eyebrow={`${displayText(selected.agent, 'agent')} · ${displayText(selected.phaseId, 'phase')}`}
        title={displayText(selected.model, 'Agent attempt')}
        titleId={titleId}
        onClose={() => setSelected(null)}
      >
        <FactoryAttemptDetails attempt={selected} runAttempts={props.runAttempts} />
      </DetailDrawer>
    ) : null}
  </>
}

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

function stringField(value: unknown): string | null {
  return typeof value === 'string' && value.length > 0 ? value : null
}

function checkArtifact(check: Record<string, unknown>): string | null {
  for (const key of ['output_path', 'output_artifact', 'artifact_path', 'log_path', 'stderr_path', 'stdout_path', 'path']) {
    const value = stringField(check[key])
    if (value) return value
  }
  return null
}

function checkPassed(check: Record<string, unknown> | null): boolean | null {
  if (!check) return null
  if (typeof check.ok === 'boolean') return check.ok
  if (typeof check.passed === 'boolean') return check.passed
  if (typeof check.returncode === 'number') return check.returncode === 0
  return null
}

function GateVerdict({ passed }: { passed: boolean | null }): JSX.Element {
  if (passed === null) return <StatusChip status="unknown" label="unknown" />
  return passed
    ? <StatusChip status="completed" label="passed" />
    : <StatusChip status="failed" label="failed" />
}

type CheckRow = {
  id: string
  index: number
  label: string
  passed: boolean | null
  command: string
  exitCode: number | null
  durationMs: number | null
  commandLog: string | null
  note: string
  artifact: string | null
  raw: unknown
}

function checkRows(gate: FactoryGateResultView): CheckRow[] {
  if (!Array.isArray(gate.checks)) return []
  return gate.checks.map((value, index) => {
    const check = record(value)
    return {
      id: `check-${index}`,
      index,
      label: check ? displayText(stringField(check.item) ?? stringField(check.name), `Check ${index + 1}`) : `Check ${index + 1}`,
      passed: checkPassed(check),
      command: check ? displayText(stringField(check.command) ?? stringField(check.cmd)) : '—',
      exitCode: check && typeof check.exit_code === 'number'
        ? check.exit_code
        : check && typeof check.returncode === 'number'
          ? check.returncode
          : null,
      durationMs: check && typeof check.duration_ms === 'number' ? check.duration_ms : null,
      commandLog: check ? stringField(check.command_log) : null,
      note: check ? displayText(stringField(check.note) ?? stringField(check.reason)) : '—',
      artifact: check ? checkArtifact(check) : null,
      raw: value,
    }
  })
}

const CHECK_CAPABILITIES = [withSorting(), withColumnResizing()]

const CHECK_COLUMNS: DataTableColumn<CheckRow>[] = [
  { id: 'item', header: 'Check', sortable: true, sortValue: (row) => row.label, minWidth: 160, cell: (row) => <span className="font-semibold">{row.label}</span> },
  { id: 'status', header: 'Pass / fail', sortable: true, sortValue: (row) => (row.passed === null ? 2 : row.passed ? 1 : 0), minWidth: 110, cell: (row) => <GateVerdict passed={row.passed} /> },
  { id: 'command', header: 'Command', minWidth: 240, cell: (row) => <span className="font-mono text-[11px] text-fg-muted">{row.command}</span> },
  { id: 'returncode', header: 'Return code', sortable: true, sortValue: (row) => row.exitCode ?? -1, minWidth: 100, cell: (row) => <span className="tabular-nums">{row.exitCode ?? '—'}</span> },
  { id: 'note', header: 'Note', minWidth: 160, cell: (row) => row.note },
  { id: 'output', header: 'Output artifact', minWidth: 120, cell: (row) => <ArtifactLink path={row.artifact} /> },
]

const FAILED_CHECK_COLUMNS: DataTableColumn<CheckRow>[] = [
  ...CHECK_COLUMNS.slice(0, 3),
  { id: 'exit-code', header: 'Exit code', sortable: true, sortValue: (row) => row.exitCode ?? -1, minWidth: 100, cell: (row) => <span className="tabular-nums">{row.exitCode ?? '—'}</span> },
  { id: 'duration', header: 'Duration', sortable: true, sortValue: (row) => row.durationMs ?? -1, minWidth: 100, cell: (row) => <span className="tabular-nums">{row.durationMs === null ? '—' : formatDurationMs(row.durationMs)}</span> },
  { id: 'command-log', header: 'Command log', minWidth: 280, cell: (row) => row.commandLog === null ? '—' : <span className="select-all break-all font-mono text-[11px] text-fg-muted">{row.commandLog}</span> },
  ...CHECK_COLUMNS.slice(4),
]

function Checks({ gate }: { gate: FactoryGateResultView }): JSX.Element {
  if (!Array.isArray(gate.checks)) {
    const content = gate.checks === null
      ? '—'
      : typeof gate.checks === 'string'
        ? gate.checks
        : JSON.stringify(gate.checks, null, 2)
    return <pre className="max-h-96 overflow-auto whitespace-pre-wrap break-words rounded border border-border bg-bg p-3 font-mono text-xs text-fg">{content}</pre>
  }
  const rows = checkRows(gate)
  return <>
    <DataTable
      caption={`${displayText(gate.gate, 'Gate')} checks`}
      columns={gate.passed === false ? FAILED_CHECK_COLUMNS : CHECK_COLUMNS}
      rows={rows}
      getRowId={(row) => row.id}
      capabilities={CHECK_CAPABILITIES}
    />
    <CollapsibleText eyebrow={`${displayText(gate.phaseId, 'phase')} · ${displayText(gate.gate, 'gate')}`} label="Raw gate result" text={JSON.stringify(gate.checks, null, 2)} />
  </>
}

type ObservableFactoryGate = FactoryGateResultView & { violations?: unknown }

function FailureDetails({ gate }: { gate: ObservableFactoryGate }): JSX.Element | null {
  if (gate.passed !== false) return null
  if (!Array.isArray(gate.violations) || gate.violations.length === 0) {
    return <p className="text-sm text-fg-muted">No failure details were recorded for this failed gate.</p>
  }
  return (
    <CollapsibleText
      eyebrow={`${displayText(gate.gate, 'gate')} · ${displayText(gate.phaseId, 'phase')}`}
      label="Failure detail"
      text={JSON.stringify(gate.violations, null, 2)}
    />
  )
}

const GATE_CAPABILITIES = [withSorting(), withColumnResizing()]

export function FactoryGateTable(props: { gates: FactoryGateResultView[] }): JSX.Element | null {
  const [selected, setSelected] = useState<number | null>(null)
  const titleId = useId()
  if (props.gates.length === 0) return null

  const rows = props.gates.map((gate, index) => ({ gate, index }))
  const columns: DataTableColumn<{ gate: FactoryGateResultView; index: number }>[] = [
    {
      id: 'gate',
      header: 'Gate',
      sortable: true,
      sortValue: (row) => row.gate.gate ?? '',
      minWidth: 180,
      cell: (row) => (
        <Button size="sm" variant="ghost" tone="neutral" onClick={() => setSelected(row.index)}>
          <span className="flex items-center gap-1.5">
            <span aria-hidden="true" className="text-fg-subtle">▸</span>
            <span className="font-semibold">{displayText(row.gate.gate)}</span>
          </span>
        </Button>
      ),
    },
    { id: 'phase', header: 'Phase', sortable: true, sortValue: (row) => row.gate.phaseId ?? '', minWidth: 160, cell: (row) => displayText(row.gate.phaseId) },
    { id: 'attempt', header: 'Attempt', sortable: true, sortValue: (row) => row.gate.attempt ?? 0, minWidth: 90, cell: (row) => <span className="tabular-nums">{row.gate.attempt ?? '—'}</span> },
    { id: 'checks', header: 'Checks', minWidth: 80, cell: (row) => <span className="tabular-nums">{Array.isArray(row.gate.checks) ? row.gate.checks.length : '—'}</span> },
    { id: 'status', header: 'Pass / fail', sortable: true, sortValue: (row) => (row.gate.passed === null ? 2 : row.gate.passed ? 1 : 0), minWidth: 110, cell: (row) => <GateVerdict passed={row.gate.passed} /> },
  ]

  const open: ObservableFactoryGate | null = selected === null ? null : props.gates[selected] ?? null
  return <>
    <DataTable
      caption="Factory gates"
      columns={columns}
      rows={rows}
      getRowId={(row) => `gate-${row.index}`}
      capabilities={GATE_CAPABILITIES}
    />
    {open ? (
      <DetailDrawer
        size="wide"
        eyebrow={displayText(open.phaseId, 'run')}
        title={displayText(open.gate, 'Gate')}
        titleId={titleId}
        onClose={() => setSelected(null)}
      >
        <div className="mt-4 space-y-3">
          <FailureDetails gate={open} />
          <Checks gate={open} />
        </div>
      </DetailDrawer>
    ) : null}
  </>
}

const DIFF_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'phase', direction: 'asc' } }), withColumnResizing()]

const DIFF_FILE_CAPABILITIES = [withSorting({ defaultSort: { columnId: 'path', direction: 'asc' } }), withColumnResizing()]

const DIFF_FILE_COLUMNS: DataTableColumn<FactoryDiffFileView>[] = [
  { id: 'path', header: 'File', sortable: true, sortValue: (file) => file.path, minWidth: 320, cell: (file) => <span className="select-all break-all font-mono text-[11px] text-fg">{file.path}</span> },
  { id: 'status', header: 'Change', sortable: true, sortValue: (file) => file.status ?? '', minWidth: 90, cell: (file) => displayText(file.status) },
  { id: 'insertions', header: 'Added', sortable: true, sortValue: (file) => file.insertions ?? 0, minWidth: 90, cell: (file) => <span className="tabular-nums text-fg-muted">{file.insertions === null ? '—' : `+${file.insertions}`}</span> },
  { id: 'deletions', header: 'Removed', sortable: true, sortValue: (file) => file.deletions ?? 0, minWidth: 90, cell: (file) => <span className="tabular-nums text-fg-muted">{file.deletions === null ? '—' : `−${file.deletions}`}</span> },
]

function DiffDetails({ diff }: { diff: FactoryPhaseDiffView }): JSX.Element {
  return (
    <div className="mt-4 space-y-3">
      <KvPanel rows={[
        { label: 'phase id', value: displayText(diff.phaseId) },
        { label: 'attempt', value: diff.attempt === null ? '—' : String(diff.attempt) },
        { label: 'files changed', value: String(diff.files.length) },
        { label: 'insertions', value: diff.insertions === null ? '—' : String(diff.insertions) },
        { label: 'deletions', value: diff.deletions === null ? '—' : String(diff.deletions) },
        { label: 'recorded', value: formatAbsoluteTimestamp(diff.createdAt) },
        { label: 'truncated', value: diff.truncated ? 'yes' : 'no' },
      ]} />
      {diff.files.length > 0 ? (
        <DataTable
          caption="Changed files"
          columns={DIFF_FILE_COLUMNS}
          rows={diff.files}
          getRowId={(file) => file.path}
          capabilities={DIFF_FILE_CAPABILITIES}
        />
      ) : null}
      {diff.diffText !== null ? (
        <CollapsibleText eyebrow={displayText(diff.phaseId, 'phase')} label={diff.truncated ? 'Diff (truncated)' : 'Diff'} text={diff.diffText} />
      ) : <p className="text-sm text-fg-muted">No diff text was recorded for this phase.</p>}
    </div>
  )
}

export function FactoryDiffTable(props: { diffs: FactoryPhaseDiffView[] }): JSX.Element | null {
  const [selected, setSelected] = useState<number | null>(null)
  const titleId = useId()
  if (props.diffs.length === 0) return null

  const rows = props.diffs.map((diff, index) => ({ diff, index }))
  const columns: DataTableColumn<{ diff: FactoryPhaseDiffView; index: number }>[] = [
    {
      id: 'phase',
      header: 'Phase',
      sortable: true,
      sortValue: (row) => row.diff.phaseId,
      minWidth: 180,
      cell: (row) => (
        <Button size="sm" variant="ghost" tone="neutral" onClick={() => setSelected(row.index)}>
          <span className="flex items-center gap-1.5">
            <span aria-hidden="true" className="text-fg-subtle">▸</span>
            <span className="font-semibold">{displayText(row.diff.phaseId)}</span>
          </span>
        </Button>
      ),
    },
    { id: 'attempt', header: 'Attempt', sortable: true, sortValue: (row) => row.diff.attempt ?? 0, minWidth: 90, cell: (row) => <span className="tabular-nums">{row.diff.attempt ?? '—'}</span> },
    { id: 'files', header: 'Files', sortable: true, sortValue: (row) => row.diff.files.length, minWidth: 80, cell: (row) => <span className="tabular-nums">{row.diff.files.length}</span> },
    { id: 'insertions', header: 'Added', sortable: true, sortValue: (row) => row.diff.insertions ?? 0, minWidth: 90, cell: (row) => <span className="tabular-nums text-fg-muted">{row.diff.insertions === null ? '—' : `+${row.diff.insertions}`}</span> },
    { id: 'deletions', header: 'Removed', sortable: true, sortValue: (row) => row.diff.deletions ?? 0, minWidth: 90, cell: (row) => <span className="tabular-nums text-fg-muted">{row.diff.deletions === null ? '—' : `−${row.diff.deletions}`}</span> },
    { id: 'truncated', header: 'Complete', sortable: true, sortValue: (row) => (row.diff.truncated ? 0 : 1), minWidth: 110, cell: (row) => row.diff.truncated ? <StatusChip status="failed" label="truncated" /> : <StatusChip status="completed" label="full" /> },
    { id: 'recorded', header: 'Recorded', sortable: true, sortValue: (row) => row.diff.createdAt ?? '', minWidth: 120, cell: (row) => <span className="tabular-nums">{formatAbsoluteTimestamp(row.diff.createdAt)}</span> },
  ]

  const open = selected === null ? null : props.diffs[selected] ?? null
  return <>
    <DataTable
      caption="Factory phase diffs"
      columns={columns}
      rows={rows}
      getRowId={(row) => `diff-${row.index}`}
      capabilities={DIFF_CAPABILITIES}
    />
    {open ? (
      <DetailDrawer
        size="wide"
        eyebrow={displayText(open.phaseId, 'phase')}
        title="Phase diff"
        titleId={titleId}
        onClose={() => setSelected(null)}
      >
        <DiffDetails diff={open} />
      </DetailDrawer>
    ) : null}
  </>
}

const PROCESS_CAPABILITIES = [
  withSorting({ defaultSort: { columnId: 'started', direction: 'asc' } }),
  withSearch({ label: 'Search processes' }),
  withColumnResizing(),
]

export function FactoryProcessTable(props: { processes: FactoryProcessView[] }): JSX.Element | null {
  if (props.processes.length === 0) return null

  const rows = props.processes.map((process, index) => ({ process, index }))
  const columns: DataTableColumn<{ process: FactoryProcessView; index: number }>[] = [
    { id: 'kind', header: 'Kind', sortable: true, sortValue: (row) => row.process.kind ?? '', minWidth: 90, cell: (row) => <span className="font-semibold">{displayText(row.process.kind)}</span> },
    { id: 'name', header: 'Name', sortable: true, sortValue: (row) => row.process.name ?? '', minWidth: 140, cell: (row) => displayText(row.process.name) },
    { id: 'pid', header: 'PID', sortable: true, sortValue: (row) => row.process.pid ?? 0, minWidth: 90, cell: (row) => <span className="tabular-nums">{row.process.pid ?? '—'}</span> },
    { id: 'state', header: 'State', sortable: true, sortValue: (row) => (row.process.endedAt === null ? 0 : 1), minWidth: 110, cell: (row) => row.process.endedAt === null ? <StatusChip status="running" label="alive" /> : <StatusChip status="completed" label="exited" /> },
    { id: 'started', header: 'Started', sortable: true, sortValue: (row) => row.process.startedAt ?? '', minWidth: 120, cell: (row) => <span className="tabular-nums">{formatAbsoluteTimestamp(row.process.startedAt)}</span> },
    { id: 'ended', header: 'Ended', sortable: true, sortValue: (row) => row.process.endedAt ?? '', minWidth: 120, cell: (row) => <span className="tabular-nums">{formatAbsoluteTimestamp(row.process.endedAt)}</span> },
    { id: 'duration', header: 'Duration', sortable: true, sortValue: (row) => row.process.durationMs ?? -1, minWidth: 100, cell: (row) => <span className="tabular-nums">{row.process.durationMs === null ? '—' : formatDurationMs(row.process.durationMs)}</span> },
    {
      id: 'command',
      header: 'Command',
      minWidth: 280,
      searchValue: (row) => `${row.process.name ?? ''} ${row.process.command ?? ''}`,
      cell: (row) => row.process.command === null
        ? <span className="text-fg-subtle">—</span>
        : <span className="select-all break-all font-mono text-[11px] text-fg-muted">{row.process.command}</span>,
    },
  ]

  return (
    <DataTable
      caption="Factory processes"
      columns={columns}
      rows={rows}
      getRowId={(row) => `process-${row.index}`}
      capabilities={PROCESS_CAPABILITIES}
    />
  )
}
