/**
 * A confirmation that has to be read.
 *
 * An alert dialog is not a dialog with different copy. It refuses to be
 * dismissed by clicking past it, it opens with focus on the safe choice, and it
 * names the thing it is about to destroy — because the surface it replaces was
 * a plain `Dialog` whose default focus landed on the delete button and whose
 * scrim dismissed a decision the person had not made.
 *
 * Built on `@radix-ui/react-dialog` with `role="alertdialog"` rather than
 * `@radix-ui/react-alert-dialog`, which is not a dependency of this app. The
 * three behaviours that package adds are the three declared below, so the
 * component is the contract, not the package.
 */
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"

import { cn } from "@/lib/utils"
import { Button, type ButtonProps } from "@/components/ui"
import {
  DialogDescription,
  DialogHeader,
  DialogOverlay,
  DialogTitle,
  useOverlayFocusRestore,
} from "@/components/ui/dialog"

const AlertDialog = DialogPrimitive.Root

const AlertDialogTrigger = DialogPrimitive.Trigger

const AlertDialogPortal = DialogPrimitive.Portal

const AlertDialogOverlay = DialogOverlay

/*
 * The cancel control registers itself here so the content can put initial
 * focus on it. It has to be a registration rather than "focus the first
 * focusable", because the destructive action deliberately comes first in the
 * DOM — see AlertDialogFooter — and a destructive button holding focus the
 * moment the dialog opens is one Enter away from the thing the dialog exists
 * to prevent.
 */
const AlertDialogCancelContext =
  React.createContext<React.MutableRefObject<HTMLButtonElement | null> | null>(null)

const AlertDialogContent = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(
  (
    {
      className,
      children,
      onCloseAutoFocus,
      onOpenAutoFocus,
      onInteractOutside,
      ...props
    },
    ref
  ) => {
    const focus = useOverlayFocusRestore(ref, onCloseAutoFocus)
    const cancelRef = React.useRef<HTMLButtonElement | null>(null)

    return (
      <AlertDialogPortal>
        <AlertDialogOverlay />
        <DialogPrimitive.Content
          ref={focus.ref}
          role="alertdialog"
          onCloseAutoFocus={focus.onCloseAutoFocus}
          onOpenAutoFocus={(event) => {
            onOpenAutoFocus?.(event)
            if (event.defaultPrevented) return
            const cancel = cancelRef.current
            if (!cancel) return
            event.preventDefault()
            cancel.focus()
          }}
          onInteractOutside={(event) => {
            onInteractOutside?.(event)
            // A decision is not made by clicking somewhere else.
            event.preventDefault()
          }}
          className={cn(
            "fixed left-[50%] top-[50%] z-50 grid w-[calc(100%-2rem)] max-w-md translate-x-[-50%] translate-y-[-50%] gap-4 rounded-2xl border border-border bg-surface p-5 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-3 data-[state=closed]:duration-3 data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=open]:ease-enter data-[state=closed]:ease-exit",
            className
          )}
          {...props}
        >
          <AlertDialogCancelContext.Provider value={cancelRef}>
            {children}
          </AlertDialogCancelContext.Provider>
        </DialogPrimitive.Content>
      </AlertDialogPortal>
    )
  }
)
AlertDialogContent.displayName = "AlertDialogContent"

const AlertDialogHeader = DialogHeader

/**
 * Destructive first in the DOM, last on the screen.
 *
 * A screen reader reads the footer in source order, and the consequence has to
 * arrive before the escape from it. Sighted order is the platform convention —
 * safe choice on the left, the commit on the right — and is restored with
 * `order`, which changes the painting and not the reading.
 */
const AlertDialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn("flex flex-col gap-2 sm:flex-row sm:justify-end", className)}
    {...props}
  />
)
AlertDialogFooter.displayName = "AlertDialogFooter"

const AlertDialogTitle = DialogTitle

const AlertDialogDescription = DialogDescription

/**
 * The commit. Destructive by default: this component exists for the deletes,
 * and a confirmation whose commit button looks like every other button is a
 * confirmation nobody reads. Pass `variant` for the non-destructive cases.
 */
const AlertDialogAction = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant = "danger", type = "button", ...props }, ref) => (
    <DialogPrimitive.Close asChild>
      <Button
        ref={ref}
        type={type}
        variant={variant}
        className={cn("order-1 sm:order-2", className)}
        {...props}
      />
    </DialogPrimitive.Close>
  )
)
AlertDialogAction.displayName = "AlertDialogAction"

const AlertDialogCancel = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant = "outline", type = "button", ...props }, ref) => {
    const registry = React.useContext(AlertDialogCancelContext)
    const setRef = React.useCallback(
      (node: HTMLButtonElement | null) => {
        if (registry) registry.current = node
        if (typeof ref === "function") ref(node)
        else if (ref) ref.current = node
      },
      [ref, registry]
    )
    return (
      <DialogPrimitive.Close asChild>
        <Button
          ref={setRef}
          type={type}
          variant={variant}
          className={cn("order-2 sm:order-1", className)}
          {...props}
        />
      </DialogPrimitive.Close>
    )
  }
)
AlertDialogCancel.displayName = "AlertDialogCancel"

export {
  AlertDialog,
  AlertDialogPortal,
  AlertDialogOverlay,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogFooter,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogAction,
  AlertDialogCancel,
}
