import { Link, useForm, usePage } from '@inertiajs/react'
import { History } from 'lucide-react'
import { useEffect, useMemo, useRef, useState } from 'react'

import { BatchProgress } from '@/components/BatchProgress'
import { BatchStateBadge, batchStateLabel } from '@/components/BatchStateBadge'
import {
  ClientDataTable,
  createClientDataTableColumns,
} from '@/components/ClientDataTable'
import type {
  ClientDataTableColumn,
  ClientDataTableFacetedFilter,
} from '@/components/ClientDataTable'
import { DataTimestamp, TimezoneFootnote } from '@/components/DataTimestamp'
import { useBatchPolling } from '@/hooks/useBatchPolling'
import { AppLayout } from '@/layouts/AppLayout'
import { Button } from '@/components/ui/button'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { route } from '@/lib/routes'
import {
  batchAction,
  batchDuration,
  batchKindLabel,
  batchOriginLabel,
  batchPath,
  batchReasonText,
  shortBatchName,
} from '@/lib/batch'
import type { Batch } from '@/lib/batch'

interface Props {
  domain: {
    id: string
    hostname: string
    is_operational: boolean
    access_state: string
  }
  batches: Batch[]
  options: { state: string[]; kind: string[]; origin: string[] }
  /** Estados no terminales del dominio entero, no de la página. De acá sale el sondeo. */
  active_states: string[]
  pending_urls: number
}

const columnHelper = createClientDataTableColumns<Batch>()

/**
 * Las columnas de la tabla.
 *
 * El estado y su motivo van en la **misma** celda. Separarlos obligaría a cruzar
 * dos columnas para responder la única pregunta que trae acá —«¿lo último que
 * corrió terminó bien?»— y dejaría la fila parcial indistinguible de la
 * terminada salvo por el color del badge, que es exactamente lo que R-C
 * prohíbe.
 *
 * Los identificadores también nombran filtros y columnas escondibles dentro de
 * la tabla local; por eso son estables aunque cambie el texto visible.
 */
/**
 * El orden de la columna «Inicio», que también tiene que ubicar lo que no
 * arrancó.
 *
 * Dos reglas, en este orden:
 *
 * 1. **Un borrador va primero.** Es lo único de esta tabla que espera a una
 *    persona: no arrancó porque nadie lo mandó todavía. Ordenado por su fecha de
 *    arranque —que es nula— terminaba al final, que es donde nadie mira.
 * 2. **El resto, por cuándo empezó**, y lo que está en cola por cuándo se
 *    encoló. Es el mismo criterio con el que el servidor arma la lista
 *    (`Coalesce('started_at', 'created_at')`): tenerlo distinto de los dos lados
 *    hace que la tabla se reacomode sola al primer clic en un encabezado.
 *
 * Devuelve el orden **ascendente** —la tabla invierte para descendente, que es
 * como arranca esta pantalla—, así que acá los borradores van al final para
 * quedar arriba en lo que se ve.
 */
export function byWhenItStarted(rowA: { original: Batch }, rowB: { original: Batch }): number {
  const draft = waitingForAPerson(rowA.original) - waitingForAPerson(rowB.original)
  if (draft !== 0) return draft

  const a = rowA.original.started_at ?? rowA.original.created_at
  const b = rowB.original.started_at ?? rowB.original.created_at
  // Las dos son fechas ISO en UTC, así que comparar el texto compara el
  // instante y no hace falta construir dos objetos por comparación.
  return a < b ? -1 : a > b ? 1 : 0
}

function waitingForAPerson(batch: Batch): number {
  return batch.state === 'DRAFT' ? 1 : 0
}

function batchColumns(domainId: string): ClientDataTableColumn<Batch>[] {
  return [
    // El identificador alimenta la búsqueda pero ya se muestra abreviado junto
    // al tipo, así que no ocupa una segunda columna visible.
    columnHelper.accessor('id', {
      id: 'batch_id',
      header: t('batches.batchWord'),
    }),
    columnHelper.accessor('kind', {
      id: 'kind',
      header: t('batches.column.kind'),
      meta: { label: t('batches.column.kind') },
      filterFn: 'includesSelected',
      enableGlobalFilter: false,
      cell: (info) => {
        const batch = info.row.original
        return (
          <div className="flex flex-col items-start gap-0.5">
            <Link
              href={batchPath(batch.id)}
              className="focus-visible:ring-ring rounded font-medium underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:outline-none"
            >
              {batchKindLabel(batch.kind)}
            </Link>
            <span className="text-muted-foreground text-xs">
              {batchOriginLabel(batch.origin)} · {t('batches.batchWord')}{' '}
              <span translate="no" className="font-mono">
                {shortBatchName(batch.id)}
              </span>
            </span>
          </div>
        )
      },
    }),
    
    columnHelper.accessor('state', {
      id: 'state',
      header: t('batches.column.state'),
      meta: { label: t('batches.column.state') },
      filterFn: 'includesSelected',
      enableGlobalFilter: false,
      cell: (info) => <RowStateCell batch={info.row.original} domainId={domainId} />,
    }),
    
    columnHelper.accessor('origin', {
      id: 'origin',
      header: t('batches.filter.origin'),
      meta: { label: t('batches.filter.origin') },
      filterFn: 'includesSelected',
      enableGlobalFilter: false,
      cell: (info) => (
        <span className="text-muted-foreground whitespace-nowrap">
          {batchOriginLabel(info.getValue())}
        </span>
      ),
    }),

    columnHelper.accessor('processed_items', {
      id: 'progress',
      header: t('batches.column.progress'),
      meta: { label: t('batches.column.progress') },
      enableGlobalFilter: false,
      cell: (info) => {
        const batch = info.row.original
        return (
          <BatchProgress
            className="min-w-40"
            total={batch.total_items}
            processed={batch.processed_items}
            failed={batch.failed_items}
            state={batch.state}
          />
        )
      },
    }),

    columnHelper.accessor('failed_items', {
      id: 'failed',
      header: t('batches.column.failed'),
      meta: { label: t('batches.column.failed'), numeric: true },
      enableGlobalFilter: false,
      cell: (info) => <span className="tabular-nums whitespace-nowrap">{formatNumber(info.getValue())}</span>,
    }),

    columnHelper.accessor('quota_consumed', {
      id: 'quota',
      header: t('batches.column.quota'),
      meta: { label: t('batches.column.quota'), numeric: true },
      enableGlobalFilter: false,
      cell: (info) => (
        <span className="tabular-nums whitespace-nowrap">{formatNumber(info.getValue())}</span>
      ),
    }),

    columnHelper.accessor('started_at', {
      id: 'started',
      header: t('batches.column.started'),
      meta: { label: t('batches.column.started') },
      enableGlobalFilter: false,
      // Lo que no arrancó se ordena por cuándo se creó, que es el mismo criterio
      // con el que el servidor arma la lista. Sin esto TanStack ordena por la
      // fecha de arranque cruda, que en un borrador es nula, y los manda al
      // final: justo lo que espera a una persona termina donde no se mira.
      sortFn: byWhenItStarted,
      cell: (info) => {
        const batch = info.row.original
        // Un lote que no arrancó no lleva guion: escondería el dato que sí
        // existe, que es cuándo quedó a la espera. Un borrador espera a una
        // persona y uno en cola espera su turno, así que no se dicen igual.
        if (!batch.started_at) {
          return (
            <span className="text-muted-foreground text-sm whitespace-nowrap">
              {batch.state === 'DRAFT' ? t('batches.waitingForYou') : t('batches.queuedSince')}{' '}
              <DataTimestamp value={batch.created_at} className="whitespace-nowrap" />
            </span>
          )
        }
        return <DataTimestamp value={batch.started_at} className="whitespace-nowrap" />
      },
    }),
    columnHelper.accessor('finished_at', {
      id: 'finished',
      header: t('batches.column.finished'),
      meta: { label: t('batches.column.finished') },
      enableGlobalFilter: false,
      cell: (info) => {
        const batch = info.row.original
        if (batch.finished_at) return <DataTimestamp value={batch.finished_at} className="whitespace-nowrap" />
        return (
          <span className="text-muted-foreground text-sm whitespace-nowrap">
            {batch.state === 'RUNNING' ? t('batches.stillRunning') : '—'}
          </span>
        )
      },
    }),
    columnHelper.accessor('duration_seconds', {
      id: 'duration',
      header: t('batches.column.duration'),
      meta: { label: t('batches.column.duration'), numeric: true },
      enableGlobalFilter: false,
      cell: (info) => (
        <span className="tabular-nums whitespace-nowrap">{batchDuration(info.getValue())}</span>
      ),
    }),
  ]
}

export default function BatchesIndex({
  domain,
  batches,
  options,
  active_states,
  pending_urls,
}: Props) {
  const columns = useMemo(() => batchColumns(domain.id), [domain.id])
  const filters = useMemo<ClientDataTableFacetedFilter[]>(
    () => [
      {
        column: 'state',
        title: t('batches.column.state'),
        options: options.state.map((value) => ({ value, label: batchStateLabel(value) })),
        selection: 'multiple',
      },
      {
        column: 'kind',
        title: t('batches.filter.kind'),
        options: options.kind.map((value) => ({ value, label: batchKindLabel(value) })),
        selection: 'multiple',
      },
      {
        column: 'origin',
        title: t('batches.filter.origin'),
        options: options.origin.map((value) => ({ value, label: batchOriginLabel(value) })),
        selection: 'multiple',
      },
    ],
    [options]
  )

  // Único mecanismo de actualización del producto (RT-13): mientras haya un lote
  // sin terminar y la pestaña esté visible, se vuelven a pedir las props que
  // cambian. Corta sola al llegar todos a un estado terminal.
  useBatchPolling({
    states: active_states,
    only: ['batches', 'active_states', 'pending_urls'],
  })

  const announcement = useStateChangeAnnouncement(batches)

  return (
    <AppLayout
      title={t('batches.title', { hostname: domain.hostname })}
      description={t('batches.description')}
      /*
        Sin acción de crear, y es a propósito.

        Había un botón «Crear lote» que llevaba a la cobertura. Prometía algo
        que esta pantalla no puede cumplir: los lotes son de cuatro clases y
        cada una nace de un lugar distinto —la cobertura consulta, los sitemaps
        sincronizan, la exportación se pide desde su diálogo, la indexación
        desde la selección de la tabla—. Un solo botón genérico sugería una
        elección que no existe y aterrizaba en una tabla sin decir qué hacer.

        Esta pantalla es un **registro**: muestra lo que corrió. Lo que arranca
        trabajo vive donde ese trabajo tiene sentido.
      */
    >
      {/*
        El sondeo se anuncia sólo cuando un lote cambia de estado, no en cada
        tic: una región viva que repite lo mismo cada cinco segundos convierte
        la pantalla en ruido continuo para quien usa lector.
      */}
      <p role="status" aria-live="polite" className="sr-only">
        {announcement}
      </p>

      <ClientDataTable
        data={batches}
        columns={columns}
        rowId={(row) => row.id}
        unit={t('batches.unit')}
        unitSingular={t('batches.unitSingular')}
        caption={t('batches.caption', { hostname: domain.hostname })}
        search={{
          label: t('batches.search.label'),
          placeholder: t('batches.search.placeholder'),
        }}
        facetedFilters={filters}
        hiddenColumns={['batch_id']}
        sort={{ column: 'started', descending: true }}
        empty={{
          icon: History,
          title: t('batches.empty.title'),
          description: t('batches.empty.description'),
          action: <FirstBatchAction domain={domain} pending={pending_urls} />,
        }}
      />

      {/*
        La aclaración es fija y no un aviso que se cierra: la confusión que evita
        vuelve cada vez que se mira esta pantalla, así que el texto también.
      */}
      <div className="text-muted-foreground max-w-7xl space-y-2 text-sm">
        <p>{t('batches.footnote.inspect')}</p>
        <p>{t('batches.footnote.quota')}</p>
      </div>

      <TimezoneFootnote />
    </AppLayout>
  )
}

/**
 * El estado, su motivo y —si la hay— la acción que lo destraba.
 *
 * `COMPLETED` y `PARTIAL` ya se distinguen por la silueta del icono de `C-07`,
 * que es lo que los separa en escala de grises. La línea de motivo es la otra
 * mitad: sin ella, «Parcial» obliga a abrir la ficha para saber si se cortó por
 * cuota o porque algo falló, y son dos situaciones con dos desenlaces distintos.
 */
function RowStateCell({ batch, domainId }: { batch: Batch; domainId: string }) {
  const action = batchAction(batch, domainId)
  const reason = batchReasonText(batch)

  return (
    <div className="flex flex-col items-start gap-1.5 whitespace-nowrap">
      <BatchStateBadge state={batch.state} reason={reason} />
      {action ? (
        <Button asChild size="sm" variant="outline">
          <Link href={action.href}>{action.label}</Link>
        </Button>
      ) : null}
    </div>
  )
}

/**
 * Qué ofrecer cuando el dominio nunca ejecutó nada.
 *
 * La acción se **reemplaza** según qué esté faltando, no se deshabilita
 * (RT-18): con la credencial rota o el acceso sin confirmar, inspeccionar
 * fallaría, y un botón que falla siempre es peor que ningún botón. Sin URLs
 * monitoreadas el paso previo es registrar un sitemap, que es de donde salen.
 */
function FirstBatchAction({
  domain,
  pending,
}: {
  domain: Props['domain']
  pending: number
}) {
  const { account } = usePage().props
  const form = useForm({})

  if (!account?.can_operate) {
    return (
      <Button asChild>
        <Link href={route('settings')}>{t('domains.reviewConnection')}</Link>
      </Button>
    )
  }

  if (!domain.is_operational) {
    return (
      <Button asChild>
        <Link href={route('domain.show', { domain_id: domain.id })}>
          {t('batch.action.checkAccess')}
        </Link>
      </Button>
    )
  }

  if (pending === 0) {
    return (
      <Button asChild>
        <Link href={route('sitemaps', { domain_id: domain.id })}>
          {t('batches.registerSitemap')}
        </Link>
      </Button>
    )
  }

  // La acción termina en la tabla de cobertura, no acá: es el servidor el que
  // elige el destino y sólo distingue «desde la ficha del dominio» del resto.
  // Ahí es donde aparecen el lote recién encolado y, si el encolado se rechazó,
  // el error con su código (RT-08), así que esta pantalla no los espera de vuelta.
  return (
    <Button
      onClick={() => form.post(route('coverage.inspect', { domain_id: domain.id }))}
      disabled={form.processing}
      aria-busy={form.processing}
    >
      {form.processing ? t('batches.queueing') : t('batches.inspectNow')}
    </Button>
  )
}

/**
 * Anuncia el cambio de estado de un lote, y sólo eso.
 *
 * Compara los estados contra los de la carga anterior y se calla si no cambió
 * ninguno. Sin esta comparación, cada tic del sondeo volvería a anunciar la
 * tabla entera; con ella, el lector de pantalla dice justo lo que pasó mientras
 * la persona estaba mirando otra cosa.
 */
function useStateChangeAnnouncement(rows: Batch[]): string {
  const previous = useRef(new Map(rows.map((row) => [row.id, row.state])))
  const [announcement, setAnnouncement] = useState('')

  useEffect(() => {
    // Sólo los lotes que ya estaban: uno que aparece al cambiar de página o de
    // filtro no cambió de estado, apenas entró en la vista.
    const changed = rows.filter((row) => {
      const before = previous.current.get(row.id)
      return before !== undefined && before !== row.state
    })

    previous.current = new Map(rows.map((row) => [row.id, row.state]))

    if (changed.length === 0) return

    setAnnouncement(
      changed
        .map(
          (row) =>
            t('batches.announcement', {
              kind: batchKindLabel(row.kind),
              name: shortBatchName(row.id),
              state: batchStateLabel(row.state),
              reason: batchReasonText(row),
            })
        )
        .join(' ')
    )
  }, [rows])

  return announcement
}
