import { Link, usePage } from '@inertiajs/react'
import { CircleCheck, Clock, FileText } from 'lucide-react'
import type { ReactNode } from 'react'

import { AccessStateBadge, accessStateTone } from '@/components/AccessStateBadge'
import { AttentionDot } from '@/components/AttentionDot'
import { BatchStateBadge } from '@/components/BatchStateBadge'
import type { Activity } from '@/components/chart-area-interactive'
import { MetricCard } from '@/components/MetricCard'
import { Section } from '@/components/Section'
import { StatusBadge } from '@/components/StatusBadge'
import { Button } from '@/components/ui/button'
import { formatNumber } from '@/lib/format'
import { locale, t } from '@/lib/i18n'
import { route } from '@/lib/routes'

/**
 * Los cinco aspectos del tablero (V0), en tres grupos con nombre.
 *
 * Cada tarjeta contesta una pregunta con una cifra, su denominador y **una**
 * salida hacia el detalle. **No hay gráfico ni listas en esta pantalla**: son
 * detalle, y el detalle es bajo demanda. El gráfico y los últimos lotes no se
 * borraron del producto: viven en el panel de «Actividad».
 *
 * **Los grupos son por la pregunta que contestan, no por parecido visual.** Una
 * fila de cinco tarjetas iguales una al lado de la otra no dice en qué se
 * diferencian, y el ojo termina leyendo cinco veces lo mismo para descubrirlo:
 *
 * - **Tus sitios** — qué hay y si se puede leer. Es inventario.
 * - **Lo que sabemos** — la cobertura, que es la pregunta de la vista y por eso
 *   está sola en su grupo.
 * - **Hoy** — lo que se movió y lo que queda por moverse en el día de hoy. Las
 *   dos cifras se renuevan cada mañana; las de arriba, no.
 *
 * **No hay tarjeta de avisos.** El mismo aviso estaría en el contador de la
 * barra lateral, en el bloque de atención de esta misma pantalla y en una
 * tarjeta: tres lugares para el mismo dato es justo lo que este rediseño vino a
 * arreglar. El contador del menú ya lleva al recorte que nombra.
 *
 * Tres aspectos abren un panel y uno navega, y la línea que los separa es si lo
 * que hay abajo ya es una pantalla. Dominios tiene la suya, así que duplicarla
 * en un panel sería mantener dos diseños de lo mismo. Cobertura, cupo,
 * actividad y sitemaps son agregados de cuenta que hoy sólo existen por
 * dominio: no hay adónde navegar, y por eso abren panel.
 *
 * **Ninguna tarjeta lleva una flecha de tendencia.** El demo traía «+12,5 %» con
 * su icono en las cuatro, y una variación porcentual sobre una muestra parcial
 * del sitio es la restricción de honestidad dibujada con un dibujito: sugiere
 * una serie histórica comparable donde sólo hay lo que se alcanzó a consultar.
 * El espacio del denominador se usa para el dato que mantiene honesta a la cifra
 * grande —cuántas URLs siguen sin consultar, cuánto cupo queda, en qué estado
 * cerró el ciclo—, que es información y no pronóstico.
 *
 * Las cinco cifras van con su denominador y con su fecha (R-A). «1.240 URLs con
 * dato» no significa nada sin el total ni sin decir de cuándo es.
 */

export interface DomainsSummary {
  total: number
  /** Cuántos tienen la propiedad autorizada. No alcanza para decir que funcionan (RT-18). */
  operational: number
  /** El id cuando hay un solo dominio. Con dos o más es nulo: no se adivina cuál importa. */
  only_id: string | null
  checked_at: string | null
}

export interface CoverageDomain {
  id: string
  hostname: string
  total: number
  with_data: number
  without_data: number
  indexed: number
  last_checked_at: string | null
}

export interface CoverageOverview {
  total: number
  with_data: number
  /** `UNKNOWN`: todavía no le preguntamos a Google. Fuera de todo porcentaje (R-B). */
  without_data: number
  indexed: number
  /** Reparto de las que tienen dato, por estado. Sin `UNKNOWN`, que no es un estado informado. */
  by_state: Record<string, number>
  /**
   * Cuáles son, por estado. Es lo que permite abrir la cifra donde se la lee.
   *
   * Viene **vacío salvo con el panel de cobertura abierto**, y sin los estados
   * «indexada» y «sin consultar»: mandar el inventario entero en cada carga de
   * la pantalla más visitada del producto es peso por una lista que casi nunca
   * se despliega. Las listas están recortadas; `by_state` tiene el total.
   */
  urls_by_state: Record<string, string[]>
  domains: CoverageDomain[]
  domains_with_urls: number
  /** Dominios que ya no aportan datos nuevos: su parte del total está congelada (R-F). */
  frozen_domains: number
  last_checked_at: string | null
}

export interface QuotaDomain {
  id: string
  hostname: string
  limit_total: number
  manual_reserve: number
  used_automatic: number
  used_manual: number
}

export interface TodayQuota {
  /** Día de calendario en `AAAA-MM-DD`, en la zona de presentación. */
  date: string
  limit_total: number
  manual_reserve: number
  used: number
  used_automatic: number
  used_manual: number
  /** Un renglón por propiedad con presupuesto de hoy: el cupo es de cada una, no común. */
  domains: QuotaDomain[]
}

export interface SitemapDomain {
  id: string
  hostname: string
  total: number
  last_read_at: string | null
}

export interface SitemapsSummary {
  total: number
  domains_with_sitemaps: number
  domains: SitemapDomain[]
  last_read_at: string | null
}

export interface LastCycle {
  id: string
  hostname: string
  state: string
  processed_items: number
  total_items: number
  finished_at: string | null
}

/**
 * Todo lo que el tablero abre en un panel, por su nombre en la dirección.
 *
 * Los cuatro aspectos que no tienen pantalla propia, más la lista de lo que
 * necesita atención cuando hay más de una cosa. Es una sola lista porque es un
 * solo parámetro: `?panel=` no puede tener dos valores a la vez, y tener dos
 * juegos de nombres sería tener dos formas de abrir lo mismo.
 */
export const PANEL_KEYS = ['attention', 'coverage', 'quota', 'activity', 'sitemaps'] as const

export type PanelKey = (typeof PANEL_KEYS)[number]

interface Props {
  domains: DomainsSummary
  coverage: CoverageOverview
  quota: TodayQuota
  sitemaps: SitemapsSummary
  activity: Activity
  lastCycle: LastCycle | null
  /*
    `needsAction` salió de acá: era «cuántos dominios piden algo», y con un solo
    sitio la respuesta es cero o uno. Eso ya lo dice el estado del sitio, con su
    palabra y su color, en vez de con una cifra que hay que interpretar.
  */
  /** La dirección que abre un panel, conservando lo demás que haya puesto. */
  panelHref: (panel: PanelKey) => string
}

export function SectionCards({
  domains,
  coverage,
  quota,
  sitemaps,
  activity,
  lastCycle,
  panelHref,
}: Props) {
  const { account, site } = usePage().props
  const canOperate = account?.can_operate ?? false

  const remaining = Math.max(quota.limit_total - quota.used, 0)
  const hasQuota = quota.limit_total > 0
  const withoutSitemap = Math.max(domains.total - sitemaps.domains_with_sitemaps, 0)

  return (
    /*
      Un fragmento y no un contenedor: los tres grupos son hijos directos del
      `<main>` del armazón, así que el ritmo de 24 px entre ellos ya está puesto.
      Envolverlos en un `div` con su propio `gap` es exactamente lo que hace que
      dos vistas respiren distinto.
    */
    <>
      <Section title={{ text: t('dashboard.group.sites') }}>
        <div className={CARD_ROW}>
          {/*
            La primera tarjeta dejó de contar sitios y pasó a decir **cómo está
            el sitio**. Contarlos tenía sentido cuando podían ser varios; con uno
            solo, un «1» permanente ocupa el lugar de la única pregunta que esa
            tarjeta puede contestar de un vistazo —¿está todo bien con mi sitio?—
            y la deja sin contestar.

            Son dos formas: sin sitio conectado y con sitio, y la segunda cambia
            de color con lo que le pase.
          */}
          <MetricCard
            label={t('dashboard.site.label')}
            value={
              site ? (
                // A 18 px y no a 24: un hostname largo a tamaño de cifra parte
                // la tarjeta en tres renglones. `break-all` porque un dominio no
                // tiene espacios donde cortar.
                <span className="text-lg! font-medium break-all" translate="no">
                  {site.hostname}
                </span>
              ) : (
                '—'
              )
            }
            denominator={
              /*
                El punto de atención va en el slot de acciones del encabezado, que
                es donde se lo busca de un vistazo. **No dice el estado por sí
                solo** (RT-04): la palabra está abajo, en la nota, y el punto es
                el refuerzo que se ve sin leer.

                Sin credencial que sirva el sitio no se presenta como operativo
                aunque su propia columna lo diga (RT-18), y de eso ya se encarga
                `accessStateTone`: el punto y la palabra salen del mismo criterio.
              */
              <AttentionDot
                tone={site ? accessStateTone(site.access_state, canOperate) : 'neutral'}
              />
            }
            note={
              site ? (
                <AccessStateBadge state={site.access_state} canOperate={canOperate} />
              ) : (
                t('dashboard.site.notConnected')
              )
            }
            fetchedAt={domains.checked_at}
            action={
              /*
                Lleva al sitio, no a un listado. La interfaz trabaja contra uno
                solo, así que «ver los sitios» ofrecería una elección que no
                existe; sin sitio conectado, lo que sigue es conectarlo, y eso
                se hace en la conexión.
              */
              <GoLink
                href={
                  site ? route('domain.show', { domain_id: site.id }) : route('settings')
                }
              >
                {site ? t('dashboard.domains.action') : t('dashboard.domains.connect')}
              </GoLink>
            }
          />

          <MetricCard
            label={t('dashboard.sitemaps.label')}
            value={formatNumber(sitemaps.total)}
            denominator={
              withoutSitemap > 0 ? (
                <StatusBadge tone="attention" icon={FileText}>
                  {t('dashboard.sitemaps.without', {
                    count: withoutSitemap,
                    total: formatNumber(withoutSitemap),
                  })}
                </StatusBadge>
              ) : (
                <StatusBadge tone="positive" icon={CircleCheck}>
                  {t('dashboard.sitemaps.allWith')}
                </StatusBadge>
              )
            }
            // Una línea y no dos. La segunda mitad —«sin uno registrado no hay
            // nada que consultar»— vive en el panel, en el renglón del dominio
            // que no tiene ninguno, que es donde significa algo; acá sólo hacía
            // más alta la fila entera, porque el alto de una fila lo pone su
            // tarjeta más larga.
            note={t('dashboard.sitemaps.note')}
            fetchedAt={sitemaps.last_read_at}
            action={
              <PanelLink href={panelHref('sitemaps')}>{t('dashboard.sitemaps.action')}</PanelLink>
            }
          />
        </div>
      </Section>

      <Section title={{ text: t('dashboard.group.known') }}>
        {/*
          Sola en su grupo y a lo ancho: es la pregunta de la vista, y el grupo
          se llama por ella. La misma grilla que las otras dos la dejaría con
          media fila vacía al lado, que se lee como una tarjeta que falta.
        */}
        <MetricCard
          label={t('dashboard.coverage.label')}
          value={coverageValue(coverage)}
          denominator={
            coverage.without_data > 0 ? (
              <StatusBadge tone="unknown" icon={Clock} help={t('dashboard.coverage.unknownHelp')}>
                {t('dashboard.coverage.unknown', {
                  total: formatNumber(coverage.without_data),
                })}
              </StatusBadge>
            ) : undefined
          }
          note={coverageNote(coverage)}
          fetchedAt={coverage.last_checked_at}
          action={
            /*
              El rótulo dice a qué se entra y ya no cuántos dominios hay
              adentro. «¿En qué dominio?» era la pregunta que seguía a la cifra
              cuando la cuenta podía tener varios; con un solo sitio el conteo
              sería siempre uno y anunciaría una elección que no existe. El panel
              sigue abriendo el reparto por estado, que es lo que se va a mirar.
            */
            <PanelLink href={panelHref('coverage')}>{t('dashboard.coverage.action')}</PanelLink>
          }
        />
      </Section>

      <Section title={{ text: t('dashboard.group.today') }}>
        <div className={CARD_ROW}>
          <MetricCard
            label={t('dashboard.quota.label')}
            value={
              hasQuota
                ? t('dashboard.quota.value', {
                    used: formatNumber(quota.used),
                    limit: formatNumber(quota.limit_total),
                  })
                : '—'
            }
            denominator={
              hasQuota ? t('dashboard.quota.remaining', { total: formatNumber(remaining) }) : undefined
            }
            note={quotaNote(quota, hasQuota)}
            action={<PanelLink href={panelHref('quota')}>{t('dashboard.quota.action')}</PanelLink>}
          />

          <MetricCard
            label={t('dashboard.batches.label')}
            value={formatNumber(activity.batches_today)}
            note={cycleNote(lastCycle)}
            fetchedAt={lastCycle ? lastCycle.finished_at : undefined}
            action={
              <PanelLink href={panelHref('activity')}>{t('dashboard.batches.action')}</PanelLink>
            }
          />
        </div>
      </Section>
    </>
  )
}

/**
 * La fila de tarjetas de un grupo.
 *
 * Se escribe una vez para los dos grupos que tienen dos: si cada uno declarara
 * la suya, alcanzaría con que una diga `md:grid-cols-3` para que las tarjetas de
 * arriba y las de abajo dejen de medir lo mismo.
 */
const CARD_ROW = 'grid grid-cols-1 gap-4 md:grid-cols-2'

/**
 * La salida de una tarjeta hacia su detalle.
 *
 * Es un enlace de texto y no un botón: la pantalla pide **una** acción o
 * ninguna, y cinco botones rellenos compitiendo entre sí dejarían el tablero sin
 * ningún primero. El nombre de cada uno es distinto —«Ver el reparto», «Ver los
 * dos bolsillos»— porque cinco controles llamados «Ver» obligan a recorrer la
 * pantalla para saber cuál es cuál.
 */
function PanelLink({ href, children }: { href: string; children: ReactNode }) {
  return (
    <Button asChild variant="link" size="sm">
      {/* Abrir un panel no recarga la pantalla: lo único que cambia es la
          dirección, que es donde vive lo que está abierto. */}
      <Link href={href} preserveScroll preserveState>
        {children}
      </Link>
    </Button>
  )
}

/** La salida de una tarjeta hacia otra pantalla, que sí es una navegación. */
function GoLink({ href, children }: { href: string; children: ReactNode }) {
  return (
    <Button asChild variant="link" size="sm">
      <Link href={href}>{children}</Link>
    </Button>
  )
}

/**
 * Cuántas quedaron indexadas, sobre todas las que conocemos.
 *
 * **Sin ninguna consulta hecha no hay cifra, y se dice con una raya.** «0 de
 * 120» sería afirmar que Google no indexó ninguna, cuando lo único cierto es que
 * todavía no le preguntamos por ninguna: es la confusión que este producto
 * existe para no cometer (R-B). La raya no es un dato faltante disimulado —el
 * badge de al lado dice cuántas están sin consultar y la bajada dice por qué no
 * hay número—, es la única respuesta honesta.
 */
export function coverageValue(coverage: CoverageOverview): string {
  if (coverage.with_data === 0) return '—'
  return t('dashboard.coverage.value', {
    indexed: formatNumber(coverage.indexed),
    total: formatNumber(coverage.total),
  })
}

/**
 * La mitad del resto que **no** dice el badge.
 *
 * Con «62 de 120» adelante, lo peligroso es que las 58 que faltan se lean todas
 * como malas. No lo son: son dos cosas distintas y cada una se nombra una vez
 * —las que Google miró y no indexó, acá; las que todavía no consultamos, en el
 * badge de al lado, con su reloj y su familia visual propia (RT-03)—. Sumadas
 * con la cifra grande cierran el total, y ningún número se escribe dos veces.
 *
 * La cifra grande no es un porcentaje a propósito: un porcentaje de indexación
 * calculado sobre lo que se alcanzó a consultar es la afirmación falsa más fácil
 * de cometer en un tablero, donde los números se leen sueltos y grandes.
 */
export function coverageNote(coverage: CoverageOverview): string {
  if (coverage.with_data === 0) return t('dashboard.coverage.note.none')

  const notIndexed = Math.max(coverage.with_data - coverage.indexed, 0)

  if (notIndexed === 0) {
    return t('dashboard.coverage.note.allIndexed', {
      count: coverage.with_data,
      total: formatNumber(coverage.with_data),
    })
  }

  return t('dashboard.coverage.note.notIndexed', {
    count: notIndexed,
    total: formatNumber(notIndexed),
  })
}

/**
 * El cupo mide cuánto le consultamos a Google, no cuánto del sitio está
 * cubierto: confundir las dos cosas convierte un cupo lleno en una promesa de
 * cobertura completa. Y va con su día, porque un saldo sin fecha se lee como
 * permanente cuando en realidad se renueva cada mañana.
 */
export function quotaNote(quota: TodayQuota, hasQuota: boolean): string {
  if (!hasQuota) return t('dashboard.quota.note.pending', { day: dayLabel(quota.date) })

  return t('dashboard.quota.note', {
    automatic: formatNumber(quota.used_automatic),
    manual: formatNumber(quota.used_manual),
    day: dayLabel(quota.date),
  })
}

/**
 * Lo último que produjo datos, con lo que alcanzó a hacer.
 *
 * El estado del ciclo va en el denominador con `BatchStateBadge`, que distingue
 * `PARTIAL` de `COMPLETED` por silueta y no por color: un ciclo cortado por cupo
 * dejó URLs sin consultar, y verse igual que uno completo haría leer una foto
 * parcial del sitio como si fuera el sitio entero (R-C).
 */
/**
 * El estado del último ciclo viaja con la frase del último ciclo, no al lado de
 * la cifra de hoy.
 *
 * Puesto arriba, en la ranura del denominador, el badge se lee como si
 * calificara al conteo de hoy: «0 lotes» con un «Parcial» al lado dice que ese
 * cero quedó a medias. Y el ciclo que quedó parcial puede ser de otro día, así
 * que el estado le pertenece a la oración que lo nombra.
 */
function cycleNote(lastCycle: LastCycle | null): ReactNode {
  if (!lastCycle) return t('dashboard.cycle.none')

  return (
    <span className="flex flex-wrap items-center gap-x-1.5 gap-y-1">
      <span>
        {t('dashboard.cycle.note', {
          processed: formatNumber(lastCycle.processed_items),
          total: formatNumber(lastCycle.total_items),
          hostname: lastCycle.hostname,
        })}
      </span>
      <BatchStateBadge state={lastCycle.state} />
    </span>
  )
}

/**
 * El día del cupo es una fecha de calendario, no un instante.
 *
 * Por eso no pasa por `C-08 DataTimestamp`: ese componente convierte un instante
 * a la zona de presentación, y aplicarle esa conversión a «2026-08-18» lo
 * correría un día entero hacia atrás. Un cupo mostrado con la fecha del día
 * anterior no explica ninguna cifra.
 */
export function dayLabel(date: string): string {
  const [year, month, day] = date.split('-').map(Number)
  if (!year || !month || !day) return date

  return new Intl.DateTimeFormat(locale(), {
    day: 'numeric',
    month: 'short',
    year: 'numeric',
    timeZone: 'UTC',
  }).format(new Date(Date.UTC(year, month - 1, day)))
}
