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

import { ClientDataTable, createClientDataTableColumns } from '@/components/ClientDataTable'
import type { ClientDataTableColumn } from '@/components/ClientDataTable'
import { DeltaCell } from '@/components/DeltaCell'
import { SectionCard } from '@/components/SectionCard'
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { route } from '@/lib/routes'
import { TopKeywordsChart } from '@/pages/Seo/TopKeywordsChart'
import type { TopMetric } from '@/pages/Seo/TopKeywordsChart'
import type { KeywordRow, TopKeywords as TopKeywordsProps } from '@/pages/Seo/types'

interface Props {
  data: TopKeywordsProps
  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

/** Las tres métricas del interruptor, en el orden en que se leen. */
const METRICS: TopMetric[] = ['impressions', 'clicks', 'position']

/**
 * Por qué búsquedas aparece el sitio, arriba de todo y a lo ancho.
 *
 * Es la contracara de las tarjetas de indicadores: aquéllas dan la cifra del
 * período y ésta muestra **cómo se llegó a ella**. Van en la misma pantalla y
 * sobre el mismo período por eso mismo.
 *
 * **La tabla va en el pie de la tarjeta y es una excepción declarada**, por dos
 * reglas a la vez:
 *
 * - Una data table no va adentro de una tarjeta. Acá son **diez filas fijas**,
 *   que es el límite que esa regla ya se había puesto, y sin buscador ni menú de
 *   columnas ni paginado, porque el conjunto lo decidió el servidor.
 * - El pie de este proyecto es para cierres —una nota, un par de acciones—. Se
 *   usa igual **por el borde**: `CardFooter` trae `border-t` y un fondo apenas
 *   distinto, y eso es exactamente lo que separa la tabla del gráfico. En el
 *   contenido las dos quedarían pegadas, sin nada que diga dónde termina una.
 */
export function TopKeywords({ data, window_days, min_impressions }: Props) {
  const [metric, setMetric] = useState<TopMetric>('impressions')

  const columns = useMemo<ClientDataTableColumn<KeywordRow>[]>(
    () => [
      columnHelper.accessor('query', {
        id: 'query',
        header: t('seo.column.keyword'),
        cell: (info) => (
          <Link
            href={`${route('seo.keyword')}?q=${encodeURIComponent(info.getValue())}`}
            className="font-medium whitespace-nowrap hover:underline"
            translate="no"
          >
            {info.getValue()}
          </Link>
        ),
      }),
      columnHelper.accessor('position', {
        id: 'position',
        header: 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 }),
          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'),
        cell: (info) => <span className="tabular-nums">{formatNumber(info.getValue())}</span>,
      }),
      columnHelper.accessor('clicks', {
        id: 'clicks',
        header: t('seo.column.clicks'),
        cell: (info) => <span className="tabular-nums">{formatNumber(info.getValue())}</span>,
      }),
      columnHelper.accessor('ctr', {
        id: 'ctr',
        header: t('seo.column.ctr'),
        cell: (info) => (
          <span className="tabular-nums">{(info.getValue() * 100).toFixed(1)}%</span>
        ),
      }),
      // Ninguna columna declara `meta.label`, y así el menú de columnas no se
      // dibuja: sobre diez filas que el servidor eligió, esconder columnas es un
      // control que sobra.
    ],
    [min_impressions]
  )

  return (
    <SectionCard
      title={{ text: t('seo.top.title') }}
      description={t('seo.top.description', { days: window_days })}
      actions={
        /*
          Es un grupo de botones que se comporta como radios: una métrica por
          vez. No son interruptores acumulables como en la ficha de una keyword
          —allá las tres se superponen sobre dos ejes—, porque acá cada métrica
          ya trae diez líneas y sumarlas daría treinta.
        */
        <ToggleGroup
          type="single"
          variant="outline"
          size="sm"
          spacing={0}
          value={metric}
          // Sin valor no hay gráfico: apretar la métrica activa no la apaga, la
          // deja donde está. Un marco vacío se lee como un error de carga.
          onValueChange={(next) => next && setMetric(next as TopMetric)}
          aria-label={t('seo.top.metric')}
        >
          {METRICS.map((option) => (
            <ToggleGroupItem key={option} value={option}>
              {t(`seo.column.${option}` as 'seo.column.position')}
            </ToggleGroupItem>
          ))}
        </ToggleGroup>
      }
      /*
        La tarjeta recorta, así que el redondeo lo dibuja ella y no la tabla. Es
        lo que permite que la tabla llegue hasta el borde: con el pie sin relleno
        y la tabla con sus esquinas rectas, las dos coinciden y no queda ese
        halo de fondo entre el borde de la caja y el borde de la tabla.
      */
      className="overflow-hidden"
      footerClassName="flex-col items-stretch p-0"
      footer={
        <ClientDataTable
          data={data.keywords}
          columns={columns}
          rowId={(row) => row.query}
          unit={t('seo.keywords.unit')}
          unitSingular={t('seo.keywords.unitSingular')}
          caption={t('seo.top.caption', { days: window_days })}
          // Sin orden declarado: llegan ordenadas por impresiones desde el
          // servidor, y son **las mismas diez y en el mismo orden** que las
          // líneas del gráfico. Reordenarlas acá las separaría de la leyenda.
          noPagination
          /*
            El marco de la tabla pierde borde y redondeo —los pone la tarjeta— y
            el contador recupera el relleno que el pie ya no tiene. Van como
            selectores y no como props nuevas porque es el único lugar del
            producto donde una tabla vive a ras del borde de una caja: dos props
            en la tabla compartida para un caso serían dos props que hay que
            entender en las otras seis pantallas que no las usan.
          */
          className="gap-0 [&>div:first-child]:rounded-none [&>div:first-child]:border-0 [&>div:last-child]:px-4 [&>div:last-child]:py-2"
          empty={{
            icon: SearchIcon,
            title: t('seo.keywords.empty.title'),
            description: t('seo.keywords.empty.description'),
          }}
        />
      }
    >
      <TopKeywordsChart dates={data.dates} series={data.series} metric={metric} />
    </SectionCard>
  )
}
