import { ListIcon } from 'lucide-react'
import { useMemo, useState } from 'react'
import Lightbox from 'yet-another-react-lightbox'
import 'yet-another-react-lightbox/styles.css'

import { ClientDataTable, createClientDataTableColumns } from '@/components/ClientDataTable'
import type {
  ClientDataTableColumn,
  ClientDataTableFacetedFilter,
  RowActionsFor,
} from '@/components/ClientDataTable'
import { DataTimestamp, TimezoneFootnote } from '@/components/DataTimestamp'
import { GenerationStateBadge } from '@/components/GenerationStateBadge'
import { Badge } from '@/components/ui/badge'
import { batchDuration } from '@/lib/batch'
import { t } from '@/lib/i18n'

import {
  areaLabel,
  generationErrorText,
  generationStateLabel,
  kindLabel,
  originLabel,
  serviceLabel,
} from './ImageGenerator/labels'
import { generationHref, servicesOf } from './ImageGenerator/rows'
import type { Generation, GenerationOrigin, GenerationState } from './ImageGenerator/types'

interface Props {
  generations: Generation[]
  /** Los valores por los que se puede filtrar. */
  options: { origin: GenerationOrigin[]; state: GenerationState[]; service: string[] }
  empty: { title: string; description: string }
  /** Cómo se nombra la lista para quien no la ve. */
  caption: string
  /** Al abrir una fila desde el menú. Sin esto, abre el generador con ese pedido. */
  onOpen?: (generation: Generation) => void
}

const columnHelper = createClientDataTableColumns<Generation>()

/**
 * Las columnas, con la imagen abriendo un visor y no otra pestaña.
 *
 * La imagen es lo que se vino a ver: va grande, en 16:9 y de cien de alto,
 * aunque engorde la fila. Al hacerle clic se abre en un visor sobre la misma
 * pantalla, con flechas para pasar a la siguiente, en vez de mandar a la
 * persona a otra pestaña por cada diseño.
 *
 * Qué se pidió se dice con las áreas del pedido, como chips; una fila v1 no
 * tiene áreas y dice su tipo de entonces (decisión 1). Los servicios son una
 * columna aparte y escondida: existe para filtrar, no para leer, porque ya se
 * leen en el detalle.
 */
function generationColumns(open: (generation: Generation) => void): ClientDataTableColumn<Generation>[] {
  return [
    columnHelper.accessor('id', { id: 'generation_id', header: t('ai.queue.column.id') }),
    columnHelper.accessor('result_url', {
      id: 'thumbnail',
      header: t('ai.queue.column.image'),
      meta: { label: t('ai.queue.column.image') },
      enableSorting: false,
      enableGlobalFilter: false,
      cell: (info) => {
        const generation = info.row.original
        if (!generation.result_url) {
          return (
            <div
              aria-hidden
              className="bg-muted text-muted-foreground flex aspect-video h-25 items-center justify-center rounded-md border text-xs"
            >
              —
            </div>
          )
        }
        return (
          <button
            type="button"
            onClick={() => open(generation)}
            aria-label={t('ai.queue.viewImage')}
            className="focus-visible:ring-ring block aspect-video h-25 cursor-zoom-in overflow-hidden rounded-md border focus-visible:ring-2 focus-visible:outline-none"
          >
            <img
              src={generation.result_url}
              alt=""
              loading="lazy"
              className="bg-muted size-full object-cover"
            />
          </button>
        )
      },
    }),
    columnHelper.accessor('areas', {
      id: 'areas',
      header: t('ai.queue.column.areas'),
      meta: { label: t('ai.queue.column.areas') },
      enableSorting: false,
      enableGlobalFilter: false,
      cell: (info) => {
        const generation = info.row.original
        if (!generation.is_v2) {
          return <span className="font-medium">{kindLabel(generation.kind)}</span>
        }
        return (
          <div className="flex max-w-xs flex-wrap gap-1">
            {generation.areas.map((container) => (
              <Badge key={container.area} variant="outline">
                {areaLabel(container.area)}
              </Badge>
            ))}
          </div>
        )
      },
    }),
    columnHelper.accessor(servicesOf, {
      id: 'service',
      header: t('ai.queue.column.service'),
      meta: { label: t('ai.queue.column.service') },
      enableSorting: false,
      enableGlobalFilter: false,
      cell: (info) => info.getValue().map(serviceLabel).join(', '),
    }),
    columnHelper.accessor('origin', {
      id: 'origin',
      header: t('ai.queue.column.origin'),
      meta: { label: t('ai.queue.column.origin') },
      filterFn: 'includesSelected',
      enableGlobalFilter: false,
      cell: (info) => {
        const generation = info.row.original
        return (
          <div className="flex flex-col items-start gap-0.5 whitespace-nowrap">
            <span>{originLabel(generation.origin)}</span>
            {generation.has_source_image ? (
              <span className="text-muted-foreground text-xs">{t('ai.queue.withPhoto')}</span>
            ) : null}
          </div>
        )
      },
    }),
    columnHelper.accessor('requested_by', {
      id: 'requested_by',
      header: t('ai.queue.column.requestedBy'),
      meta: { label: t('ai.queue.column.requestedBy') },
      cell: (info) => info.getValue() ?? <span className="text-muted-foreground">{t('ai.queue.anonymous')}</span>,
    }),
    columnHelper.accessor('created_at', {
      id: 'created',
      header: t('ai.queue.column.created'),
      meta: { label: t('ai.queue.column.created') },
      enableGlobalFilter: false,
      cell: (info) => <DataTimestamp value={info.getValue()} className="whitespace-nowrap" />,
    }),
    columnHelper.accessor('state', {
      id: 'state',
      header: t('ai.queue.column.state'),
      meta: { label: t('ai.queue.column.state') },
      filterFn: 'includesSelected',
      enableGlobalFilter: false,
      cell: (info) => {
        const generation = info.row.original
        return (
          <div className="flex flex-col items-start gap-1">
            <GenerationStateBadge state={generation.state} />
            {generation.state === 'FAILED' ? (
              <span className="text-muted-foreground max-w-xs text-xs text-pretty">
                {generationErrorText(generation.error_code)}
              </span>
            ) : null}
          </div>
        )
      },
    }),
    columnHelper.accessor('duration_seconds', {
      id: 'duration',
      header: t('ai.queue.column.duration'),
      meta: { label: t('ai.queue.column.duration'), numeric: true },
      enableGlobalFilter: false,
      cell: (info) => (
        <span className="tabular-nums whitespace-nowrap">{batchDuration(info.getValue())}</span>
      ),
    }),
  ]
}

/**
 * La tabla de pedidos, la misma en la cola y en la pestaña de recientes.
 *
 * Una sola pieza para las dos pantallas: imagen, áreas, origen, quién, cuándo,
 * estado y duración son idénticos, y dos copias de esto ya divergieron una vez
 * en el tamaño de la miniatura. Se filtra por estado, servicio y origen; el
 * filtro por servicio mira los contenedores de cada fila, así que una fila con
 * dos servicios sale con cualquiera de los dos.
 */
export function GenerationsTable({ generations, options, empty, caption, onOpen }: Props) {
  // Las imágenes del visor, en el orden de llegada y sólo las que existen: el
  // índice que se abre es la posición en esta lista, no la fila de la tabla.
  const slides = useMemo(
    () =>
      generations
        .filter((generation) => generation.result_url)
        .map((generation) => ({
          id: generation.id,
          src: generation.result_url as string,
          alt: t('ai.result.alt'),
        })),
    [generations]
  )
  const [viewing, setViewing] = useState<number | null>(null)

  const columns = useMemo(
    () =>
      generationColumns((generation) => {
        const index = slides.findIndex((slide) => slide.id === generation.id)
        if (index >= 0) setViewing(index)
      }),
    [slides]
  )

  const filters = useMemo<ClientDataTableFacetedFilter[]>(
    () => [
      {
        column: 'state',
        title: t('ai.queue.column.state'),
        options: options.state.map((value) => ({ value, label: generationStateLabel(value) })),
        selection: 'multiple',
      },
      {
        column: 'service',
        title: t('ai.queue.column.service'),
        options: options.service.map((value) => ({ value, label: serviceLabel(value) })),
        selection: 'multiple',
      },
      {
        column: 'origin',
        title: t('ai.queue.column.origin'),
        options: options.origin.map((value) => ({ value, label: originLabel(value) })),
        selection: 'multiple',
      },
    ],
    [options]
  )

  const rowActions = (generation: Generation): RowActionsFor => ({
    label: t('ai.queue.row.actions'),
    actions: {
      detail: onOpen ? { onSelect: () => onOpen(generation) } : { href: generationHref(generation) },
    },
  })

  return (
    <>
      <ClientDataTable
        data={generations}
        columns={columns}
        rowId={(row) => row.id}
        unit={t('ai.queue.unit')}
        unitSingular={t('ai.queue.unitSingular')}
        caption={caption}
        search={{ label: t('ai.queue.search.label'), placeholder: t('ai.queue.search.placeholder') }}
        facetedFilters={filters}
        hiddenColumns={['generation_id', 'service']}
        sort={{ column: 'created', descending: true }}
        rowActions={rowActions}
        empty={{ icon: ListIcon, ...empty }}
      />
      <TimezoneFootnote />
      <Lightbox
        open={viewing !== null}
        index={viewing ?? 0}
        close={() => setViewing(null)}
        slides={slides}
        controller={{ closeOnBackdropClick: true }}
      />
    </>
  )
}
