import { router, usePage } from '@inertiajs/react'
import type { FormEvent, ReactNode } from 'react'
import { useEffect, useRef } from 'react'

import { addressParam, addressWithout } from '@/components/address'
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import { FieldGroup } from '@/components/ui/field'
import { Spinner } from '@/components/ui/spinner'
import { t } from '@/lib/i18n'

/**
 * Si el formulario está abierto lo dice **la dirección**, y nada más.
 *
 * Ésta es la regla entera de la que cuelga todo lo demás. Un formulario que
 * guarda su apertura en React se cierra al enviar, y entonces el error que
 * devuelve el servidor llega a una pantalla donde el campo que lo causó ya no
 * está: hay que volver a abrirlo, volver a escribir y adivinar qué pasó. Con la
 * apertura en la dirección, una respuesta con error —que vuelve a la misma
 * dirección, con el parámetro puesto— deja el formulario abierto, con lo
 * escrito y con el error al lado del campo.
 */
export function isOpenFromAddress(address: string, param: string): boolean {
  return addressParam(address, param) !== null
}

interface Props {
  /** La clave en la dirección: `?new`, `?rename=<id>`. */
  param: string
  title: string
  description?: string
  /** El verbo: «Crear la clave», «Guardar el nombre». Nunca «Aceptar». */
  submitLabel: string
  /** El mismo verbo en gerundio: «Creando…». */
  pendingLabel: string
  /**
   * Si la petición está en vuelo.
   *
   * Pasarla es lo que permite llevar el foco al campo que falló cuando el
   * servidor contesta: sin ella no hay forma de saber que hubo una respuesta.
   */
  pending?: boolean
  onSubmit: (event: FormEvent<HTMLFormElement>) => void
  /** Los campos, con `Field` / `FieldLabel` / `FieldError`. Hasta cinco. */
  children: ReactNode
}

/**
 * `C-1B-3` · El formulario corto que bloquea, abierto desde la dirección.
 *
 * Cinco campos como mucho: encolar una inspección manual, crear o renombrar una
 * clave de API. Más que eso es una `Section` de la página o una página propia,
 * nunca un panel lateral.
 *
 * **No se cierra al enviar.** Es la única regla que la pieza no negocia, y el
 * motivo está arriba, en `isOpenFromAddress`. Lo cierra el servidor: cuando la
 * acción sale bien, redirige a una dirección sin el parámetro y el diálogo se
 * va solo; cuando falla, vuelve a la misma y el diálogo sigue ahí.
 *
 * Cuando la respuesta trae un error de validación, el foco baja al primer campo
 * marcado (RT-08 y RT-17): un error que aparece abajo de la pantalla mientras
 * el foco quedó en el botón es un error que nadie lee.
 */
export function FormDialog({
  param,
  title,
  description,
  submitLabel,
  pendingLabel,
  pending = false,
  onSubmit,
  children,
}: Props) {
  const { url } = usePage()
  const content = useRef<HTMLDivElement>(null)
  const wasPending = useRef(false)

  const close = () => {
    router.get(addressWithout(url, param), {}, { preserveScroll: true, preserveState: true })
  }

  useEffect(() => {
    // El flanco de bajada es «el servidor contestó». Si lo que volvió fue un
    // error, está junto al campo: el foco va ahí y no al botón que lo disparó.
    if (wasPending.current && !pending) {
      content.current?.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus()
    }
    wasPending.current = pending
  }, [pending])

  return (
    <Dialog
      open={isOpenFromAddress(url, param)}
      onOpenChange={(isOpen) => {
        if (!isOpen) close()
      }}
    >
      <DialogContent
        ref={content}
        // El cierre propio del diálogo viene rotulado en inglés y además
        // duplicaría a «Cancelar», que dice lo mismo con todas las letras.
        showCloseButton={false}
        {...(description ? {} : { 'aria-describedby': undefined })}
      >
        <form onSubmit={onSubmit} className="grid gap-4">
          <DialogHeader>
            <DialogTitle className="text-balance">{title}</DialogTitle>
            {description ? (
              <DialogDescription className="text-pretty">
                {description}
              </DialogDescription>
            ) : null}
          </DialogHeader>

          <FieldGroup>{children}</FieldGroup>

          <DialogFooter>
            <Button type="button" variant="outline" onClick={close}>
              {t('common.cancel')}
            </Button>
            {/*
              El botón queda habilitado hasta que arranca la petición y lleva
              `aria-busy`: deshabilitarlo bajo el foco lo manda al `body` y quien
              navega con teclado pierde el diálogo.
            */}
            <Button type="submit" aria-busy={pending || undefined}>
              {pending ? (
                <>
                  <Spinner />
                  {pendingLabel}
                </>
              ) : (
                submitLabel
              )}
            </Button>
          </DialogFooter>
        </form>
      </DialogContent>
    </Dialog>
  )
}
