/**
 * Lo que las dos tablas del producto comparten.
 *
 * Hay dos y son dos a propósito. `DataTable` pagina, ordena y filtra **en el
 * servidor**, porque lista decenas de miles de filas y un orden calculado sobre
 * lo que alcanzó a llegar es un orden que miente; su estado vive en la dirección
 * y por eso cada control suyo es un enlace de verdad. `ClientDataTable` hace
 * todo **en el navegador**, porque lista lo que entra en una respuesta; su
 * estado vive en React y sus controles son botones.
 *
 * Lo que **no** puede cambiar entre una y otra es lo que la persona percibe: la
 * flecha del orden, el `aria-sort`, el encabezado que se queda fijo, la
 * alineación de las cifras y la frase que dice hacia dónde ordena el clic
 * siguiente. Todo eso vive acá, y por eso las dos no pueden divergir sin que
 * alguien lo borre a mano.
 *
 * Lo que no está acá tampoco es un olvido. `TablePage` y `tableUrl` se quedan en
 * `DataTable` porque describen un contrato con el servidor que la tabla de
 * navegador no tiene, y traerlos sugeriría que sí lo tiene. Y los constructores
 * de columnas son uno por tabla porque el `ColumnHelper` de TanStack está
 * declarado invariante en su juego de capacidades: un helper atado a las
 * capacidades de una **no produce** columnas válidas para la otra.
 */

import { ArrowDown, ArrowUp, ChevronsUpDown } from 'lucide-react'

import { Skeleton } from '@/components/ui/skeleton'
import { TableCell, TableRow } from '@/components/ui/table'
import { t } from '@/lib/i18n'

/**
 * Lo que una columna puede declarar sobre sí misma.
 *
 * `numeric` viene de que la regla de las columnas de cifras se rompía fila por
 * fila: estaba escrita dos veces —una en la celda y otra en el encabezado— y
 * alcanzaba con olvidarse de una. Declarada acá, la aplican las dos tablas en
 * los dos lugares a la vez.
 *
 * `label` es cómo se llama la columna cuando se la nombra **fuera** de su
 * encabezado —el menú de columnas de la tabla de navegador—. Hace falta porque
 * `header` puede ser un nodo o una función y ese menú necesita una cadena; lo
 * único que quedaría si no está es `column.id`, que es un identificador en
 * inglés, y mostrarlo sería escribir texto de pantalla fuera del catálogo. Una
 * columna sin nombre en palabras no se ofrece en el menú, en vez de ofrecerse
 * con su id.
 */
export interface DataTableColumnMeta {
  /** Alinea a la derecha y fija `tabular-nums`, en la celda **y** en el encabezado. */
  numeric?: boolean
  /** El nombre de la columna en palabras, ya traducido. */
  label?: string
  /**
   * La columna es de controles: ocupa lo mínimo y alinea al centro.
   *
   * `align-top` es lo correcto para una celda con texto —una URL de tres
   * renglones al lado de una fecha corta se lee mal centrada— y es exactamente
   * lo incorrecto para una casilla al lado de un botón, que tienen alturas
   * distintas y quedarían con los centros a seis píxeles uno del otro.
   */
  compact?: boolean
}

/**
 * Le agrega `numeric()` a un constructor de columnas.
 *
 * Se conservan las sobrecargas de `accessor` —clave o función, con o sin id— en
 * vez de volver a declararlas: lo único que agrega `numeric` es la marca, y una
 * firma copiada a mano se desincroniza en la primera versión que cambie la de
 * arriba. Está acá y no en cada tabla porque el cast es lo único delicado del
 * asunto, y escrito dos veces se desincroniza igual.
 */
export function withNumeric<H extends { accessor: unknown }>(
  helper: H
): H & { numeric: H['accessor'] } {
  const numeric = ((accessor: unknown, options: { meta?: DataTableColumnMeta } = {}) =>
    (helper.accessor as (target: unknown, config: unknown) => unknown)(accessor, {
      ...options,
      meta: { ...options.meta, numeric: true },
    })) as H['accessor']

  return { ...helper, numeric }
}

/** Cómo se dice en `aria-sort` cada uno de los tres estados de una columna. */
export const ARIA_SORT = {
  asc: 'ascending',
  desc: 'descending',
  none: 'none',
} as const

/**
 * El control del encabezado ordenable, con su foco visible propio (RT-15).
 *
 * En una tabla es un enlace y en la otra un botón, así que las clases son las
 * mismas y el elemento no: lo que se comparte es el dibujo, no la etiqueta.
 */
export const SORT_CONTROL =
  '-mx-1 inline-flex items-center gap-1 rounded px-1 py-0.5 ' +
  'hover:text-foreground focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none'

/**
 * Una sola región desplazable, y con altura acotada.
 *
 * `overflow-x` convierte también el eje vertical en desplazable, así que un
 * encabezado fijo dentro de un contenedor sin altura no se fija contra nada.
 * Acotarla es lo que hace que `sticky` signifique algo, y de paso lo que
 * garantiza que el desplazamiento horizontal sea de la tabla y nunca de la
 * página (RT-09).
 *
 * `relative` **no es decoración.** Todo lo que una tabla dice sólo para un
 * lector de pantalla —la fecha absoluta de cada celda, el `caption`, el nombre
 * del menú de cada fila— se posiciona con `absolute`, y sin un ancestro
 * posicionado resuelve contra el armazón en vez de contra este recuadro: se
 * escapa del recorte y estira la página cientos de píxeles hacia el vacío.
 * Medido en la lista de sesiones: 1.333 px de desplazamiento fantasma contra
 * 114 con esta clase.
 */
export const TABLE_SCROLLER =
  'border-border relative max-h-[70vh] overflow-auto overscroll-contain rounded-lg border'

/**
 * El marco de una tabla que **no** se desplaza por dentro.
 *
 * Es lo que le corresponde a una lista corta: quien mira elige cuántas filas ve,
 * y todas entran en la página. Un recuadro con barra propia adentro de una
 * página que ya se desplaza son dos desplazamientos peleándose por la misma
 * rueda, y el de adentro gana justo cuando el puntero pasa por encima de la
 * tabla —que es todo el tiempo—.
 *
 * `overflow-x-auto` queda igual, porque una columna con una URL larga desborda a
 * lo ancho aunque sobren filas; el `relative` es por lo mismo que en el otro
 * marco: contiene lo que se posiciona en absoluto.
 */
export const TABLE_FRAME = 'border-border relative w-full overflow-x-auto rounded-lg border'

/** El encabezado se queda arriba mientras la tabla se desplaza. */
export const HEAD_CELL = 'bg-background sticky top-0 z-10 shadow-[inset_0_-1px_0_var(--border)]'

/**
 * Una celda corta lo que no entra en vez de empujar la página al costado.
 *
 * `wrap-anywhere` es lo que evita que una URL larga arrastre la tabla entera
 * hacia la derecha (RT-09).
 */
export const BODY_CELL = 'py-2.5 align-top break-words whitespace-normal wrap-anywhere align-middle'

/** Lo que le toca a una celda de cifras, en la celda y en el encabezado. */
export const NUMERIC_CELL = 'text-right tabular-nums'

/**
 * Lo que le toca a una celda de controles.
 *
 * `w-0` con `whitespace-nowrap` es cómo se le pide a una tabla que una columna
 * ocupe lo mínimo: el ancho cero es un pedido, no una orden, y el contenido que
 * no se puede partir lo empuja a lo justo. Sin eso, la columna de la casilla se
 * lleva su parte proporcional del ancho y deja los controles nadando.
 */
export const COMPACT_CELL = 'w-0 align-middle whitespace-nowrap'

/**
 * Cómo se dice la unidad según cuántas hay.
 *
 * Sin esto el contador dice «1 sitemaps», que es de las cosas que hacen dudar de
 * todo lo demás que muestra la pantalla. El singular se deduce quitando la «s»
 * final, que alcanza para «dominios» y «sitemaps», y se declara a mano cuando no
 * —«URLs» no es «URL» por esa regla—.
 */
export function unitName(count: number, plural: string, singular?: string): string {
  if (count === 1) return singular ?? plural.replace(/s$/, '')
  return plural
}

/**
 * Qué hace el clic siguiente sobre esta columna, dicho para quien no ve la
 * flecha.
 *
 * Es una función y no un mapa a nivel de módulo porque devuelve texto, y los
 * mapas de módulo se arman al importar, cuando el catálogo todavía no está
 * instalado. Compartida además para que una tabla no diga «Quitar el orden» y la
 * otra «Sin orden» sobre el mismo control.
 */
export function sortHint(next: 'asc' | 'desc' | false): string {
  if (next === false) return t('table.sort.clear')
  return next === 'asc' ? t('table.sort.asc') : t('table.sort.desc')
}

/** La flecha del orden vigente, o las dos apiladas cuando la columna no ordena. */
export function SortIndicator({ sorted }: { sorted: 'asc' | 'desc' | false }) {
  if (sorted === 'asc') return <ArrowUp className="size-3.5" aria-hidden />
  if (sorted === 'desc') return <ArrowDown className="size-3.5" aria-hidden />
  return <ChevronsUpDown className="text-muted-foreground size-3.5" aria-hidden />
}

/**
 * El esqueleto mientras la tabla trabaja.
 *
 * Conserva la altura de lo que había, para que el diseño no salte al cambiar de
 * página (RT-12).
 */
export function SkeletonRows({ rows, columns }: { rows: number; columns: number }) {
  return (
    <>
      {Array.from({ length: rows }, (_unused, row) => (
        <TableRow key={`skeleton-${row}`}>
          {Array.from({ length: columns }, (_ignored, cell) => (
            <TableCell key={cell} className="py-3">
              <Skeleton className="h-4 w-full" />
            </TableCell>
          ))}
        </TableRow>
      ))}
    </>
  )
}
