import { useId } from "react";
import type { ReactNode } from "react";

import type { SectionTitle } from "@/components/Section";
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { cn } from "@/lib/utils";

/**
 * El título de una tarjeta: el mismo de `Section` **menos la posibilidad de
 * esconderlo**.
 *
 * Es la regla dura escrita en el sistema de tipos en vez de en un comentario:
 * toda tarjeta lleva título y el título se ve. `Section` puede esconderlo
 * porque agrupa cosas que a veces no necesitan encabezado a la vista —una barra
 * de filtros—; una tarjeta no, porque ya es una caja con borde propio, y una
 * caja con borde y sin nombre obliga a deducir qué contiene mirando lo que hay
 * adentro.
 *
 * Que salga de `SectionTitle` y no sea otro tipo escrito al lado es lo que hace
 * que las dos piezas no se separen: un campo nuevo allá aparece acá solo.
 */
export type SectionCardTitle = Omit<SectionTitle, "isHidden">;

interface Props {
  /**
   * Todo lo del título en un objeto, igual que en `Section`.
   *
   * «Qué dice, 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.
   */
  title: SectionCardTitle;
  description?: string;
  /**
   * La ranura de la derecha del encabezado.
   *
   * **Esta tarjeta no sabe qué va acá y no pregunta.** Sólo reserva el lugar y
   * lo alinea; que sean dos botones, un menú o una insignia lo decide quien la
   * usa. Es la diferencia entre una pieza que se puede reusar y una que hay que
   * ampliar cada vez que aparece un control nuevo.
   */
  actions?: ReactNode;
  /** El pie, a lo ancho y abajo de todo. Ranura ciega, igual que `actions`. */
  footer?: ReactNode;
  /**
   * El sufijo del id, para poder encontrar esta tarjeta desde afuera.
   *
   * A mano sólo cuando hace falta enlazarla o apuntarle desde otra pieza; si no,
   * sale de `useId`. Todo lo que la pieza emite lleva el prefijo `section-card-`
   * para poder reconocerlo en el inspector.
   */
  id?: string;
  children?: ReactNode;
  className?: string;
  /** Sustituye el espaciado del contenido. Por omisión, una columna con 16 px. */
  contentClassName?: string;
  /**
   * Sustituye el espaciado del pie. Hermana de `contentClassName`.
   *
   * Existe para lo que ocupa el ancho entero del pie —una tabla— y por lo tanto
   * no quiere el relleno lateral. Con la tarjeta en `overflow-hidden`, el
   * redondeo lo dibuja la tarjeta y el contenido llega hasta el borde.
   */
  footerClassName?: string;
}

/**
 * La tarjeta con título: la forma canónica de una sección con caja.
 *
 * Es la contracara de `Section`, con la misma firma a propósito: **`Section`
 * agrupa sin dibujar caja y `SectionCard` dibuja la caja**. Ésa es la regla que
 * ya estaba escrita en el docstring de `Section` —«el borde es de la `Card`»—
 * convertida en dos piezas en vez de en una prohibición. Y sigue valiendo la
 * consecuencia: **no se anidan**. Una tarjeta adentro de otra tarjeta es la
 * caja anidada que las dos piezas existen para evitar.
 *
 * «La misma firma» es literal y no una intención: el título sale del mismo tipo
 * que el de `Section`, menos `isHidden`. Dos firmas parecidas escritas por
 * separado divergen en el primer campo que una de las dos gane, que es
 * exactamente el defecto que estas dos piezas existen para evitar.
 *
 * Nace porque la convención sola no se sostuvo. Con la `Card` cruda había
 * veintidós usos en nueve archivos, y de veintidós títulos catorce eran un
 * `<div>` en vez de un encabezado —se olvidaban del `asChild`—, la descripción
 * repetía las mismas dos clases en cuatro archivos y el contenido se espaciaba
 * de seis maneras distintas. Nada de eso era una decisión: era lo que pasa
 * cuando la forma correcta hay que escribirla de nuevo en cada pantalla.
 *
 * **Una ranura sin contenido no se rinde.** No es prolijidad: la `Card` de este
 * proyecto reacciona a lo que tiene adentro —`has-data-[slot=card-footer]`
 * le saca el relleno de abajo cuando hay pie—, así que un pie vacío rendido por
 * las dudas le cambia el relleno a la tarjeta entera y le deja una franja con
 * borde y fondo sin nada escrito. Lo mismo, en chico, con el contenedor de
 * acciones: un `<div>` vacío no dibuja nada pero cobra su `gap` igual.
 */
export function SectionCard({
  title,
  description,
  actions,
  footer,
  id,
  children,
  className,
  contentClassName,
  footerClassName,
}: Props) {
  // `useId` se llama siempre y se descarta si sobra, nunca adentro de la rama de
  // un ternario: un hook que unas veces se llama y otras no cambia la cantidad
  // de hooks entre dos dibujados, y ahí React pierde de qué estado es cada cual.
  // Pasaría en cuanto una tarjeta reciba `id` en un render y no en el siguiente.
  const generatedId = useId();
  const titleId = id ? `section-card-${id}` : `section-card-${generatedId}`;
  const Heading = title.level ?? "h2";

  return (
    // `role="group"` y no una `<section>` alrededor: el nombre accesible tiene
    // que estar en la tarjeta misma, porque un envoltorio extra la dejaría de
    // ser hija directa de la grilla que la acomoda. Y `group` y no `region`
    // porque una pantalla con cinco tarjetas produciría cinco puntos de
    // referencia, que es cómo la lista de puntos de referencia deja de servir.
    <Card role="group" aria-labelledby={titleId} className={className}>
      <CardHeader className="flex flex-row items-start justify-between gap-3">
        {/* Heading Container */}
        <div className="flex min-w-0 flex-col">
          <CardTitle asChild>
            {/*
              El dibujo no depende del nivel: un `h3` adentro de otra sección se
              ve igual que el `h2` de la tarjeta de al lado, porque lo que cambia
              es dónde cae en el índice de encabezados y no su peso visual.
            */}
            <Heading
              id={titleId}
              className={cn("text-balance", title.className)}
            >
              {title.text}
            </Heading>
          </CardTitle>
          {Boolean(description) && (
            <CardDescription className="text-pretty">
              {description}
            </CardDescription>
          )}
        </div>
        {/* Actions Container */}
        {Boolean(actions) && <div className="shrink-0">{actions}</div>}
      </CardHeader>

      {children && (
        <CardContent className={cn("flex flex-col gap-4", contentClassName)}>
          {children}
        </CardContent>
      )}

      {Boolean(footer) && (
        <CardFooter className={footerClassName}>{footer}</CardFooter>
      )}
    </Card>
  );
}
