import { Fragment, useId, useMemo } from 'react'
import { traceAxisTicks } from './format'
import { useDeckTooltip } from './DeckTooltip'
import { TraceSegment } from './TraceSegment'

export interface TraceLane {
  taskId: string
  name: string
  wave: number | null
  segments: Array<{
    t0: number
    durMs: number
    cat: string
    status?: string
    agentId?: string
    running?: boolean
    note?: string
    journalSeq?: number
  }>
}

export interface ConditionBand {
  wrapper: string
  fromMs: number
  toMs: number
  laneIds: string[]
  label: string
}

type Zoom = 'fit' | 'hour' | 'live'

const TRACE_GRID_COLUMNS = 'grid-cols-[180px_minmax(0,1fr)]'

function hasTraceTemporalData(lanes: TraceLane[], bands: ConditionBand[]): boolean {
  return bands.length > 0 || lanes.some((lane) => lane.segments.length > 0)
}

function effectiveZoom(nowMs: number | null, zoom: Zoom): Zoom {
  if (nowMs === null && zoom === 'live') return 'fit'
  return zoom
}

function traceBounds(lanes: TraceLane[], bands: ConditionBand[], nowMs: number | null, zoom: Zoom) {
  const starts = lanes.flatMap((lane) => lane.segments.map((segment) => segment.t0))
  const ends = lanes.flatMap((lane) => lane.segments.map((segment) => segment.t0 + segment.durMs))
  if (!hasTraceTemporalData(lanes, bands)) return { start: 0, end: 1 }
  const fitStart = Math.min(...starts, ...bands.map((band) => band.fromMs))
  const fitEnd = Math.max(...ends, ...bands.map((band) => band.toMs))
  if (!Number.isFinite(fitStart) || !Number.isFinite(fitEnd)) return { start: 0, end: 1 }
  if (zoom === 'fit') return { start: fitStart, end: Math.max(fitStart + 1, fitEnd) }
  const end = nowMs ?? fitEnd
  const duration = zoom === 'hour' ? 3_600_000 : 900_000
  return { start: end - duration, end }
}

function geometry(fromMs: number, toMs: number, start: number, end: number) {
  const duration = Math.max(1, end - start)
  const clippedStart = Math.max(start, fromMs)
  const clippedEnd = Math.min(end, toMs)
  return {
    left: ((clippedStart - start) / duration) * 100,
    width: (Math.max(0, clippedEnd - clippedStart) / duration) * 100,
  }
}

function TraceTimeAxisTick(props: {
  tick: { pct: number; label: string; epochMs: number }
  tickIndex: number
  tickCount: number
}) {
  const tooltip = useDeckTooltip(new Date(props.tick.epochMs).toISOString())
  const isFirst = props.tickIndex === 0
  const isLast = props.tickIndex === props.tickCount - 1

  return (
    <div
      data-testid="trace-time-axis-tick"
      data-epoch-ms={props.tick.epochMs}
      className={`absolute top-0 flex flex-col ${
        isFirst
          ? 'left-0 items-start'
          : isLast
            ? 'left-full -translate-x-full items-end'
            : '-translate-x-1/2 items-center'
      }`}
      style={!isFirst && !isLast ? { left: `${props.tick.pct}%` } : undefined}
      aria-hidden="true"
      {...tooltip}
    >
      <span className="whitespace-nowrap text-xs tabular-nums text-fg-subtle">{props.tick.label}</span>
      <span className="mt-0.5 h-2 w-px bg-border" />
    </div>
  )
}

export function SwimlaneTrace(props: {
  lanes: TraceLane[]
  nowMs: number | null
  bands: ConditionBand[]
  zoom: Zoom
  onZoom(z: Zoom): void
  onSegmentClick(laneId: string, segIndex: number): void
  title?: string
  description?: string
  showGroups?: boolean
}) {
  const titleId = useId()
  const axisLabelId = useId()
  const zoom = effectiveZoom(props.nowMs, props.zoom)
  const liveAvailable = props.nowMs !== null
  const { start, end } = traceBounds(props.lanes, props.bands, props.nowMs, zoom)
  const hasTemporalData = hasTraceTemporalData(props.lanes, props.bands)
  const axisTicks = useMemo(
    () => (hasTemporalData ? traceAxisTicks(start, end) : []),
    [hasTemporalData, start, end],
  )
  const axisRangeLabel =
    axisTicks.length > 0
      ? `UTC wall-clock range from ${new Date(start).toISOString()} (${axisTicks[0]!.label}) to ${new Date(end).toISOString()} (${axisTicks.at(-1)!.label})`
      : ''
  const nowInView =
    props.nowMs !== null && props.nowMs >= start && props.nowMs <= end
  const nowGeometry = nowInView ? geometry(props.nowMs!, props.nowMs!, start, end) : null
  const nowLabelBeforePlayhead = nowGeometry !== null && nowGeometry.left >= 50

  return (
    <section aria-labelledby={titleId} className="min-w-0 rounded-xl border border-border bg-surface">
      <header className="flex flex-wrap items-center gap-4 border-b border-border px-5 py-4">
        <div>
          <h2 id={titleId} className="text-base font-semibold text-fg">
            {props.title ?? 'Run timeline'}
          </h2>
          <p className="mt-0.5 text-xs text-fg-muted">{props.description ?? 'one row per task · shared clock'}</p>
        </div>
        <div className="ml-auto inline-flex overflow-hidden rounded-md border border-border" role="group" aria-label="Timeline window">
          {([
            ['fit', 'Fit run'],
            ['hour', 'Last hour'],
            ['live', 'Follow live'],
          ] as const).map(([value, label]) => (
            <button
              key={value}
              type="button"
              disabled={value === 'live' && !liveAvailable}
              aria-pressed={zoom === value}
              onClick={() => props.onZoom(value)}
              className="min-h-11 border-l border-border px-3 text-xs font-semibold text-fg-muted first:border-l-0 aria-pressed:bg-surface-raised aria-pressed:text-fg disabled:cursor-not-allowed disabled:opacity-50"
            >
              {label}
            </button>
          ))}
        </div>
      </header>

      {props.lanes.length === 0 ? (
        <p className="px-5 py-6 text-sm text-fg-muted">No timeline segments recorded yet.</p>
      ) : (
        <div className="overflow-x-auto" data-testid="trace-scroll-container">
          <div className="min-w-4xl">
            <div className="relative" data-testid="trace-lanes-plot">
              {props.lanes.map((lane, laneIndex) => {
                const startsWave = props.showGroups !== false && (laneIndex === 0 || props.lanes[laneIndex - 1]?.wave !== lane.wave)
                return (
                  <Fragment key={lane.taskId}>
                    {startsWave && (
                      <div className="border-y border-border bg-surface-raised px-5 py-1.5 text-xs font-semibold text-fg-subtle first:border-t-0">
                        {lane.wave === null ? 'Sequence not recorded' : `Wave ${lane.wave}`}
                      </div>
                    )}
                    <div
                      data-testid={`trace-lane-${lane.taskId}`}
                      data-lane-id={lane.taskId}
                      className={`grid min-h-12 ${TRACE_GRID_COLUMNS} border-b border-border last:border-b-0`}
                    >
                      <div className="min-w-0 px-4 py-2">
                        <strong className="block truncate text-xs text-fg">{lane.taskId}</strong>
                        <span className="block truncate text-xs text-fg-muted">{lane.name}</span>
                      </div>
                      <div className="relative min-h-12 border-l border-border bg-surface-raised/40">
                        {props.bands
                          .filter((band) => band.laneIds.includes(lane.taskId))
                          .map((band) => {
                            const bandGeometry = geometry(band.fromMs, band.toMs, start, end)
                            if (bandGeometry.width <= 0) return null
                            return (
                              <div
                                key={`${band.wrapper}-${band.fromMs}`}
                                data-condition-band={band.wrapper}
                                data-tipb={band.label}
                                data-tips={`${band.wrapper} condition`}
                                className="absolute inset-y-0 z-0 overflow-hidden bg-[var(--mod-color-run-condition-ratelimit)]/20 text-[9px] text-fg-muted"
                                style={{ left: `${bandGeometry.left}%`, width: `${bandGeometry.width}%` }}
                              >
                                <span className="sr-only">{band.label}</span>
                              </div>
                            )
                          })}
                        {lane.segments.map((segment, segmentIndex) => {
                          const segmentGeometry = geometry(segment.t0, segment.t0 + segment.durMs, start, end)
                          if (segmentGeometry.width <= 0) return null
                          return (
                            <TraceSegment
                              key={`${segment.t0}-${segmentIndex}`}
                              laneId={lane.taskId}
                              segmentIndex={segmentIndex}
                              segment={segment}
                              leftPercent={segmentGeometry.left}
                              widthPercent={segmentGeometry.width}
                              startOffsetMs={Math.max(0, segment.t0 - start)}
                              onClick={() => props.onSegmentClick(lane.taskId, segmentIndex)}
                            />
                          )
                        })}
                      </div>
                    </div>
                  </Fragment>
                )
              })}

              {nowGeometry && (
                <div className={`pointer-events-none absolute inset-0 grid ${TRACE_GRID_COLUMNS}`}>
                  <div className="relative col-start-2">
                    <div
                      data-testid="trace-now-playhead"
                      className="absolute inset-y-0 z-20 w-0.5 bg-[var(--mod-color-run-playhead)]"
                      style={{ left: `${nowGeometry.left}%` }}
                    >
                      <span
                        data-testid="trace-now-label"
                        className={`absolute top-1 text-[9px] font-bold text-accent ${
                          nowLabelBeforePlayhead ? 'right-1' : 'left-1'
                        }`}
                      >
                        NOW
                      </span>
                    </div>
                  </div>
                </div>
              )}
            </div>

            {hasTemporalData ? (
              <div
                className={`grid ${TRACE_GRID_COLUMNS} border-t border-border`}
                role="group"
                aria-labelledby={axisLabelId}
                aria-describedby={`${axisLabelId}-range`}
              >
                <div id={axisLabelId} className="px-4 py-2 text-xs font-semibold text-fg-subtle">
                  Time (UTC)
                </div>
                <div
                  data-testid="trace-time-axis"
                  className="relative min-h-8 border-l border-border px-1 py-1.5"
                >
                  <p id={`${axisLabelId}-range`} className="sr-only">
                    {axisRangeLabel}
                  </p>
                  {axisTicks.map((tick, tickIndex) => (
                    <TraceTimeAxisTick
                      key={tick.epochMs}
                      tick={tick}
                      tickIndex={tickIndex}
                      tickCount={axisTicks.length}
                    />
                  ))}
                </div>
              </div>
            ) : null}
          </div>
        </div>
      )}
    </section>
  )
}
