import { Clock, TriangleAlert } from 'lucide-react'
import { useId } from 'react'

import { coverageStateLabel } from '@/components/CoverageStateBadge'
import { DataTimestamp } from '@/components/DataTimestamp'
import { RichText } from '@/components/RichText'
import { formatNumber } from '@/lib/format'
import { t } from '@/lib/i18n'
import { cn } from '@/lib/utils'

/** Tal como lo devuelve `coverage_summary()` en el servidor. */
export interface CoverageSummaryData {
  /** URLs con al menos una respuesta de Google. Es el **único** denominador válido. */
  with_data: number
  /** URLs en `UNKNOWN`. Nunca entran en un porcentaje ni en una barra apilada. */
  without_data: number
  indexed: number
  /** Nulo cuando no hay ninguna URL con dato: un cero se leería como «ninguna indexada». */
  indexed_percentage: number | null
  /** Conteo por estado, sin `UNKNOWN`. */
  by_state: Record<string, number>
  last_checked_at: string | null
}

export interface DateWindow {
  start: string | null
  end: string | null
}

interface Props {
  summary: CoverageSummaryData
  /** URLs conocidas del dominio: con dato **más** sin consultar. */
  total: number
  fullCycleEstimateDays: number | null
  dateWindow?: DateWindow | null
  /** Consultas por día del ciclo automático, para redondear la estimación. */
  queriesPerDay?: number | null
  className?: string
}

/**
 * Resumen de cobertura (C-15). Es donde más fácil se miente.
 *
 * Dos reglas gobiernan todo lo de abajo:
 *
 * 1. **El denominador nunca incluye lo que no se consultó.** Un «62 % indexado»
 *    calculado sobre el total de URLs cuando sólo se consultó una quinta parte
 *    del sitio no es un redondeo optimista: es una afirmación falsa sobre las
 *    URLs de las que no sabemos nada (R-B). Por eso todo porcentaje va sobre
 *    `with_data` y lo dice al lado, y por eso `UNKNOWN` queda fuera de la barra.
 * 2. **Ningún conteo de estados sin su fecha de obtención** (R-A). Un reparto
 *    por estado sin decir de cuándo es podría ser de hoy o de hace tres meses.
 *
 * Los dos bloques —«con dato» y «sin consultar todavía»— están separados
 * físicamente, cada uno con su familia visual (RT-03). Mezclarlos en un solo
 * gráfico es la forma más directa de convertir «no sabemos» en «no indexada».
 */
export function CoverageSummary({
  summary,
  total,
  fullCycleEstimateDays,
  dateWindow,
  queriesPerDay,
  className,
}: Props) {
  const withDataId = useId()
  const withoutDataId = useId()

  const withData = Math.max(summary.with_data, 0)
  // El total llega aparte del resumen. Si no coincidiera con la suma, manda la
  // suma: la línea de honestidad tiene que cerrar la cuenta a la vista.
  const safeTotal = Math.max(total, withData + Math.max(summary.without_data, 0))
  const notQueried = Math.max(safeTotal - withData, 0)

  const indexed = Math.min(Math.max(summary.indexed, 0), withData)
  const otherWithData = Math.max(withData - indexed, 0)

  const to = dateWindow?.end ?? summary.last_checked_at
  const from = dateWindow?.start ?? null
  const missingDate = withData > 0 && !to

  const rows = Object.entries(summary.by_state ?? {})
    // Última línea de defensa de R-B: aunque el servidor lo mandara, `UNKNOWN`
    // no se cuenta acá adentro. Este bloque es «lo que Google nos dijo», y de
    // estas URLs Google no dijo nada.
    .filter(([state, count]) => state !== 'UNKNOWN' && count > 0)
    .sort((a, b) => b[1] - a[1])

  const estimate = estimateText(safeTotal, fullCycleEstimateDays, queriesPerDay)

  if (safeTotal === 0) {
    return (
      <section aria-label={t('coverageSummary.label')} className={cn('flex flex-col gap-2', className)}>
        <h2 className="text-xl font-medium text-balance">{t('coverageSummary.noUrls')}</h2>
        <p className="text-muted-foreground text-sm text-pretty">
          {t('coverageSummary.noUrls.note')}
        </p>
      </section>
    )
  }

  return (
    <section aria-label={t('coverageSummary.label')} className={cn('flex flex-col gap-4', className)}>
      {/* Lo primero que se lee es el denominador. Si eso queda claro, el resto
          del tablero se interpreta bien solo. Encabezado de verdad y no un
          párrafo grande: es el título del contenido de la vista, y de él
          cuelgan los dos bloques de abajo. */}
      <div className="flex flex-col gap-1">
        <h2 className="text-xl font-medium text-balance tabular-nums">
          {coverageHeadline(withData, notQueried, safeTotal)}
        </h2>

        {/*
          La otra mitad de la línea de honestidad: desde cuándo. «1.240 de 5.000
          tienen dato» sin la ventana de fechas no dice si ese dato es de esta
          mañana o de un recorrido que arrancó hace tres semanas, así que las dos
          frases van juntas y no una arriba y otra dentro de una tarjeta.

          R-A: si falta la fecha se muestra el defecto en vez de disimularlo. Un
          reparto por estado sin fecha no es un dato, es una impresión.
        */}
        {missingDate ? (
          <p className="text-destructive flex items-start gap-2 text-sm text-pretty">
            <TriangleAlert className="mt-0.5 size-4 shrink-0" aria-hidden />
            {t('coverageSummary.missingDate')}
          </p>
        ) : to ? (
          <p className="text-muted-foreground text-sm text-pretty">
            {from && from !== to ? (
              <>
                {t('coverageSummary.window.from')} <DataTimestamp value={from} />{' '}
                {t('coverageSummary.window.to')} <DataTimestamp value={to} />.
              </>
            ) : (
              <>
                {t('coverageSummary.window.last')} <DataTimestamp value={to} />.
              </>
            )}
          </p>
        ) : null}

        {estimate ? (
          <p className="text-muted-foreground text-sm text-pretty">
            {estimate} {t('coverageSummary.estimate.caveat')}
          </p>
        ) : null}
      </div>

      <div className="grid gap-4 md:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]">
        <section aria-labelledby={withDataId} className="flex flex-col gap-3 rounded-lg border p-4">
          <div className="flex flex-col gap-1">
            <h3 id={withDataId} className="text-sm font-medium">
              {t('coverageSummary.withData.title')}
            </h3>
            <p className="text-2xl font-semibold tabular-nums">{formatNumber(withData)}</p>
            <p className="text-muted-foreground text-sm">
              {t('coverageSummary.urls', { count: withData })}
            </p>
          </div>

          {withData === 0 ? (
            <p className="text-muted-foreground text-sm text-pretty">
              {t('coverageSummary.withData.none')}
            </p>
          ) : (
            <>
              {summary.indexed_percentage !== null ? (
                <p className="text-sm text-pretty">
                  <RichText
                    text={t('coverageSummary.indexedShare', {
                      percentage: formatNumber(summary.indexed_percentage),
                      total: formatNumber(withData),
                    })}
                  />
                </p>
              ) : null}

              {/* Barra apilada sólo sobre las que tienen dato. Es gráfico: la
                  tabla de abajo dice lo mismo en texto. */}
              <div
                className="bg-muted flex h-2 w-full overflow-hidden rounded-full"
                aria-hidden
              >
                <span
                  className="bg-emerald-600/80"
                  style={{ width: `${(indexed / withData) * 100}%` }}
                />
                <span
                  className="bg-amber-600/70"
                  style={{ width: `${(otherWithData / withData) * 100}%` }}
                />
              </div>
              <p className="text-muted-foreground flex flex-wrap gap-x-4 gap-y-1 text-xs tabular-nums">
                <span className="flex items-center gap-1.5">
                  <span className="size-2 shrink-0 rounded-full bg-emerald-600/80" aria-hidden />
                  {t('coverageSummary.legend.indexed', { total: formatNumber(indexed) })}
                </span>
                <span className="flex items-center gap-1.5">
                  <span className="size-2 shrink-0 rounded-full bg-amber-600/70" aria-hidden />
                  {t('coverageSummary.legend.notIndexed', {
                    total: formatNumber(otherWithData),
                  })}
                </span>
              </p>

              {rows.length > 0 ? (
                <table className="w-full text-sm">
                  <caption className="sr-only">
                    {t('coverageSummary.table.caption', {
                      withData: formatNumber(withData),
                      notQueried: formatNumber(notQueried),
                    })}
                  </caption>
                  <thead>
                    <tr className="text-muted-foreground text-xs">
                      <th scope="col" className="py-1 text-left font-medium">
                        {t('coverageSummary.table.state')}
                      </th>
                      <th scope="col" className="py-1 text-right font-medium">
                        {t('coverageSummary.table.urls')}
                      </th>
                      <th scope="col" className="py-1 text-right font-medium whitespace-nowrap">
                        {t('coverageSummary.table.share', { total: formatNumber(withData) })}
                      </th>
                    </tr>
                  </thead>
                  <tbody>
                    {rows.map(([state, count]) => (
                      <tr key={state} className="border-border/60 border-t">
                        <td className="py-1 pr-2">{coverageStateLabel(state)}</td>
                        <td className="py-1 text-right tabular-nums">{formatNumber(count)}</td>
                        <td className="py-1 text-right tabular-nums">
                          {formatNumber(percentage(count, withData))}&nbsp;%
                        </td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              ) : null}
            </>
          )}
        </section>

        {/* Familia visual propia: borde punteado, fondo neutro y reloj, igual
            que el badge de `UNKNOWN` (RT-03). Ni verde, ni rojo, ni ámbar: no
            es un resultado, es una pregunta que todavía no hicimos. */}
        <section
          aria-labelledby={withoutDataId}
          className="border-muted-foreground/40 flex h-fit flex-col gap-1 rounded-lg border border-dashed p-4"
        >
          <h3
            id={withoutDataId}
            className="text-muted-foreground flex items-center gap-2 text-sm font-medium"
          >
            <Clock className="size-4 shrink-0" aria-hidden />
            {t('coverage.UNKNOWN.label')}
          </h3>
          <p className="text-2xl font-semibold tabular-nums">{formatNumber(notQueried)}</p>
          <p className="text-muted-foreground text-sm">
            {t('coverageSummary.urls', { count: notQueried })}
          </p>
          <p className="text-muted-foreground mt-1 text-sm text-pretty">
            {notQueried === 0
              ? t('coverageSummary.withoutData.none')
              : t('dashboard.coverage.unknownHelp')}
          </p>
        </section>
      </div>
    </section>
  )
}

function coverageHeadline(withData: number, notQueried: number, total: number): string {
  if (withData === 0) {
    return t('coverageSummary.headline.none', { count: total, total: formatNumber(total) })
  }
  if (notQueried === 0) {
    return t('coverageSummary.headline.all', { count: total, total: formatNumber(total) })
  }
  return t('coverageSummary.headline.partial', {
    count: total,
    withData: formatNumber(withData),
    total: formatNumber(total),
    notQueried: formatNumber(notQueried),
  })
}

/**
 * La estimación describe **nuestra** capacidad de consulta, no una predicción
 * sobre Google. De ahí que diga «recorrer» y nunca cuándo va a rastrear o
 * indexar algo (RT-05).
 */
function estimateText(
  total: number,
  days: number | null,
  queriesPerDay: number | null | undefined
): string | null {
  if (!days || days <= 0) return null

  // Con cupo conocido y sin él son dos oraciones distintas y no una con un
  // pedazo opcional: en inglés el inciso va en otro lugar de la frase.
  return queriesPerDay
    ? t('coverageSummary.estimate.withQuota', {
        count: days,
        days: formatNumber(days),
        quota: formatNumber(queriesPerDay),
        total: formatNumber(total),
      })
    : t('coverageSummary.estimate', {
        count: days,
        days: formatNumber(days),
        total: formatNumber(total),
      })
}

function percentage(part: number, total: number): number {
  if (!total) return 0
  return Math.round((part / total) * 1000) / 10
}
