import { SplitIcon } from 'lucide-react'
import { useMemo } from 'react'

import { ClientDataTable, createClientDataTableColumns } from '@/components/ClientDataTable'
import type {
  ClientDataTableColumn,
  ClientDataTableFacetedFilter,
} from '@/components/ClientDataTable'
import { AppLayout } from '@/layouts/AppLayout'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { CannibalizationPages } from '@/pages/Seo/CannibalizationPages'
import type {
  CannibalizationSeverity,
  CannibalizedKeyword,
  PageRotation,
  SiteProps,
} from '@/pages/Seo/types'
import { ROTATION_LABEL, SEVERITY_LABEL } from '@/pages/Seo/vocabulary'

interface Props {
  domain: SiteProps
  rows: CannibalizedKeyword[]
  /** Con qué recorte llega la pantalla, por eje. Vacío casi siempre. */
  filters: Record<string, string[]>
  threshold: number
  window_days: number
}

const columnHelper = createClientDataTableColumns<CannibalizedKeyword>()

/**
 * Las consultas por las que compiten varias páginas del sitio.
 *
 * Es una tabla con filas que se abren, y no una lista: hay decenas de consultas
 * afectadas, y buscar una, ordenar por cuántas páginas se pisan o pasar de
 * página son exactamente las cosas que una tabla hace y una lista no.
 *
 * **La tabla no va adentro de una `SectionCard`.** Una tarjeta es una caja con
 * título para agrupar contenido; una tabla con buscador, filtros y paginado ya
 * trae su propio marco y sus propios controles, y meterla adentro produce dos
 * bordes concéntricos y dos encabezados —el de la tarjeta y el de la tabla—
 * compitiendo por decir qué se está mirando. La tarjeta se reserva para listas
 * cortas, de diez elementos o menos, que no necesitan ninguno de esos controles.
 *
 * Lo que se cuenta son páginas **del mismo día**. Contarlas a lo largo del
 * período mide casi el doble y mide otra cosa: dos páginas que rankearon en
 * semanas distintas no compitieron, se reemplazaron.
 */
export default function Cannibalization({
  domain,
  rows,
  filters,
  threshold,
  window_days,
}: Props) {
  const columns = useMemo<ClientDataTableColumn<CannibalizedKeyword>[]>(
    () => [
      columnHelper.accessor('query', {
        id: 'query',
        header: t('seo.column.keyword'),
        // No se puede esconder: es lo único que dice de qué habla la fila.
        enableHiding: false,
        // Sin partir en renglones: una keyword de cinco palabras repartida en
        // tres líneas engorda la columna hacia abajo y estira todas las filas de
        // la tabla al alto de la peor. El marco ya se desplaza a lo ancho.
        cell: (info) => (
          <span className="font-medium whitespace-nowrap" translate="no">
            {info.getValue()}
          </span>
        ),
      }),
      columnHelper.accessor('competing', {
        id: 'competing',
        header: t('seo.column.competing'),
        meta: { label: t('seo.column.competing') },
        cell: (info) => <span className="tabular-nums">{formatNumber(info.getValue())}</span>,
      }),
      /*
        Cuántas aparecieron en todo el período, al lado de cuántas se pisaron el
        mismo día. La diferencia entre las dos es cuánto rotó la URL que rankea,
        que es otra historia y conviene no confundirla con ésta: por eso son dos
        columnas y no una.
      */
      columnHelper.accessor('pages_in_period', {
        id: 'pages_in_period',
        header: t('seo.column.pagesInPeriod'),
        meta: { label: t('seo.column.pagesInPeriod') },
        cell: (info) => <span className="tabular-nums">{formatNumber(info.getValue())}</span>,
      }),
      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>,
      }),

      /*
        Los dos ejes de filtrado, cada uno con su columna. Arrancan escondidas:
        el estado del recorte es de la columna, no de la tabla, así que el filtro
        necesita una donde colgarse, pero lo que dicen ya está en las dos
        columnas de al lado —la gravedad sale de «compiten el mismo día» y la
        rotación, de la diferencia entre las dos cifras—. Quien las quiera ver
        las trae desde el menú de columnas.

        Fuera del buscador: sus valores son identificadores en mayúsculas.
      */
      columnHelper.accessor((row) => row.severity, {
        id: 'severity',
        header: t('seo.column.severity'),
        meta: { label: t('seo.column.severity') },
        enableGlobalFilter: false,
        cell: (info) => (
          <span className="text-muted-foreground text-xs">
            {SEVERITY_LABEL[info.getValue() as CannibalizationSeverity]()}
          </span>
        ),
      }),
      columnHelper.accessor((row) => row.rotation, {
        id: 'rotation',
        header: t('seo.column.rotation'),
        meta: { label: t('seo.column.rotation') },
        enableGlobalFilter: false,
        cell: (info) => (
          <span className="text-muted-foreground text-xs">
            {ROTATION_LABEL[info.getValue() as PageRotation]()}
          </span>
        ),
      }),
    ],
    []
  )

  const facetedFilters = useMemo<ClientDataTableFacetedFilter[]>(
    () => [
      {
        column: 'severity',
        title: t('seo.column.severity'),
        // Múltiple: los tres tramos son contiguos, así que «alta o grave» es una
        // pregunta corriente y con selección simple habría que hacerla dos veces.
        selection: 'multiple',
        options: (Object.keys(SEVERITY_LABEL) as CannibalizationSeverity[]).map((value) => ({
          value,
          label: SEVERITY_LABEL[value](),
        })),
      },
      {
        column: 'rotation',
        title: t('seo.column.rotation'),
        // Simple: son dos valores que se excluyen, y elegir los dos es no
        // filtrar. Ofrecer marcarlos juntos sería ofrecer nada.
        selection: 'single',
        options: (Object.keys(ROTATION_LABEL) as PageRotation[]).map((value) => ({
          value,
          label: ROTATION_LABEL[value](),
        })),
      },
    ],
    []
  )

  return (
    <AppLayout
      title={t('seo.cannibalization.title')}
      description={t('seo.cannibalization.description', { threshold, days: window_days })}
    >
      <p className="text-muted-foreground text-sm text-pretty">
        {t('seo.cannibalization.listHelp')}
      </p>

      <ClientDataTable
        data={rows}
        columns={columns}
        rowId={(row) => row.query}
        unit={t('seo.keywords.unit')}
        unitSingular={t('seo.keywords.unitSingular')}
        caption={t('seo.cannibalization.list')}
        // Por cuántas páginas se pisan y no alfabéticamente: la pregunta con la
        // que se entra acá es «cuál está peor».
        sort={{ column: 'competing', descending: true }}
        pageSize={20}
        search={{
          label: t('seo.cannibalization.searchLabel'),
          placeholder: t('seo.cannibalization.searchPlaceholder'),
        }}
        /*
          La ranura del detalle. La tabla no sabe qué va acá —reserva el ancho y
          nada más—, y lo que va es una grilla que **se pide al abrir**: de las
          decenas de consultas de esta pantalla se abren dos o tres, y mandar las
          URLs de todas en la carga inicial era traer cientos de filas para
          dibujar unas pocas.
        */
        renderExpanded={(row) => (
          <CannibalizationPages query={row.query} hostname={domain.hostname} />
        )}
        facetedFilters={facetedFilters}
        filters={filters}
        hiddenColumns={['severity', 'rotation']}
        empty={{
          icon: SplitIcon,
          title: t('seo.cannibalization.empty.title'),
          description: t('seo.cannibalization.empty.description', { threshold }),
        }}
      />

      <FilterNotes threshold={threshold} />
    </AppLayout>
  )
}

/**
 * Qué significa cada opción de los filtros, al pie y en chico.
 *
 * Va acá y no en un globo sobre cada opción: son cinco explicaciones y una de
 * ellas —la rotación— no cabe en una línea, porque lo que hay que entender no es
 * el rótulo sino **por qué son dos problemas distintos**. Cinco globos obligan a
 * apuntar cinco veces para entender un solo control.
 *
 * Al pie de la pantalla y no arriba de la tabla porque es material de consulta:
 * se lee una vez, cuando aparece la duda, y el resto del tiempo no tiene que
 * estar entre el encabezado y los datos.
 */
function FilterNotes({ threshold }: { threshold: number }) {
  return (
    <section className="text-muted-foreground space-y-3 border-t pt-4 text-xs">
      <h2 className="text-foreground text-sm font-medium">{t('seo.cannibalization.notes')}</h2>

      <dl className="space-y-2">
        <Note term={t('seo.column.severity')} description={t('seo.severity.help', { threshold })} />
        <Note term={t('seo.column.rotation')} description={t('seo.rotation.help')} />
      </dl>
    </section>
  )
}

function Note({ term, description }: { term: string; description: string }) {
  return (
    <div className="flex flex-col gap-0.5 sm:flex-row sm:gap-2">
      <dt className="text-foreground shrink-0 font-medium">{term}</dt>
      <dd className="text-pretty">{description}</dd>
    </div>
  )
}
