import { Link } from '@inertiajs/react'
import { SearchIcon } from 'lucide-react'
import { useMemo } from 'react'

import { ClientDataTable, createClientDataTableColumns } from '@/components/ClientDataTable'
import type {
  ClientDataTableColumn,
  ClientDataTableFacetedFilter,
} from '@/components/ClientDataTable'
import { DeltaCell } from '@/components/DeltaCell'
import { AppLayout } from '@/layouts/AppLayout'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { route } from '@/lib/routes'
import { BAND_LABEL, REACH_LABEL, TRACKING_LABEL } from '@/pages/Seo/vocabulary'
import { ModuleStatusBadge } from '@/pages/Seo/status'
import type {
  Engagement,
  KeywordRow,
  ModuleStatus,
  PositionBand,
  SiteProps,
} from '@/pages/Seo/types'

interface Props {
  domain: SiteProps
  rows: KeywordRow[]
  /**
   * Con qué recorte llega la pantalla, por eje. Vacío casi siempre.
   *
   * Lo arma el servidor a partir de la dirección, y es lo que permite que una
   * tarjeta del inicio enlace acá **contestando la misma pregunta con el mismo
   * conjunto**, que es la única condición bajo la cual una cifra puede enlazar
   * en vez de abrirse donde se la lee.
   */
  filters: Record<string, string[]>
  status: ModuleStatus
  last_closed_date: string | null
  window_days: number
  min_impressions: number
}

const columnHelper = createClientDataTableColumns<KeywordRow>()

/** Las cuatro ventanas, en el orden en que se leen. Coinciden con `DELTA_WINDOWS`. */
const WINDOWS = [1, 3, 7, 15] as const

/**
 * Por qué búsquedas aparece el sitio, y cómo viene cambiando.
 *
 * Todo lo que se ve acá sale de la tabla de totales por consulta. **La tabla de
 * páginas no se suma**: sus impresiones están repartidas entre las URLs que
 * aparecieron en cada búsqueda, y sumarlas inflaría una de cada cuatro filas.
 *
 * Los tres ejes por los que se filtra —tramo, alcance y seguimiento— llegan
 * **calculados por el servidor** en cada fila. No se deducen acá a propósito: el
 * mismo corte lo usa el indicador del inicio, y la misma regla escrita dos veces
 * —una en Python y otra en TypeScript— se separa en cuanto alguien toca una de
 * las dos, sin que nada falle: la tarjeta diría 43 y la tabla mostraría 51.
 */
export default function Keywords({
  domain,
  rows,
  filters,
  status,
  last_closed_date,
  window_days,
  min_impressions,
}: Props) {
  const columns = useMemo<ClientDataTableColumn<KeywordRow>[]>(
    () => [
      columnHelper.accessor('query', {
        id: 'query',
        header: t('seo.column.keyword'),
        // No se puede esconder: es la única columna que dice de qué habla la
        // fila, y sin ella quedan nueve cifras sin sujeto.
        enableHiding: false,
        cell: (info) => (
          <Link
            href={`${route('seo.keyword')}?q=${encodeURIComponent(
              info.getValue()
            )}`}
            className="font-medium hover:underline"
            translate="no"
          >
            {info.getValue()}
          </Link>
        ),
      }),
      columnHelper.accessor('position', {
        id: 'position',
        header: t('seo.column.position'),
        // `label` es lo que hace que la columna aparezca en el menú de columnas.
        // Sin ella `hideableColumns()` la descarta —lo único que quedaría para
        // nombrarla es su `id`, que es un identificador en inglés— y el menú
        // entero no se dibuja. Era exactamente lo que le pasaba a esta tabla.
        meta: { label: t('seo.column.position') },
        cell: (info) => (
          <span className="tabular-nums">{info.getValue().toFixed(1)}</span>
        ),
      }),
      ...WINDOWS.map((days) =>
        columnHelper.accessor((row) => row.deltas[String(days)]?.value ?? null, {
          id: `delta${days}`,
          header: t('seo.column.delta', { days }),
          meta: { label: t('seo.column.delta', { days }) },
          cell: (info) => (
            <DeltaCell
              delta={info.row.original.deltas[String(days)] ?? null}
              window={days}
              minImpressions={min_impressions}
            />
          ),
        })
      ),
      columnHelper.accessor('impressions', {
        id: 'impressions',
        header: t('seo.column.impressions'),
        meta: { label: t('seo.column.impressions') },
        cell: (info) => (
          <span className="tabular-nums">{formatNumber(info.getValue())}</span>
        ),
      }),
      columnHelper.accessor('clicks', {
        id: 'clicks',
        header: t('seo.column.clicks'),
        meta: { label: t('seo.column.clicks') },
        cell: (info) => (
          <span className="tabular-nums">{formatNumber(info.getValue())}</span>
        ),
      }),
      columnHelper.accessor('ctr', {
        id: 'ctr',
        header: t('seo.column.ctr'),
        meta: { label: t('seo.column.ctr') },
        // El porcentaje nunca va solo: al lado están las impresiones, que son su
        // denominador, así que la cifra se puede leer sin adivinar sobre qué es.
        cell: (info) => (
          <span className="tabular-nums">{(info.getValue() * 100).toFixed(1)}%</span>
        ),
      }),

      /*
        Los tres ejes de filtrado, cada uno con su columna.

        Arrancan escondidas y no ausentes, y son dos cosas distintas: un filtro
        necesita una columna para colgarse —el estado del recorte es de la
        columna, no de la tabla— y la tabla ya tiene nueve, así que sumarle tres
        visibles la volvería ilegible por resolver el filtrado. Escondidas, el
        filtro funciona igual y quien quiera ver el tramo de cada fila lo trae
        desde el menú de columnas.

        No participan del buscador: sus valores son identificadores en
        mayúsculas, y escribir «clic» traería todas las filas con clics sin que
        nada en pantalla explique por qué.
      */
      columnHelper.accessor((row) => row.band, {
        id: 'band',
        header: t('seo.column.band'),
        meta: { label: t('seo.column.band') },
        enableGlobalFilter: false,
        // El casteo es porque `ClientDataTableColumn` declara el valor de la
        // celda como `any`: el juego cerrado lo pone el tipo de la fila, no la
        // tabla.
        cell: (info) => (
          <span className="text-muted-foreground text-xs">
            {BAND_LABEL[info.getValue() as PositionBand]()}
          </span>
        ),
      }),
      columnHelper.accessor((row) => row.engagement, {
        id: 'engagement',
        header: t('seo.column.reach'),
        meta: { label: t('seo.column.reach') },
        enableGlobalFilter: false,
        cell: (info) => (
          <span className="text-muted-foreground text-xs">
            {REACH_LABEL[info.getValue() as Engagement]()}
          </span>
        ),
      }),
      columnHelper.accessor((row) => (row.tracked ? 'TRACKED' : 'UNTRACKED'), {
        id: 'tracked',
        header: t('seo.column.tracked'),
        meta: { label: t('seo.column.tracked') },
        enableGlobalFilter: false,
        cell: (info) => (
          <span className="text-muted-foreground text-xs">
            {TRACKING_LABEL[info.getValue() as 'TRACKED' | 'UNTRACKED']()}
          </span>
        ),
      }),
    ],
    [domain.id, min_impressions]
  )

  const facetedFilters = useMemo<ClientDataTableFacetedFilter[]>(
    () => [
      {
        column: 'band',
        title: t('seo.column.band'),
        // Múltiple: los cuatro tramos son contiguos y excluyentes, así que
        // «primera página o segunda» es una pregunta corriente y con selección
        // simple habría que hacerla en dos pasadas.
        selection: 'multiple',
        options: (Object.keys(BAND_LABEL) as (keyof typeof BAND_LABEL)[]).map((value) => ({
          value,
          label: BAND_LABEL[value](),
        })),
      },
      {
        column: 'engagement',
        title: t('seo.column.reach'),
        selection: 'multiple',
        options: (Object.keys(REACH_LABEL) as (keyof typeof REACH_LABEL)[]).map((value) => ({
          value,
          label: REACH_LABEL[value](),
        })),
      },
      {
        column: 'tracked',
        title: t('seo.column.tracked'),
        // Simple: son dos valores que se excluyen y elegir los dos es no
        // filtrar, así que ofrecer marcarlos juntos es ofrecer nada.
        selection: 'single',
        options: (Object.keys(TRACKING_LABEL) as (keyof typeof TRACKING_LABEL)[]).map((value) => ({
          value,
          label: TRACKING_LABEL[value](),
        })),
      },
    ],
    []
  )

  return (
    <AppLayout
      title={t('seo.keywords.title')}
      description={t('seo.keywords.description', { days: window_days })}
      /*
        Arriba a la derecha, que es donde se buscan el estado y las acciones de
        una pantalla. Debajo de la descripción quedaba en el medio del camino
        entre el encabezado y la tabla, y ese renglón no es ni una cosa ni la
        otra.

        Hasta qué día llegan los datos va al lado y no en otro renglón: Google
        publica con dos o tres días de atraso, así que sin esa fecha la tabla
        parece decir «hoy» y no lo dice.
      */
      actions={
        <div className="flex flex-wrap items-center gap-3 text-sm">
          {last_closed_date ? (
            <span className="text-muted-foreground">
              {t('seo.upTo', { date: last_closed_date })}
            </span>
          ) : null}
          <ModuleStatusBadge status={status} />
        </div>
      }
    >
      <ClientDataTable
        data={rows}
        columns={columns}
        rowId={(row) => row.query}
        unit={t('seo.keywords.unit')}
        unitSingular={t('seo.keywords.unitSingular')}
        caption={t('seo.keywords.caption')}
        // Ordena por impresiones y no alfabéticamente: la pregunta con la que se
        // entra acá es «por qué me encuentran más», no «cuál empieza con A».
        sort={{ column: 'impressions', descending: true }}
        pageSize={50}
        search={{
          label: t('seo.keywords.searchLabel'),
          placeholder: t('seo.keywords.searchPlaceholder'),
        }}
        facetedFilters={facetedFilters}
        filters={filters}
        hiddenColumns={['band', 'engagement', 'tracked']}
        empty={{
          icon: SearchIcon,
          title: t('seo.keywords.empty.title'),
          description: t('seo.keywords.empty.description'),
        }}
      />
    </AppLayout>
  )
}
