import { CodeBlock } from '@astryxdesign/core/CodeBlock'
import { Collapsible } from '@astryxdesign/core/Collapsible'
import { Drawer, LogStream, type LogEntry } from '@astryxdesign/lab'
import { DiffViewer, formatDurationMs, parseUnifiedDiff } from '@overdeck/deck-ui'
import type { FactoryAgentAttemptView, FactoryGateResultView, FactoryToolCallView } from '../../lib/factory-types'
import type { TranscriptStep } from './factory-run-view-helpers'
import { AgentInputPanel } from './AgentInputPanel'

type GateCheck = {
  label: string
  argv: string | null
  returncode: number | null
  passed: boolean | null
  output: string | null
}

const truncated = (text: string | null) => Boolean(text?.endsWith('\n… [truncated]'))
const text = (value: unknown): string | null => typeof value === 'string' && value.length > 0 ? value : null
const record = (value: unknown): Record<string, unknown> | null => value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null
const command = (args: Record<string, unknown> | null) => ['command', 'cmd', 'script'].map(key => text(args?.[key])).find((value): value is string => value !== null) ?? (Array.isArray(args?.argv) ? args.argv.map(String).join(' ') : '')
const toolDiff = (args: Record<string, unknown> | null): string | null => {
  if (!args) return null
  for (const key of ['diff', 'patch']) if (typeof args[key] === 'string') return args[key] as string
  if (typeof args.before === 'string' || typeof args.after === 'string') return `--- before\n+++ after\n@@ -1 +1 @@\n-${String(args.before ?? '')}\n+${String(args.after ?? '')}`
  const first = Array.isArray(args.edits) ? record(args.edits[0]) : null
  return typeof first?.oldText === 'string' || typeof first?.newText === 'string' ? `--- before\n+++ after\n@@ -1 +1 @@\n-${String(first?.oldText ?? '')}\n+${String(first?.newText ?? '')}` : null
}
const toolTarget = (tool: FactoryToolCallView): string => ['path', 'file', 'pattern', 'query'].map(key => text(tool.args?.[key])).find((value): value is string => value !== null) ?? tool.toolName ?? 'tool'
const iso = (value: string | null) => value ?? '—'
const failureReasons = (value: unknown): string[] => typeof value === 'string' ? [value] : Array.isArray(value) ? value.filter((reason): reason is string => typeof reason === 'string') : []

function gateChecks(gate: FactoryGateResultView): GateCheck[] {
  if (!Array.isArray(gate.checks)) return []
  return gate.checks.map((value, index) => {
    const check = record(value)
    const argv = text(check?.command) ?? text(check?.cmd) ?? (Array.isArray(check?.argv) ? check.argv.map(String).join(' ') : null)
    const returncode = typeof check?.returncode === 'number' ? check.returncode : typeof check?.exit_code === 'number' ? check.exit_code : null
    const passed = typeof check?.ok === 'boolean' ? check.ok : typeof check?.passed === 'boolean' ? check.passed : returncode === null ? null : returncode === 0
    return { label: text(check?.item) ?? text(check?.name) ?? `Check ${index + 1}`, argv, returncode, passed, output: text(check?.output) ?? text(check?.['output_tail']) ?? text(check?.command_log) ?? text(check?.output_excerpt) }
  })
}

function ToolCall({ tool }: { tool: FactoryToolCallView }) {
  const name = (tool.toolName ?? '').toLowerCase()
  const output = tool.resultExcerpt ?? ''
  if (name.includes('bash') || name === 'command') {
    const entries: LogEntry[] = [{ id: `${tool.toolCallId}-command`, timestamp: '', level: tool.ok === false ? 'error' : 'info', message: command(tool.args) }, { id: `${tool.toolCallId}-output`, timestamp: '', level: tool.ok === false ? 'error' : 'info', message: output }]
    return <div><LogStream variant="terminal" hasTimestamps={false} entries={entries} label={tool.toolName ?? 'bash'} />{truncated(output) && <p className="text-xs text-fg-muted">Result excerpt truncated.</p>}</div>
  }
  const patch = toolDiff(tool.args)
  if (name.includes('write') || name.includes('edit')) return <div><h3 className="font-medium">{tool.toolName ?? 'tool'}</h3>{patch ? <DiffViewer diff={patch} /> : typeof tool.args?.content === 'string' ? <CodeBlock code={tool.args.content} language="text" /> : null}{truncated(output) && <p className="text-xs text-fg-muted">Result excerpt truncated.</p>}</div>
  if (['read', 'ls', 'grep', 'find'].some(kind => name === kind || name.includes(kind))) return <Collapsible trigger={`${tool.toolName ?? 'tool'} · ${toolTarget(tool)}`} defaultIsOpen={false}><CodeBlock code={output} language="text" />{truncated(output) && <p className="text-xs text-fg-muted">Result excerpt truncated.</p>}</Collapsible>
  return <div><h3 className="font-medium">{tool.toolName ?? 'tool'}</h3><CodeBlock code={[JSON.stringify(tool.args ?? {}, null, 2), output].filter(Boolean).join('\n')} language="text" />{truncated(output) && <p className="text-xs text-fg-muted">Result excerpt truncated.</p>}</div>
}

function FileDiff({ diffText, path }: { diffText: string; path: string }) {
  const files = parseUnifiedDiff(diffText).files.filter(file => file.newPath === path || file.oldPath === path)
  return <DiffViewer files={files} allowViewToggle={false} ariaLabel={`Diff for ${path}`} />
}

function Gate({ gate }: { gate: FactoryGateResultView }) {
  const checks = gateChecks(gate)
  const reasons = failureReasons(gate.violations)
  return <div className="space-y-2"><h3 className="font-medium">{gate.gate ?? 'Gate'} — {gate.passed === null ? 'unknown' : gate.passed ? 'passed' : 'failed'}</h3>{checks.map((check, index) => <div key={`${check.label}-${index}`}><p className="text-xs">{check.label} · {check.passed === null ? 'unknown' : check.passed ? 'passed' : 'failed'} · return code {check.returncode ?? '—'}</p>{check.argv && <p className="font-mono text-xs text-fg-muted">{check.argv}</p>}{check.output && <LogStream variant="terminal" hasTimestamps={false} label={check.label} entries={[{ id: `${index}-output`, timestamp: '', level: check.passed === false ? 'error' : 'info', message: check.output }]} />}</div>)}{reasons.map((reason, index) => <LogStream key={index} variant="terminal" hasTimestamps={false} label="Failure output" entries={[{ id: `${index}-violation`, timestamp: '', level: 'error', message: reason }]} />)}</div>
}

export function FactoryStepDrawer({ step, gates, runAttempts, onClose }: { step: TranscriptStep; gates: FactoryGateResultView[]; runAttempts: FactoryAgentAttemptView[]; onClose: () => void }) {
  const attempt: FactoryAgentAttemptView | null = step.type === 'attempt' ? step.attempt : null
  const tools = [...(attempt?.toolCalls ?? [])].sort((a, b) => a.seq - b.seq)
  const startedAt = attempt?.startedAt ?? step.phase.startedAt
  const endedAt = attempt?.endedAt ?? step.phase.endedAt
  const duration = attempt?.durationMs ?? step.phase.durationMs
  return <Drawer isOpen onClose={onClose} label="Factory step details" side="end" size="55%"><div className="space-y-5 p-4 text-sm text-fg"><section><h2 className="font-semibold">Identity</h2><dl className="mt-2 grid grid-cols-2 gap-2 text-xs"><dt>Agent</dt><dd>{attempt?.agent ?? step.phase.owner ?? '—'}</dd><dt>Model</dt><dd>{attempt?.model ?? '—'}</dd><dt>Account</dt><dd>{attempt?.account ?? '—'}</dd><dt>Host</dt><dd>{attempt?.host ?? '—'}</dd><dt>Started</dt><dd className="tabular-nums">{iso(startedAt)}</dd><dt>Ended</dt><dd className="tabular-nums">{iso(endedAt)}</dd><dt>Duration</dt><dd>{duration == null ? '—' : formatDurationMs(duration)}</dd>{attempt && <><dt>Command</dt><dd className="break-all font-mono">{attempt.command ?? '—'}</dd><dt>Return code</dt><dd>{attempt.returncode ?? '—'}</dd><dt>Session</dt><dd>{attempt.sessionId ?? '—'}</dd></>}</dl></section>{attempt && <AgentInputPanel attempt={attempt} runAttempts={runAttempts} />}{tools.length > 0 && <section className="space-y-3"><h2 className="font-semibold">Tool calls</h2>{tools.map(tool => <ToolCall key={tool.toolCallId} tool={tool} />)}</section>}{step.diffs.length > 0 && <section className="space-y-2"><h2 className="font-semibold">Files changed</h2>{step.diffs.map((diff, index) => <div key={`${diff.phaseId}-${diff.attempt}-${index}`}>{diff.truncated && <p className="text-xs text-fg-muted">Diff output truncated.</p>}{diff.files.map(file => <Collapsible key={file.path} trigger={`${file.path} ${file.status ?? ''} ${file.insertions == null ? '' : `+${file.insertions}`} ${file.deletions == null ? '' : `−${file.deletions}`}`}><FileDiff diffText={diff.diffText ?? ''} path={file.path} /></Collapsible>)}</div>)}</section>}{gates.length > 0 && <section className="space-y-3"><h2 className="font-semibold">Gates</h2>{gates.map((gate, index) => <Gate key={`${gate.gate}-${index}`} gate={gate} />)}</section>}</div></Drawer>
}
