import { Badge } from '@astryxdesign/core/Badge'
import { Button } from '@astryxdesign/core/Button'
import { Card } from '@astryxdesign/core/Card'
import { EmptyState } from '@astryxdesign/core/EmptyState'
import { TextInput } from '@astryxdesign/core/TextInput'
import { Step, Stepper } from '@astryxdesign/lab'
import { formatRelativeTime, safeHttpUrl, useDeckTooltip } from '@overdeck/deck-ui'
import { type JSX, useMemo, useState } from 'react'
import {
  deliveryEvents,
  distinctProjects,
  distinctPlanSlugs,
  distinctSessions,
  groupShipped,
  latestRequestEvent,
  matchesProject,
  matchesPlanSlug,
  matchesSession,
  matchesSearch,
  priorityVariant,
  queuePositionLabel,
  SHIPPED_BUCKET_ORDER,
  sortByAskedOrder,
  STAGE_LABELS,
  stageActiveStep,
  summaryCounts,
  sessionFilterLabel,
  waitReason,
  type DeliveryEventView,
} from '../../lib/request-board'
import type { RequestRow } from '../../lib/request-types'
import { RequestAnswerForm } from './RequestAnswerForm'

function Timestamp({ at, prefix }: { at: string; prefix?: string }): JSX.Element {
  const parsed = Date.parse(at)
  const absolute = Number.isNaN(parsed) ? at : new Date(parsed).toLocaleString()
  const tooltip = useDeckTooltip(absolute, 'Recorded at')
  return (
    <time dateTime={at} className="requests-time" {...tooltip}>
      {prefix}{Number.isNaN(parsed) ? 'time not recorded' : formatRelativeTime(parsed)}
    </time>
  )
}

function ProjectBadge({ project }: { project: string }): JSX.Element {
  return <Badge variant="neutral" label={project} />
}

function PriorityBadge({ priority }: { priority: string }): JSX.Element {
  return <Badge variant={priorityVariant(priority)} label={priority} />
}

function AgentInitiatedBadge({ origin }: { origin: RequestRow['origin'] }): JSX.Element | null {
  if (origin === 'owner') return null
  return <Badge variant="info" label="Started by an agent" />
}

function LastEvent({ row, includeDetail = true }: { row: RequestRow; includeDetail?: boolean }): JSX.Element {
  const event = latestRequestEvent(includeDetail ? row : { ...row, detail: null })
  if (!event) return <p className="requests-card-gap">Last event not recorded</p>
  return (
    <p className="requests-last-event">
      <span aria-hidden="true" className={`requests-event-dot requests-event-dot--${event.kind}`} />
      <span>{event.label}</span>
      <Timestamp at={event.at} prefix=" · " />
    </p>
  )
}

function RequestReference({ id }: { id: string }): JSX.Element {
  const [copyState, setCopyState] = useState<'idle' | 'copied' | 'failed'>('idle')

  async function copy(): Promise<void> {
    try {
      await navigator.clipboard.writeText(id)
      setCopyState('copied')
    } catch {
      setCopyState('failed')
    }
  }

  return (
    <div className="requests-reference">
      <code>Request {id}</code>
      <Button label={`Copy request ID ${id}`} size="sm" variant="ghost" onClick={copy}>Copy ID</Button>
      {copyState === 'copied' ? <span role="status">Copied</span> : null}
      {copyState === 'failed' ? <span role="status">Clipboard blocked — select the request ID</span> : null}
    </div>
  )
}

function CardButton({ onSelect, id, children, emphasis = 'default' }: {
  onSelect?: (id: string) => void
  id: string
  children: JSX.Element
  emphasis?: 'default' | 'active'
}): JSX.Element {
  return (
    <button
      type="button"
      className={`requests-card-button${emphasis === 'active' ? ' requests-card-button--active' : ''}`}
      onClick={() => onSelect?.(id)}
    >
      {children}
    </button>
  )
}

function CardTitle({ row }: { row: RequestRow }): JSX.Element {
  return (
    <div className="requests-card-heading">
      <p className="requests-card-title">{row.title}</p>
      <div className="requests-card-badges">
        <ProjectBadge project={row.project} />
        <PriorityBadge priority={row.priority} />
        <AgentInitiatedBadge origin={row.origin} />
      </div>
    </div>
  )
}

function AskedCard({ row, index, onSelect }: { row: RequestRow; index: number; onSelect?: (id: string) => void }): JSX.Element {
  return (
    <div className="requests-card-with-reference">
      <CardButton id={row.id} onSelect={onSelect}>
        <Card variant="default" padding={3}>
          <div className="requests-asked-card">
            <div className="requests-queue-position">
              <strong>{queuePositionLabel(index).split(' ')[0]}</strong>
              <span>in line</span>
            </div>
            <div className="requests-card-body">
              <CardTitle row={row} />
              <Timestamp at={row.asked_at} prefix="asked " />
              <LastEvent row={row} />
              {!latestRequestEvent(row) ? <p className="requests-wait-reason">↻ {waitReason(index)}</p> : null}
            </div>
          </div>
        </Card>
      </CardButton>
      <RequestReference id={row.id} />
    </div>
  )
}

function InFlightCard({ row, onSelect }: { row: RequestRow; onSelect?: (id: string) => void }): JSX.Element {
  const activeStep = stageActiveStep(row) ?? -1
  return (
    <div className="requests-card-with-reference">
      <CardButton id={row.id} onSelect={onSelect} emphasis="active">
        <Card variant="default" padding={3}>
          <div className="requests-card-body">
            <CardTitle row={row} />
            <div className="requests-card-meta-line">
              <Timestamp at={row.asked_at} prefix="asked " />
              <LastEvent row={row} />
            </div>
            <div className="requests-stage-track">
              <Stepper activeStep={activeStep} label={`Progress for ${row.title}`} density="compact">
                {STAGE_LABELS.map((label, step) => <Step key={label} step={step} label={label} />)}
              </Stepper>
              <p className="requests-card-gap">Current step not recorded</p>
            </div>
            <div className="requests-worker-line">
              <span>Lane: {row.worker ?? 'not recorded'}</span>
              <span className="requests-heartbeat-gap">heartbeat not recorded</span>
            </div>
          </div>
        </Card>
      </CardButton>
      <RequestReference id={row.id} />
    </div>
  )
}

function BlockedCard({ row, onSelect, error, onOptimistic, onSuccess, onError, hideForm }: {
  row: RequestRow
  onSelect?: (id: string) => void
  error?: string
  onOptimistic(): void
  onSuccess(row: RequestRow): void
  onError(message: string): void
  hideForm?: boolean
}): JSX.Element {
  return (
    <div className="requests-blocked-card">
      <button type="button" className="requests-blocked-select" onClick={() => onSelect?.(row.id)}>
        <div className="requests-blocked-title">
          <span aria-hidden="true" className="requests-alert-mark">!</span>
          <CardTitle row={row} />
        </div>
        <Timestamp at={row.asked_at} prefix="asked " />
        <LastEvent row={row} includeDetail={false} />
        <p className="requests-owner-question">{row.detail ?? 'Your action is needed, but the question was not recorded.'}</p>
      </button>
      {/* The drawer owns answering for a row while it is open; rendering this form at
          the same time would put two "Answer for <title>" controls in the document. */}
      {hideForm ? null : <RequestAnswerForm request={row} error={error} onOptimistic={onOptimistic} onSuccess={onSuccess} onError={onError} />}
      <span className="requests-answer-cta">Answer needed</span>
      <RequestReference id={row.id} />
    </div>
  )
}

function OrphanedCard({ row, onSelect }: { row: RequestRow; onSelect?: (id: string) => void }): JSX.Element {
  return (
    <Card variant="default" padding={3}>
      <div className="flex flex-col gap-2">
        <CardButton id={row.id} onSelect={onSelect}>
          <div className="flex flex-col gap-1.5">
            <p className="text-sm font-semibold text-fg">{row.title}</p>
            <div className="flex flex-wrap items-center gap-1.5">
              <ProjectBadge project={row.project} />
              <PriorityBadge priority={row.priority} />
              <Badge variant="warning" label="Worker stopped" />
            </div>
            <p className="text-xs text-fg">{row.detail ?? 'The worker stopped; recovery details were not recorded.'}</p>
            <p className="text-xs text-fg-muted">Last worker: {row.worker ?? 'unknown'}</p>
          </div>
        </CardButton>
        <RequestReference id={row.id} />
      </div>
    </Card>
  )
}

function CanceledCard({ row, onSelect }: { row: RequestRow; onSelect?: (id: string) => void }): JSX.Element {
  return (
    <Card variant="default" padding={3}>
      <div className="flex flex-col gap-2">
        <CardButton id={row.id} onSelect={onSelect}>
          <div className="flex flex-col gap-1.5">
            <p className="text-sm font-semibold text-fg">{row.title}</p>
            <div className="flex flex-wrap items-center gap-1.5">
              <ProjectBadge project={row.project} />
              <Badge variant="neutral" label="Canceled" />
            </div>
            {row.detail ? <p className="text-xs text-fg-muted">{row.detail}</p> : null}
          </div>
        </CardButton>
        <RequestReference id={row.id} />
      </div>
    </Card>
  )
}

function ShippedCard({ row, onSelect }: { row: RequestRow; onSelect?: (id: string) => void }): JSX.Element {
  const proofUrl = safeHttpUrl(row.proof_url)
  return (
    <div className="requests-card-with-reference requests-shipped-card">
      <Card variant="default" padding={3}>
        <div className="requests-card-body">
          <button type="button" className="requests-shipped-select" onClick={() => onSelect?.(row.id)}>
            <CardTitle row={row} />
            <div className="requests-card-meta-line">
              <Timestamp at={row.updated_at} prefix="shipped " />
              <LastEvent row={row} />
            </div>
          </button>
          {proofUrl ? <a className="requests-proof-link" href={proofUrl}>Open proof ↗</a> : <p className="requests-card-gap">Proof not recorded</p>}
        </div>
      </Card>
      <RequestReference id={row.id} />
    </div>
  )
}

function ColumnShell({ title, subtitle, count, tone = 'default', children }: {
  title: string
  subtitle: string
  count: number
  tone?: 'default' | 'blocked'
  children: JSX.Element
}): JSX.Element {
  const headingId = `requests-column-${title.replace(/ /g, '-').toLowerCase()}`
  return (
    <section className={`requests-column requests-column--${tone}`} aria-labelledby={headingId}>
      <header className="requests-column-header">
        <h2 id={headingId}>{title} <span>{count}</span></h2>
        <p>{subtitle}</p>
      </header>
      <div className="requests-column-scroll">{children}</div>
    </section>
  )
}

function StatTile({ label, value, detail, alert = false }: { label: string; value: number; detail?: string; alert?: boolean }): JSX.Element {
  return (
    <div className={`requests-stat${alert ? ' requests-stat--alert' : ''}`}>
      <span>{label}</span>
      <strong className="tabular-nums">{value}{detail ? <small>{detail}</small> : null}</strong>
    </div>
  )
}

function ProjectFilter({ projects, selected, onSelect }: { projects: string[]; selected: string | null; onSelect(project: string | null): void }): JSX.Element {
  return (
    <div className="requests-project-filter" aria-label="Filter by project">
      <button type="button" aria-pressed={selected === null} onClick={() => onSelect(null)}><Badge variant={selected === null ? 'info' : 'neutral'} label="All" /></button>
      {projects.map((project) => (
        <button key={project} type="button" aria-pressed={selected === project} onClick={() => onSelect(project)}>
          <Badge variant={selected === project ? 'info' : 'neutral'} label={project} />
        </button>
      ))}
    </div>
  )
}

function DeliveryTimeline({ events, onSelect }: { events: DeliveryEventView[]; onSelect?: (id: string) => void }): JSX.Element {
  return (
    <details className="requests-deliveries">
      <summary>
        <span>⌄</span>
        <strong>Deliveries</strong>
        <small>{events.length ? `${events.length} recorded events` : 'Delivery history not recorded'}</small>
      </summary>
      {events.length ? (
        <ol className="requests-delivery-track">
          {events.slice(0, 8).map((event) => (
            <li key={event.id}>
              <button type="button" onClick={() => onSelect?.(event.requestId)}>
                <span className={`requests-delivery-dot requests-delivery-dot--${event.kind}`} />
                <Timestamp at={event.at} />
                <strong>{event.project}</strong>
                <span>{event.label}</span>
              </button>
            </li>
          ))}
        </ol>
      ) : <p className="requests-delivery-gap">The collector has not attached landing or deployment receipts to these requests.</p>}
    </details>
  )
}

export function RequestsBoard(props: { requests: RequestRow[]; onSelect?: (id: string) => void; selectedId?: string | null }): JSX.Element {
  const [search, setSearch] = useState('')
  const [project, setProject] = useState<string | null>(null)
  const [planSlug, setPlanSlug] = useState<string | null>(null)
  const [session, setSession] = useState<string | null>(null)
  const [optimisticallyAnswered, setOptimisticallyAnswered] = useState<Set<string>>(() => new Set())
  const [answerErrors, setAnswerErrors] = useState<Record<string, string>>({})
  const now = useMemo(() => new Date(), [])
  const projects = useMemo(() => distinctProjects(props.requests), [props.requests])
  const planSlugs = useMemo(() => distinctPlanSlugs(props.requests), [props.requests])
  const sessions = useMemo(() => distinctSessions(props.requests), [props.requests])
  const filtered = useMemo(
    () => props.requests.filter((row) => matchesSearch(row, search) && matchesProject(row, project) && matchesPlanSlug(row, planSlug) && matchesSession(row, session)),
    [props.requests, search, project, planSlug, session],
  )
  const summary = useMemo(() => summaryCounts(filtered, now), [filtered, now])
  const asked = useMemo(() => sortByAskedOrder(filtered.filter((row) => row.state === 'asked')), [filtered])
  const inFlight = useMemo(() => filtered.filter((row) => row.state === 'in_flight'), [filtered])
  const blocked = useMemo(() => filtered.filter((row) => row.state === 'blocked_needs_owner' && !optimisticallyAnswered.has(row.id)), [filtered, optimisticallyAnswered])
  const orphaned = useMemo(() => filtered.filter((row) => row.state === 'orphaned'), [filtered])
  const shipped = useMemo(() => filtered.filter((row) => row.state === 'shipped'), [filtered])
  const canceled = useMemo(() => filtered.filter((row) => row.state === 'canceled'), [filtered])
  const shippedGroups = useMemo(() => groupShipped(shipped, now), [shipped, now])
  const deliveries = useMemo(() => deliveryEvents(filtered), [filtered])

  return (
    <section aria-label="Requests board" className="requests-page">
      <header className="requests-pulse">
        <div className="requests-pulse-title">
          <span className="requests-mark" aria-hidden="true">◆</span>
          <h1>Requests</h1>
        </div>
        <div className="requests-search">
          <TextInput label="Search requests" isLabelHidden placeholder="find anything I asked for…" value={search} onChange={setSearch} />
        </div>
        <div className="requests-stats">
          <StatTile label="In flight" value={summary.inFlight} detail=" / limit not recorded" />
          <StatTile label="Queued" value={summary.queued} />
          <StatTile label="Blocked — needs you" value={summary.blocked} alert={summary.blocked > 0} />
          <StatTile label="Needs recovery" value={summary.needsRecovery} alert={summary.needsRecovery > 0} />
          <StatTile label="Shipped today" value={summary.shippedToday} />
        </div>
        <ProjectFilter projects={projects} selected={project} onSelect={setProject} />
        <p className="requests-freshness"><span /> feed freshness not recorded</p>
      </header>

      <div className="requests-board-columns">
        <ColumnShell title="Asked" subtitle="Intake queue" count={asked.length}>
          {asked.length ? <>{asked.map((row, index) => <AskedCard key={row.id} row={row} index={index} onSelect={props.onSelect} />)}</> : <EmptyState title="Nothing waiting" description="No requests are queued right now." />}
        </ColumnShell>
        <ColumnShell title="In flight" subtitle="Actively moving" count={inFlight.length}>
          {inFlight.length ? <>{inFlight.map((row) => <InFlightCard key={row.id} row={row} onSelect={props.onSelect} />)}</> : <EmptyState title="Nothing in flight" description="Ask for something." />}
        </ColumnShell>
        <ColumnShell title="Blocked — needs you" subtitle="Only this column ever needs you." count={blocked.length} tone="blocked">
          {blocked.length ? <>{blocked.map((row) => <BlockedCard
            key={row.id}
            row={row}
            onSelect={props.onSelect}
            error={answerErrors[row.id]}
            hideForm={row.id === props.selectedId}
            onOptimistic={() => setOptimisticallyAnswered((current) => new Set(current).add(row.id))}
            onSuccess={() => setAnswerErrors((current) => { const { [row.id]: _, ...rest } = current; return rest })}
            onError={(message) => {
              setOptimisticallyAnswered((current) => { const next = new Set(current); next.delete(row.id); return next })
              setAnswerErrors((current) => ({ ...current, [row.id]: message }))
            }}
          />)}</> : <div className="requests-blocked-empty"><strong>Nothing needs you.</strong><p>Everything else can keep moving.</p></div>}
        </ColumnShell>
        <ColumnShell title="Needs recovery" subtitle="Stopped work that needs attention" count={orphaned.length}>
          {orphaned.length ? <>{orphaned.map((row) => <OrphanedCard key={row.id} row={row} onSelect={props.onSelect} />)}</> : <EmptyState title="Nothing to recover" description="Stopped workers with uncommitted work will show here." />}
        </ColumnShell>
        <ColumnShell title="Closed" subtitle="Delivered with proof or canceled" count={shipped.length + canceled.length}>
          {shipped.length || canceled.length ? <>
            {SHIPPED_BUCKET_ORDER.filter((bucket) => shippedGroups[bucket].length).map((bucket) => <section className="requests-shipped-group" key={bucket}><h3>{bucket}</h3>{shippedGroups[bucket].map((row) => <ShippedCard key={row.id} row={row} onSelect={props.onSelect} />)}</section>)}
            {canceled.length ? <section className="requests-shipped-group"><h3>Canceled</h3>{canceled.map((row) => <CanceledCard key={row.id} row={row} onSelect={props.onSelect} />)}</section> : null}
          </> : <EmptyState title="Nothing closed yet" description="Shipped and canceled requests will show up here." />}
        </ColumnShell>
      </div>

      <DeliveryTimeline events={deliveries} onSelect={props.onSelect} />
    </section>
  )
}
