import {
  AgentComposer,
  AgentFeed,
  AgentModeToggle,
  AgentStatusBar,
  DeckTooltipLayer,
  DetailDrawer,
  SectionCard,
  SectionHeading,
  UndoToast,
  type AgentTurn,
  type AgentViewMode,
  type UndoToastHandle,
  useSseStream,
} from '@overdeck/deck-ui'
import { QueryClientProvider, useQuery } from '@tanstack/react-query'
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { CollectorHttpError, fetchCollectorState, fetchFactoryRun, fetchHarnessEvents, harnessTaskStreamUrl, postCollectorAction } from '../../lib/collector-client'
import type { HarnessCapabilities, HarnessEvent } from '../../lib/harness-types'
import type {
  ForensicsPanelData,
  HarnessPlanRun,
  HarnessWaveTask,
} from '../../lib/panel-data'
import { createOverdeckQueryClient } from '../../lib/query-client'
import { CollectorRealtimeBridge } from '../overview/CollectorRealtimeBridge'
import { attachPhaseDiff, projectAgentActivity, selectUniquePhaseDiff } from './agent-activity-projection'

const PHASE_SUMMARY: Record<string, string> = {
  'llm-implement': 'Implementation activity recorded.',
  gate0: 'Build and test gate activity recorded.',
  'llm-review': 'Review activity recorded.',
  'llm-fixer': 'Fixer activity recorded.',
  'idle-restart': 'Restart recovery activity recorded.',
}
const MAX_STREAM_EVENTS = 1024

export function eventTurn(event: HarnessEvent): AgentTurn {
  const payload = event.payload
  const at = Date.parse(event.ts)
  const duration = payload.durationMs
  const summary = typeof payload.summary === 'string'
    ? payload.summary
    : typeof payload.text === 'string'
      ? payload.text
      : `${event.kind} recorded.`
  return {
    at: Number.isFinite(at) ? at : 0,
    durMs: typeof duration === 'number' && Number.isFinite(duration) ? duration : null,
    phase: event.kind,
    summary,
    detail: JSON.stringify(payload),
  }
}

export function appendStreamEvent(previous: HarnessEvent[], event: HarnessEvent): HarnessEvent[] {
  if (previous.some((item) => item.id === event.id)) return previous
  return [...previous, event].slice(-MAX_STREAM_EVENTS)
}

export interface AgentSnapshot {
  knownAgentIds: string[]
  live: boolean
  model: string
  task: HarnessWaveTask | null
  turns: AgentTurn[]
}

export function selectExecutingAccount(events: HarnessEvent[], taskId: string): string | null {
  for (let index = events.length - 1; index >= 0; index -= 1) {
    const event = events[index]!
    if (event.taskId !== taskId) continue
    if (event.kind !== 'dispatch.attempt' && event.kind !== 'attempt.prompt' && event.kind !== 'attempt.reply') continue
    const account = event.payload.account
    if (typeof account === 'string' && account.length > 0) return account
  }
  return null
}

export function selectSafeTaskAttempt(
  events: HarnessEvent[],
  taskId: string,
  capabilities: HarnessCapabilities | undefined,
): { attemptId: string | null; events: HarnessEvent[] } {
  if (capabilities?.attemptCorrelation !== true) return { attemptId: null, events: [] }
  const taskEvents = events.filter((event) => event.taskId === taskId)
  const started = taskEvents.filter(
    (event) => event.kind === 'attempt.started' && typeof event.attemptId === 'string' && event.attemptId.length > 0,
  )
  for (let index = started.length - 1; index >= 0; index -= 1) {
    const candidate = started[index]!
    const attemptId = candidate.attemptId
    const phase = candidate.payload.phase
    if (!attemptId || typeof phase !== 'string') continue
    const completed = taskEvents.some(
      (event) => event.kind === 'attempt.completed'
        && event.attemptId === attemptId
        && event.payload.phase === phase,
    )
    if (!completed) {
      return {
        attemptId,
        events: taskEvents.filter((event) => event.attemptId === attemptId),
      }
    }
  }
  return { attemptId: null, events: [] }
}

export function selectStoryTaskEvents(
  events: HarnessEvent[],
  taskId: string,
  capabilities: HarnessCapabilities | undefined,
): { attemptId: string | null; events: HarnessEvent[] } {
  const taskEvents = events.filter((event) => event.taskId === taskId)
  if (capabilities?.attemptCorrelation !== true) return { attemptId: null, events: taskEvents }

  const starts = taskEvents.filter(
    (event) => event.kind === 'attempt.started' && typeof event.attemptId === 'string' && event.attemptId.length > 0,
  )
  const latest = starts.at(-1)
  if (!latest?.attemptId) return { attemptId: null, events: taskEvents.filter((event) => event.attemptId === undefined) }
  return {
    attemptId: latest.attemptId,
    events: taskEvents.filter((event) => event.attemptId === latest.attemptId),
  }
}

export function deriveAgentSnapshot(
  run: HarnessPlanRun,
  forensics: ForensicsPanelData | undefined,
  agentId: string,
): AgentSnapshot {
  const segments = forensics?.segments ?? []
  const knownAgentIds = [...new Set(segments.flatMap((segment) => segment.agentId ?? []))].sort()
  const agentSegments = segments
    .filter((segment) => segment.agentId === agentId || segment.taskId === agentId)
    .sort((left, right) => left.t0 - right.t0)
  const taskId = agentSegments.find((segment) => segment.taskId)?.taskId
  const task = taskId
    ? run.waves.flatMap((wave) => wave.tasks).find((candidate) => candidate.id === taskId) ?? null
    : null
  const model = task?.model ?? task?.binding?.model ?? ''

  return {
    knownAgentIds,
    task,
    model,
    live: task?.status === 'running' || task?.status === 'paused',
    turns: agentSegments.map((segment) => ({
      at: segment.t0,
      durMs: segment.durMs,
      phase: segment.cat,
      summary: PHASE_SUMMARY[segment.cat] ?? `${segment.cat} activity recorded.`,
    })),
  }
}

function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false)
  useEffect(() => {
    if (typeof window.matchMedia !== 'function') return
    const media = window.matchMedia(query)
    const update = () => setMatches(media.matches)
    update()
    media.addEventListener('change', update)
    return () => media.removeEventListener('change', update)
  }, [query])
  return matches
}

function AgentContent({ runId, agentId, backHref }: { runId: string; agentId: string; backHref: string }) {
  const stateQuery = useQuery({ queryKey: ['collector-state'], queryFn: fetchCollectorState })
  const [mode, setMode] = useState<AgentViewMode>('live')
  const undoRef = useRef<UndoToastHandle>(null)
  const toastHandle = useMemo<UndoToastHandle>(
    () => ({
      toast(message) {
        undoRef.current?.toast(message)
      },
      toastUndo(message, options) {
        undoRef.current?.toastUndo(message, options)
      },
    }),
    [],
  )

  const plansPanel = stateQuery.data?.panels.find((panel) => panel.id === 'plans')
  const run = (plansPanel?.data as { runs?: HarnessPlanRun[] } | undefined)?.runs?.find(
    (candidate) => candidate.runId === runId,
  )
  const forensicsPanel = stateQuery.data?.panels.find((panel) => panel.id === `forensics:${runId}`)
  const forensics = forensicsPanel?.data as ForensicsPanelData | undefined
  const snapshot = useMemo(
    () => (run ? deriveAgentSnapshot(run, forensics, agentId) : null),
    [agentId, forensics, run],
  )
  const eventsQuery = useQuery({
    queryKey: ['harness-events', runId],
    queryFn: () => fetchHarnessEvents(runId),
    enabled: snapshot?.task != null,
  })
  const factoryQuery = useQuery({
    queryKey: ['collector-factory-run', runId],
    queryFn: () => fetchFactoryRun(runId),
    enabled: snapshot?.task != null,
    retry: false,
  })
  const [streamEvents, setStreamEvents] = useState<HarnessEvent[]>([])
  const events = useMemo(() => {
    const byId = new Map<string, HarnessEvent>()
    for (const event of [...(eventsQuery.data?.events ?? []), ...streamEvents]) byId.set(event.id, event)
    return [...byId.values()]
  }, [eventsQuery.data?.events, streamEvents])
  const activeAttempt = snapshot?.task
    ? selectSafeTaskAttempt(events, snapshot.task.id, eventsQuery.data?.capabilities)
    : { attemptId: null, events: [] }
  const storyEvents = snapshot?.task
    ? selectStoryTaskEvents(events, snapshot.task.id, eventsQuery.data?.capabilities)
    : { attemptId: null, events: [] }
  const [expandedGroups, setExpandedGroups] = useState<Set<string>>(() => new Set())
  const [diagnosticItemId, setDiagnosticItemId] = useState<string | null | undefined>(undefined)
  const diagnosticTriggerRef = useRef<HTMLElement | null>(null)
  const wideDiagnostics = useMediaQuery('(min-width: 1280px)')
  const story = useMemo(() => {
    const projected = projectAgentActivity(storyEvents.events)
    const eventsById = new Map(storyEvents.events.map((event) => [event.id, event]))
    const diffs = factoryQuery.data?.diffs ?? []
    return {
      ...projected,
      items: projected.items.map((item) => {
        if (item.kind !== 'edit') return item
        const event = eventsById.get(item.diagnosticEventIds[0] ?? '')
        if (!event) return item
        return attachPhaseDiff(item, event, runId, selectUniquePhaseDiff(event, runId, item.path, diffs))
      }),
    }
  }, [factoryQuery.data?.diffs, runId, storyEvents.events])
  const diagnosticItem = diagnosticItemId == null ? null : story.items.find((item) => item.id === diagnosticItemId) ?? null
  const streamUrl = snapshot?.live && snapshot.task && eventsQuery.isSuccess
    ? harnessTaskStreamUrl(runId, snapshot.task.id)
    : null
  useSseStream<HarnessEvent>({
    url: streamUrl,
    onMessage: (event) => setStreamEvents((previous) => appendStreamEvent(previous, event)),
  })

  useEffect(() => {
    if (snapshot && !snapshot.live) setMode('historical')
  }, [snapshot?.live])

  useEffect(() => {
    if (typeof window === 'undefined') return
    const requestedMode = new URLSearchParams(window.location.search).get('mode')
    if (requestedMode === 'historical') setMode('historical')
  }, [])

  if (stateQuery.isLoading) {
    return <SectionCard title="Agent activity">Loading agent data…</SectionCard>
  }
  if (stateQuery.isError) {
    return (
      <SectionCard title="Agent activity" action={{ label: 'Retry', onClick: () => void stateQuery.refetch() }}>
        Collector state unavailable.
      </SectionCard>
    )
  }
  if (!run || !snapshot) {
    return <SectionCard title="Agent activity">Run {runId} is not present in collector state.</SectionCard>
  }
  if (!snapshot.task) {
    const known = snapshot.knownAgentIds.length > 0 ? snapshot.knownAgentIds.join(', ') : 'none recorded'
    return (
      <div className="grid gap-4">
        <SectionHeading title={`Agent ${agentId}`} action={{ label: 'Back to run', href: backHref }} />
        <SectionCard title="Agent not found">
          Agent {agentId} is not recorded for this run. Known agents: {known}.
        </SectionCard>
      </div>
    )
  }

  const task = snapshot.task
  const executingAccount = selectExecutingAccount(events, task.id)
  const liveView = snapshot.live && mode === 'live'
  const changeMode = (nextMode: AgentViewMode) => {
    if (nextMode === 'live' && !snapshot.live) return
    setMode(nextMode)
    if (typeof window !== 'undefined') {
      const url = new URL(window.location.href)
      url.searchParams.set('mode', nextMode)
      window.history.replaceState(null, '', url)
    }
  }
  const restart = () => {
    void postCollectorAction(
      'steer',
      { runId, taskId: task.id, restart: 'true' },
      'overdeck-web',
    )
      .then(() => toastHandle.toast('Restart requested.'))
      .catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
  }
  const send = async (text: string): Promise<void> => {
    await postCollectorAction('steer', { runId, taskId: task.id, text }, 'overdeck-web')
  }
  const capability = (name: string) => eventsQuery.data?.capabilities[name] === true
  const activeAttemptId = task.status === 'running' || task.status === 'paused' ? activeAttempt.attemptId : null
  const controlUnavailable = !activeAttemptId
    ? 'A single active attempt cannot be identified from current event data.'
    : 'Task control is not supported by this run.'
  const refreshAfterStale = async (error: unknown) => {
    if (error instanceof CollectorHttpError && error.status === 409) await Promise.all([stateQuery.refetch(), eventsQuery.refetch()])
    throw error
  }
  const control = activeAttemptId
    ? {
        mode: task.status === 'paused' ? 'resume' as const : 'pause' as const,
        onToggle: capability(task.status === 'paused' ? 'task.resume' : 'task.pause')
          ? () => void postCollectorAction(task.status === 'paused' ? 'harness.task.resume' : 'harness.task.pause', { runId, taskId: task.id, attemptId: activeAttemptId, requestId: crypto.randomUUID() }, 'overdeck-web').then(() => stateQuery.refetch()).catch(refreshAfterStale).catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
          : undefined,
        onKill: capability('task.kill')
          ? () => void postCollectorAction('harness.task.kill', { runId, taskId: task.id, attemptId: activeAttemptId, requestId: crypto.randomUUID() }, 'overdeck-web').then(() => stateQuery.refetch()).catch(refreshAfterStale).catch((error) => toastHandle.toast(error instanceof Error ? error.message : String(error)))
          : undefined,
        unavailableReason: controlUnavailable,
      }
    : { mode: 'pause' as const, unavailableReason: controlUnavailable }

  return (
    <div className="grid min-h-0 gap-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <SectionHeading
          title={`Agent ${agentId}`}
          subtitle={`· ${run.title}`}
          action={{ label: 'Back to run', href: backHref }}
        />
        <AgentModeToggle mode={mode} liveAvailable={snapshot.live} onModeChange={changeMode} />
      </div>
      <AgentStatusBar
        agent={{
          agentId,
          seat: task.seat ?? 'not recorded',
          taskId: task.id,
          model: snapshot.model,
          live: liveView,
          stateLabel: task.status === 'paused' ? 'Paused' : liveView ? 'Working' : 'Ended',
          currentWork: story.items.at(-1)?.title,
        }}
        onRestart={restart}
        toastHandle={toastHandle}
        controls={control}
      />
      <div data-agent-left-pane className="flex min-h-[42rem] flex-col gap-0">
        <AgentFeed
          items={story.items.map(item => item.kind === 'group' ? { ...item, expanded: expandedGroups.has(item.id) } : item)}
          live={liveView}
          onOpenDiagnostics={(id, trigger) => { diagnosticTriggerRef.current = trigger; setDiagnosticItemId(id) }}
          onToggleGroup={(id) => setExpandedGroups(previous => { const next = new Set(previous); next.has(id) ? next.delete(id) : next.add(id); return next })}
        />
        {liveView && <AgentComposer onSend={send} />}
      </div>
      {diagnosticItemId !== undefined && <DetailDrawer eyebrow="Agent diagnostics" title={diagnosticItem?.title ?? `Agent ${agentId}`} titleId="agent-diagnostics-title" modal={!wideDiagnostics} size={wideDiagnostics ? 'wide' : 'default'} onClose={() => { const trigger = diagnosticTriggerRef.current; setDiagnosticItemId(undefined); requestAnimationFrame(() => (trigger?.isConnected ? trigger : document.getElementById('agent-feed-title'))?.focus()) }}><div className="grid gap-4 py-4 text-sm"><section><h3 className="font-semibold text-fg">Context</h3><dl className="mt-2 grid gap-2 text-fg-muted"><div><dt className="font-medium text-fg">Task</dt><dd className="break-all">{task.id}</dd></div><div><dt className="font-medium text-fg">Attempt</dt><dd className="break-all">{storyEvents.attemptId ?? 'Not authoritatively linked'}</dd></div><div><dt className="font-medium text-fg">Connection</dt><dd>{eventsQuery.isError ? 'Event data unavailable' : liveView ? 'Live' : 'Recorded'}</dd></div><div><dt className="font-medium text-fg">Model</dt><dd>{snapshot.model || 'Not recorded'}</dd></div><div><dt className="font-medium text-fg">Account</dt><dd>{executingAccount ?? 'Not recorded'}</dd></div><div><dt className="font-medium text-fg">Branch</dt><dd className="break-all">{task.branch ?? 'Not recorded'}</dd></div></dl></section>{diagnosticItem && <section><h3 className="font-semibold text-fg">Recorded event</h3><p className="mt-2 whitespace-pre-wrap break-words text-fg-muted">{diagnosticItem.summary ?? 'No summary was recorded.'}</p><p className="mt-2 text-fg-muted">Event IDs: {diagnosticItem.diagnosticEventIds.join(', ') || 'None recorded'}</p></section>}<section><h3 className="font-semibold text-fg">Coverage</h3><p className="mt-2 text-fg-muted">{story.warnings.length > 0 ? story.warnings.map((warning) => warning.title).join('. ') : 'No projection coverage warnings were recorded.'}</p></section></div></DetailDrawer>}
      <UndoToast handleRef={undoRef} />
      <DeckTooltipLayer />
    </div>
  )
}

export function AgentApp({ runId, agentId, backHref = `/plans/${encodeURIComponent(runId)}` }: { runId: string; agentId: string; backHref?: string }) {
  const [queryClient] = useState(createOverdeckQueryClient)
  return (
    <QueryClientProvider client={queryClient}>
      <CollectorRealtimeBridge />
      <AgentContent runId={runId} agentId={agentId} backHref={backHref} />
    </QueryClientProvider>
  )
}
