/**
 * earth-now.co
 * Author:  Simon-Pierre Boucher
 * Contact: contact@spboucher.ai
 * File:    apps/web/components/LiveCounter.tsx
 * Purpose: The live counter — rAF animation of a CounterModel with windows, derived transforms, smoothing, honesty guardrails and info popover (display/card/row sizes)
 */

"use client";

import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import {
  formatRate,
  formatUncertainty,
  formatValue,
  rateAt,
  type CounterModel,
  type CounterWindow,
} from "@earth-now/counter";
import type { MetricSummary } from "@/lib/api";
import { displayRawValue, hintsFor, isEstimate } from "@/lib/derived";
import { blendValues, SMOOTHING_DURATION_MS } from "@/lib/smoothing";
import { useI18n } from "@/lib/i18n";
import { formatDateUtc, type MessageKey } from "@/lib/messages";
import FitValue from "./FitValue";

export interface PreviousModel {
  model: CounterModel;
  swapStartMs: number;
}

export interface LiveCounterProps {
  metric: MetricSummary;
  model?: CounterModel | undefined;
  /** Previous model + swap instant — enables the 60 s no-jump smoothing blend. */
  prev?: PreviousModel | undefined;
  window: CounterWindow;
  sessionStart?: number | undefined;
  /** display = the page's hero figure; card = KPI tile; row = compact reference row. */
  size?: "display" | "card" | "row";
  /** Shows the pulsing live indicator (set by the tier system, not hand-picked). */
  live?: boolean;
  /** Disables the link to the metric's own page (used ON that page). */
  noLink?: boolean;
}

interface DisplayState {
  main: string;
  rate: string | null;
  capped: string;
  pending: boolean;
}

const INITIAL_DISPLAY: DisplayState = { main: "—", rate: null, capped: "—", pending: false };

const PERCENT_HINTS = { decimals: 0, unit: "%" } as const;

/** Muted, sober treatment for mortality metrics — editorial rule, no celebratory styling. */
function isSomber(metric: MetricSummary): boolean {
  return metric.id.includes("death");
}

export default function LiveCounter({
  metric,
  model,
  prev,
  window: win,
  sessionStart,
  size = "card",
  live = false,
  noLink = false,
}: LiveCounterProps) {
  const { locale, t } = useI18n();
  const [display, setDisplay] = useState<DisplayState>(INITIAL_DISPLAY);
  const [infoOpen, setInfoOpen] = useState(false);
  const lastFinite = useRef<number | null>(null);

  const hints = hintsFor(metric, locale);
  const isRateOf = metric.derived?.op === "rate-of";
  const showRateLine =
    !isRateOf &&
    (metric.kind === "cumulative" || metric.derived?.op === "window" || size === "display");

  useEffect(() => {
    if (model === undefined) return;
    let raf = 0;
    const localHints = hintsFor(metric, locale);
    const tick = () => {
      const now = Date.now();
      let raw = displayRawValue(metric, model, now, win, sessionStart);
      // 60 s smoothing between model versions — no visible jump (production guardrail).
      if (prev !== undefined && now < prev.swapStartMs + SMOOTHING_DURATION_MS) {
        const oldRaw = displayRawValue(metric, prev.model, now, win, sessionStart);
        if (Number.isFinite(oldRaw) && Number.isFinite(raw)) {
          raw = blendValues(oldRaw, raw, now, prev.swapStartMs, SMOOTHING_DURATION_MS);
        }
      }
      // Never NaN/undefined on screen: freeze the last finite value + "data pending" chip.
      let pending = false;
      if (Number.isFinite(raw)) {
        lastFinite.current = raw;
      } else {
        pending = true;
        raw = lastFinite.current ?? NaN;
      }
      const main = isRateOf
        ? formatRate(raw, localHints, { locale })
        : formatValue(raw, localHints, { locale, applySigFigs: false });
      const capped = isRateOf ? main : formatValue(raw, localHints, { locale });
      const rate = showRateLine
        ? formatRate(rateAt(model, now), localHints, { locale })
        : null;
      setDisplay((d) =>
        d.main === main && d.rate === rate && d.capped === capped && d.pending === pending
          ? d
          : { main, rate, capped, pending },
      );
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [metric, model, prev, win, sessionStart, locale, isRateOf, showRateLine]);

  const somber = isSomber(metric);
  const source = metric.sources[0];
  const windowKey = `window.${win}` as MessageKey;
  const observedAt = model?.observedAt ?? metric.observedAt;
  const modelVersion = model?.modelVersion ?? metric.modelVersion;
  const unitLabel = metric.display.unit[locale];

  const chips = (
    <div className="flex flex-wrap items-center gap-1.5 text-[10px] uppercase tracking-wide">
      <span className="rounded bg-page px-1.5 py-0.5 text-muted">{t(windowKey)}</span>
      {metric.stale && (
        <span className="rounded border border-warn/50 px-1.5 py-0.5 text-ink2">
          ⚠ {t("chip.stale")}
        </span>
      )}
      {display.pending && (
        <span className="rounded border border-warn/50 px-1.5 py-0.5 text-ink2">
          ⚠ {t("chip.pending")}
        </span>
      )}
      {isEstimate(metric) && metric.uncertaintyFraction !== undefined && (
        <span className="rounded bg-wash px-1.5 py-0.5 normal-case text-ink2">
          ± {formatValue(metric.uncertaintyFraction * 100, PERCENT_HINTS, { locale })} % ·{" "}
          {t("chip.estimate")}
        </span>
      )}
    </div>
  );

  const infoButton = (
    <button
      type="button"
      aria-label={t("info.open")}
      onClick={() => setInfoOpen((o) => !o)}
      className="shrink-0 rounded-full px-1.5 text-xs text-muted transition-colors hover:text-ink"
    >
      ⓘ
    </button>
  );

  const infoPanel = infoOpen && (
    <div className="absolute left-2 right-2 top-full z-20 mt-1 rounded-lg border bg-surface p-3 text-xs leading-relaxed text-ink2 shadow-lg">
      <dl className="space-y-1">
        <div>
          <dt className="inline font-medium text-ink">{t("info.cappedValue")} : </dt>
          <dd className="inline tabular-nums">
            {display.capped} {isRateOf ? "" : unitLabel}
          </dd>
        </div>
        {model?.uncertainty !== undefined && (
          <div>
            <dt className="inline font-medium text-ink">{t("info.uncertainty")} : </dt>
            <dd className="inline tabular-nums">
              {formatUncertainty(model.uncertainty.low, model.uncertainty.high, hints, {
                locale,
              })}{" "}
              {unitLabel}
            </dd>
          </div>
        )}
        {metric.uncertaintyFraction !== undefined && (
          <div>
            <dt className="inline font-medium text-ink">{t("info.uncertainty")} : </dt>
            <dd className="inline tabular-nums">
              ± {formatValue(metric.uncertaintyFraction * 100, PERCENT_HINTS, { locale })} % (
              {t("info.estimateNote")})
            </dd>
          </div>
        )}
        {source !== undefined && (
          <div>
            <dt className="inline font-medium text-ink">{t("info.source")} : </dt>
            <dd className="inline">
              <a
                href={source.url}
                target="_blank"
                rel="noreferrer"
                className="text-accent underline decoration-accent/40 hover:opacity-80"
              >
                {source.name}
              </a>{" "}
              · {t("info.license")} : {source.license}
            </dd>
          </div>
        )}
        {observedAt !== undefined && (
          <div>
            <dt className="inline font-medium text-ink">{t("info.observed")} : </dt>
            <dd className="inline">{formatDateUtc(observedAt, locale)}</dd>
          </div>
        )}
        <div>
          <dt className="inline font-medium text-ink">{t("info.model")} : </dt>
          <dd className="inline">
            {metric.model}
            {modelVersion !== undefined && modelVersion !== metric.model
              ? ` (${modelVersion})`
              : ""}
          </dd>
        </div>
        {metric.editorialNote !== undefined && (
          <div className="pt-1 text-muted">{metric.editorialNote[locale]}</div>
        )}
      </dl>
      <button
        type="button"
        onClick={() => setInfoOpen(false)}
        className="mt-2 text-[10px] uppercase tracking-wide text-muted hover:text-ink"
      >
        {t("info.close")}
      </button>
    </div>
  );

  if (size === "row") {
    // Compact reference row: slow-moving values, no big animation, dense layout.
    return (
      <div className="relative flex min-w-0 items-center gap-3 px-4 py-2.5 transition-colors hover:bg-page/60">
        {noLink ? (
          <span className={`min-w-0 flex-1 truncate text-sm ${somber ? "text-muted" : "text-ink2"}`}>
            {metric.name[locale]}
          </span>
        ) : (
          <Link
            href={`/metric/${metric.id}`}
            className={`min-w-0 flex-1 truncate text-sm underline-offset-2 transition-colors hover:text-accent hover:underline ${somber ? "text-muted" : "text-ink2"}`}
          >
            {metric.name[locale]}
          </Link>
        )}
        <span className="shrink-0 text-sm font-medium text-ink tabular-nums">
          {display.main}
          <span className="ml-1 font-normal text-muted">{isRateOf ? "" : unitLabel}</span>
        </span>
        {observedAt !== undefined && (
          <span className="hidden shrink-0 text-xs text-muted lg:block">
            {formatDateUtc(observedAt, locale)}
          </span>
        )}
        {(metric.stale || display.pending) && (
          <span className="shrink-0 text-[10px] uppercase tracking-wide text-ink2">
            ⚠ {t(metric.stale ? "chip.stale" : "chip.pending")}
          </span>
        )}
        {infoButton}
        {infoPanel}
      </div>
    );
  }

  const isDisplay = size === "display";

  return (
    <div
      className={`group relative flex min-w-0 flex-col gap-1 rounded-2xl border bg-surface transition-all duration-200 hover:-translate-y-0.5 hover:shadow-lg ${
        isDisplay ? "p-6 sm:p-8" : "p-4"
      }`}
    >
      <div className="flex items-start justify-between gap-2">
        <h3
          className={`flex min-w-0 items-center gap-2 font-medium leading-snug ${
            somber ? "text-muted" : "text-ink2"
          } ${isDisplay ? "text-base" : "text-sm"}`}
        >
          {live && !somber && <span aria-hidden className="live-dot shrink-0" />}
          {noLink ? (
            <span className="min-w-0">{metric.name[locale]}</span>
          ) : (
            <Link
              href={`/metric/${metric.id}`}
              className="min-w-0 underline-offset-2 transition-colors hover:text-accent hover:underline"
            >
              {metric.name[locale]}
            </Link>
          )}
        </h3>
        {infoButton}
      </div>

      {/* Value line carries DIGITS ONLY (unit moves below) and is scale-fitted:
          it can never overflow the tile, on any viewport. */}
      <FitValue
        text={display.main}
        className={`tabular-nums font-semibold tracking-tight text-ink ${
          isDisplay ? "text-5xl leading-tight sm:text-7xl" : "text-2xl leading-snug sm:text-3xl"
        }`}
      />

      <p className={`text-muted ${isDisplay ? "text-base" : "text-xs"}`}>
        {isRateOf ? "" : unitLabel}
        {display.rate !== null && (
          <span className="tabular-nums">
            {isRateOf ? "" : " · "}
            {t("rate.current")} : {display.rate}
          </span>
        )}
      </p>

      <div className="mt-1">{chips}</div>
      {infoPanel}
    </div>
  );
}
