import { useId } from 'react'
import type { ReactNode } from 'react'

import { cn } from '@/lib/utils'

/** Todo lo del título junto, que es lo que se decide de una sola vez. */
export interface SectionTitle {
  /** Lo que dice. Siempre hay título, aunque a veces no se dibuje. */
  text: string
  /**
   * Saca el encabezado del árbol y deja el título como nombre de la sección.
   *
   * No lo esconde con `sr-only`: **no lo rinde**. Un encabezado invisible sigue
   * siendo un hijo del flex, y aunque no dibuje nada cobra su parte del `gap`;
   * el resultado es un hueco arriba del primer contenido visible que no separa
   * nada de nada. Con esto, la sección conserva su nombre —viaja en un
   * `aria-label`— y pierde el hueco.
   *
   * Lo que se cede a cambio es que la sección deja de aparecer en el índice de
   * encabezados. Sigue siendo una región con nombre, así que se puede saltar de
   * sección en sección; lo que ya no se puede es llegar a ella por la lista de
   * títulos. Es el precio de una región que a la vista no tiene título —una
   * barra de filtros—, y por eso es la excepción y no el modo normal de usar la
   * pieza.
   */
  isHidden?: boolean
  /**
   * `h2` cuando la sección es de la página; `h3` cuando vive dentro de otra que
   * ya aportó su `h2`.
   *
   * Se declara y no se deduce porque el nivel depende de dónde se monta la
   * sección, y eso sólo lo sabe quien la monta. El **dibujo no cambia** con el
   * nivel: lo que cambia es el lugar que ocupa en el índice de encabezados.
   */
  level?: 'h2' | 'h3'
  /**
   * ⚠️ **Salida de emergencia. Si estás por usarla, casi seguro no la necesitás.**
   *
   * Esta pieza dibuja su propio título y ésa es su razón de existir: antes de
   * que existiera había `<h2>` dibujados de siete maneras distintas, una por
   * vista, y ninguna de las siete era una decisión —eran siete descuidos que se
   * parecían—. Una clase escrita acá es el primer paso de vuelta a eso.
   *
   * **Lo que hay que hacer en su lugar**, según qué se necesite:
   *
   * - ¿El título tiene que verse distinto **porque la sección es de otra
   *   clase** —destructiva, de sólo lectura, en curso—? Eso es `tone`. Si el
   *   tono que hace falta no existe, se agrega a `tone` y queda disponible para
   *   todas las secciones. Ésa es la escalera correcta.
   * - ¿Tiene que verse distinto **en todas las secciones**? Entonces se cambia
   *   acá adentro, en la pieza, y cambia en las dieciséis a la vez.
   * - ¿Tiene que **no verse**? Eso es `isHidden`.
   *
   * **Nunca para color.** El color de un título comunica de qué clase es la
   * sección, y eso lo decide `tone` para que la misma clase se vea igual en toda
   * la aplicación. Un color escrito acá dice lo mismo con otro matiz en una sola
   * pantalla, que es cómo una interfaz deja de tener significado.
   *
   * Queda por si aparece una condición que hoy no prevemos y que ni `tone` ni un
   * cambio en la pieza resuelven —un recorte de ancho puntual, una alineación
   * que sólo tiene sentido en un lugar—. Para eso, y con eso escrito al lado.
   */
  className?: string
}

interface Props {
  /**
   * Todo lo del título en un objeto y no en cuatro props sueltas.
   *
   * «Hay título, se dibuja, a qué nivel y con qué pinta» es **una** decisión, y
   * repartida en props hermanas se puede escribir en combinaciones que no
   * significan nada: un `titleLevel` sobre un título que no se rinde, por
   * ejemplo.
   */
  title: SectionTitle
  description?: string
  /** Una línea: hasta dos controles o un `ButtonGroup`. Nunca un badge ni una fecha. */
  actions?: ReactNode
  /**
   * El sufijo del id, para poder encontrar esta sección desde afuera.
   *
   * Se pasa a mano sólo cuando hace falta enlazarla o apuntarle desde otra
   * pieza; si no, sale de `useId`. Con el título escondido no hay encabezado al
   * que apuntar, así que el id no llega al marcado.
   */
  id?: string
  tone?: 'default' | 'destructive'
  children: ReactNode
  className?: string
}

/**
 * Prefijo de todo id que emite esta pieza.
 *
 * Existe para poder reconocerla en el inspector: un `id` de `useId` es
 * `«r7»` y no dice de dónde salió, así que encontrar la sección que produjo un
 * nodo obligaba a recorrer el árbol hacia arriba. Con el prefijo, buscar
 * `section-` en el DOM devuelve todas y cada una dice qué es.
 */
const ID_PREFIX = 'section'

/**
 * El agrupador de contenido con título: la pieza que no existía y que por eso
 * cada vista improvisaba.
 *
 * De ahí salían las ~20 apariciones de `<section className="rounded-lg border
 * p-4">` y los `<h2>` dibujados de siete maneras distintas. **Una `Section` no
 * tiene borde ni fondo**: el borde es de la `Card` y del contenedor de
 * `DataTable`. Ésa es la regla que corta de raíz las cajas anidadas —tarjeta
 * dentro de tarjeta dentro de tarjeta— sin tener que discutirlas una por una.
 *
 * Tampoco define el ritmo entre secciones: los 24 px los da el `<main>` del
 * armazón. Acá adentro el único espaciado es el de un encabezado con su
 * contenido.
 *
 * **La sección siempre tiene nombre, y hay dos formas de dárselo.** Con el
 * título a la vista es un encabezado de verdad y la región lo señala con
 * `aria-labelledby`; con `title.isHidden` no hay encabezado y el nombre viaja
 * en un `aria-label`. Lo que nunca pasa es que se rinda algo invisible: eso
 * ocupa lugar en el flex sin dibujar nada.
 *
 * Todo lo que emite lleva el prefijo `section-`, para poder reconocerlo en el
 * inspector sin recorrer el árbol hacia arriba.
 */
export function Section({
  title,
  description,
  actions,
  id,
  tone = 'default',
  children,
  className,
}: Props) {
  const generatedId = useId()
  const titleId = `${ID_PREFIX}-${id ?? generatedId}`
  const Heading = title.level ?? 'h2'

  return (
    <section
      // El id va también en la sección, no sólo en su encabezado: es lo que
      // permite encontrarla en el inspector cuando el título está escondido y no
      // hay ningún `<h2>` que la delate.
      id={`${titleId}-region`}
      // Una de las dos, nunca las dos: `aria-labelledby` gana sobre `aria-label`
      // cuando conviven, así que declarar el que no aplica sería dejar escrito un
      // nombre que nadie lee.
      aria-labelledby={title.isHidden ? undefined : titleId}
      aria-label={title.isHidden ? title.text : undefined}
      className={cn('flex flex-col gap-2', className)}
    >
      {/*
        Con el título escondido no se rinde el encabezado **ni la fila que lo
        envuelve**, ni siquiera cuando hay acciones: las acciones van solas, que
        es lo que eran —una fila de controles arriba del contenido—.
      */}
      {title.isHidden ? (
        actions
      ) : (
        <div className="flex flex-wrap items-start justify-between gap-2">
          <Heading
            id={titleId}
            className={cn(
              // El dibujo no depende del nivel: un `h3` adentro de una sección
              // se ve igual que el `h2` de la de al lado, porque lo que cambia
              // es dónde cae en el índice de encabezados y no su peso visual.
              'font-heading text-base leading-snug font-medium text-balance',
              // Lo destructivo se distingue por el color del título y no por una
              // caja roja alrededor: una `Section` sigue sin tener borde ni fondo,
              // y el peso visual real lo pone el botón de cada acción.
              tone === 'destructive' && 'text-destructive',
              title.className
            )}
          >
            {title.text}
          </Heading>

          {actions}
        </div>
      )}

      {description ? (
        <p className="text-muted-foreground text-sm text-pretty">{description}</p>
      ) : null}

      {children}
    </section>
  )
}
