import { useEffect, useState, type ReactNode } from "react"
import { AlertTriangle, RefreshCw, ShieldOff, WifiOff } from "lucide-react"
import { LoadingOverlay } from "@crema/feedback-ui"

import { Button } from "~/components/ui/button"
import { describeError, type LoadError } from "~/lib/errors"

/**
 * The load-state discriminator every list screen renders through.
 *
 * The rule it enforces: **a failed load is never an empty one.** Screens used
 * to render their "No events match those filters — loosen the filter set…"
 * empty state underneath a red "Too Many Requests" banner, so an operator
 * couldn't tell a quiet audit log from a broken one. Exactly one of
 * error / loading / empty / content renders here, ever.
 */
export function DataState({
  loading,
  error,
  isEmpty,
  empty,
  onRetry,
  loadingLabel = "Loading…",
  children,
}: {
  loading: boolean
  /** The raw thrown value; normalised for display here. */
  error: unknown
  isEmpty: boolean
  /** What to show when the load succeeded and there is genuinely nothing. */
  empty: ReactNode
  onRetry: () => void
  loadingLabel?: string
  children: ReactNode
}) {
  if (error) return <ErrorState error={error} onRetry={onRetry} />

  // First load: nothing to show yet. Subsequent refreshes keep the table on
  // screen and let the table's own `loading` prop dim it, so the page doesn't
  // flash empty every time an operator hits Refresh.
  if (loading && isEmpty)
    return (
      <div className="relative min-h-40">
        <LoadingOverlay active label={loadingLabel} />
      </div>
    )

  if (isEmpty) return <>{empty}</>

  return <>{children}</>
}

export function ErrorState({
  error,
  onRetry,
}: {
  error: unknown
  onRetry: () => void
}) {
  const d: LoadError = describeError(error)
  const Icon =
    d.status === 403 || d.status === 401
      ? ShieldOff
      : d.title === "Can't reach arcadia"
        ? WifiOff
        : AlertTriangle

  return (
    <div className="flex flex-col items-center gap-3 px-6 py-12 text-center">
      <div className="flex size-10 items-center justify-center rounded-lg bg-destructive/10 text-destructive">
        <Icon className="size-5" />
      </div>

      <div className="max-w-md space-y-1">
        <p className="font-medium">{d.title}</p>
        {d.detail ? (
          <p className="text-sm text-muted-foreground">{d.detail}</p>
        ) : null}
        {d.fields?.length ? (
          <ul className="mt-1 space-y-0.5 text-sm text-muted-foreground">
            {d.fields.map((f) => (
              <li key={f}>{f}</li>
            ))}
          </ul>
        ) : null}
      </div>

      {d.retryable ? (
        d.retryAfterSec ? (
          <AutoRetry seconds={d.retryAfterSec} onRetry={onRetry} />
        ) : (
          <Button
            data-action="data-state-retry"
            variant="outline"
            size="sm"
            onClick={onRetry}
          >
            <RefreshCw className="size-4" />
            Retry
          </Button>
        )
      ) : null}
    </div>
  )
}

/**
 * An error raised while a dialog is open, rendered *inside* that dialog.
 *
 * Page-level banners are invisible here: the modal scrim dims them and the
 * dialog covers them. A failed submit has to speak where the operator is
 * looking — right above the buttons they just pressed.
 */
export function DialogError({
  error,
  context = "save",
}: {
  error: unknown
  context?: string
}) {
  const d = describeError(error, context)

  return (
    <div
      role="alert"
      className="flex items-start gap-2.5 rounded-md border border-destructive/30 bg-destructive/5 px-3 py-2.5"
    >
      <AlertTriangle className="mt-0.5 size-4 shrink-0 text-destructive" />
      <div className="min-w-0 space-y-0.5 text-sm">
        <p className="font-medium text-destructive">{d.title}</p>
        {d.detail ? <p className="text-muted-foreground">{d.detail}</p> : null}
        {d.fields?.length ? (
          <ul className="space-y-0.5 text-muted-foreground">
            {d.fields.map((f) => (
              <li key={f}>{f}</li>
            ))}
          </ul>
        ) : null}
      </div>
    </div>
  )
}

/** 429s resolve on their own — count down, retry, and say so. Nagging the
 *  operator to click Retry into a rate limiter would just extend it. */
function AutoRetry({
  seconds,
  onRetry,
}: {
  seconds: number
  onRetry: () => void
}) {
  const [left, setLeft] = useState(seconds)

  useEffect(() => {
    if (left <= 0) {
      onRetry()
      return
    }
    const t = setTimeout(() => setLeft((n) => n - 1), 1000)
    return () => clearTimeout(t)
    // `onRetry` is intentionally excluded: routes hand us a fresh closure each
    // render, and depending on it would reset the countdown forever.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [left])

  return (
    <div className="flex items-center gap-2">
      <Button
        data-action="data-state-retry"
        variant="outline"
        size="sm"
        onClick={onRetry}
      >
        <RefreshCw className="size-4" />
        Retry now
      </Button>
      <span className="text-xs text-muted-foreground" aria-live="polite">
        retrying in {left}s
      </span>
    </div>
  )
}
