/**
 * earth-now.co
 * Author:  Simon-Pierre Boucher
 * Contact: contact@spboucher.ai
 * File:    apps/web/components/Dashboard.tsx
 * Purpose: Client dashboard orchestrator — live-first layout (hero, live grid by tick speed, session strip, country ranking, realtime, reference strip)
 */

"use client";

import { useEffect, useMemo, useState } from "react";
import { formatValue, type CounterModel } from "@earth-now/counter";
import {
  fetchQuakes,
  sseStreamUrl,
  type Locale,
  type MetricSummary,
  type QuakesResponse,
} from "@/lib/api";
import { resolveModel, resolvePrimaryWindow } from "@/lib/derived";
import { I18nProvider, useI18n } from "@/lib/i18n";
import { formatDateUtc, isLocale } from "@/lib/messages";
import { CONTINENT_PREFIX, HERO_METRIC_ID, liveSortKey, tierFor, type DisplayTier } from "@/lib/tiers";
import CountryRanking from "./CountryRanking";
import YearProgress from "./YearProgress";
import LiveCounter, { type PreviousModel } from "./LiveCounter";

const LOCALE_STORAGE_KEY = "earth-now.locale";
const THEME_STORAGE_KEY = "earth-now.theme";

type Theme = "light" | "dark";

/** Catalog §14 vedette absolue — the "since you arrived" metrics, session window. */
const SESSION_METRIC_IDS = [
  "births_ytd",
  "deaths_ytd",
  "co2_emissions_ytd",
  "forest_loss_ytd",
  "earth_orbit_ytd",
  "solar_installed_ytd",
] as const;

interface ModelSlot {
  model: CounterModel;
  prev?: PreviousModel;
}

export interface DashboardProps {
  metrics: MetricSummary[];
  initialModels: Record<string, CounterModel>;
}

function toSlots(models: Record<string, CounterModel>): Record<string, ModelSlot> {
  const slots: Record<string, ModelSlot> = {};
  for (const [id, model] of Object.entries(models)) slots[id] = { model };
  return slots;
}

function sameModel(a: CounterModel, b: CounterModel): boolean {
  return (
    a.modelVersion === b.modelVersion &&
    a.anchorTime === b.anchorTime &&
    a.anchorValue === b.anchorValue &&
    a.observedAt === b.observedAt
  );
}

export default function Dashboard({ metrics, initialModels }: DashboardProps) {
  const [locale, setLocaleState] = useState<Locale>("fr");
  const [theme, setThemeState] = useState<Theme>("light");
  const [slots, setSlots] = useState<Record<string, ModelSlot>>(() => toSlots(initialModels));
  // User arrival instant — anchors every "since you arrived" counter.
  const sessionStart = useMemo(() => Date.now(), []);

  useEffect(() => {
    const savedLocale = window.localStorage.getItem(LOCALE_STORAGE_KEY);
    if (isLocale(savedLocale)) setLocaleState(savedLocale);
    if (window.localStorage.getItem(THEME_STORAGE_KEY) === "dark") setThemeState("dark");
  }, []);

  useEffect(() => {
    document.documentElement.lang = locale;
  }, [locale]);

  useEffect(() => {
    if (theme === "dark") document.documentElement.dataset.theme = "dark";
    else delete document.documentElement.dataset.theme;
  }, [theme]);

  const setLocale = (next: Locale) => {
    setLocaleState(next);
    window.localStorage.setItem(LOCALE_STORAGE_KEY, next);
  };
  const toggleTheme = () => {
    setThemeState((current) => {
      const next: Theme = current === "light" ? "dark" : "light";
      window.localStorage.setItem(THEME_STORAGE_KEY, next);
      return next;
    });
  };

  // SSE: the server pushes MODELS, never per-tick values. Replacements are
  // cross-faded over 60 s by LiveCounter (prev + swapStartMs) — no visible jump.
  useEffect(() => {
    const applyModel = (incoming: CounterModel, receivedAt: number) => {
      setSlots((current) => {
        const existing = current[incoming.metricId];
        if (existing !== undefined && sameModel(existing.model, incoming)) return current;
        const slot: ModelSlot =
          existing !== undefined
            ? { model: incoming, prev: { model: existing.model, swapStartMs: receivedAt } }
            : { model: incoming };
        return { ...current, [incoming.metricId]: slot };
      });
    };

    const source = new EventSource(sseStreamUrl());
    source.addEventListener("models", (event) => {
      try {
        const payload = JSON.parse((event as MessageEvent<string>).data) as
          | Record<string, CounterModel>
          | { models: Record<string, CounterModel> };
        const models =
          "models" in payload && typeof payload.models === "object"
            ? (payload as { models: Record<string, CounterModel> }).models
            : (payload as Record<string, CounterModel>);
        const now = Date.now();
        for (const model of Object.values(models)) applyModel(model, now);
      } catch {
        // Malformed frame — keep the current models (never break a running counter).
      }
    });
    source.addEventListener("model", (event) => {
      try {
        const model = JSON.parse((event as MessageEvent<string>).data) as CounterModel;
        applyModel(model, Date.now());
      } catch {
        // Malformed frame — ignored.
      }
    });
    return () => source.close();
  }, []);

  return (
    <I18nProvider locale={locale} setLocale={setLocale}>
      <DashboardBody
        metrics={metrics}
        slots={slots}
        sessionStart={sessionStart}
        theme={theme}
        onToggleTheme={toggleTheme}
      />
    </I18nProvider>
  );
}

function DashboardBody({
  metrics,
  slots,
  sessionStart,
  theme,
  onToggleTheme,
}: {
  metrics: MetricSummary[];
  slots: Record<string, ModelSlot>;
  sessionStart: number;
  theme: Theme;
  onToggleTheme: () => void;
}) {
  const { locale, setLocale, t } = useI18n();
  const models = useMemo(() => {
    const map: Record<string, CounterModel> = {};
    for (const [id, slot] of Object.entries(slots)) map[id] = slot.model;
    return map;
  }, [slots]);

  // Live-first information architecture: tiers computed from the models
  // themselves (tick speed of the last visible digit) — not hand-picked.
  const tiers = useMemo(() => {
    const now = Date.now();
    const byTier: Record<DisplayTier, MetricSummary[]> = {
      hero: [],
      live: [],
      country: [],
      realtime: [],
      reference: [],
    };
    for (const metric of metrics) {
      byTier[tierFor(metric, resolveModel(metric, models), now)].push(metric);
    }
    byTier.live.sort(
      (a, b) =>
        liveSortKey(a, resolveModel(a, models), now) -
        liveSortKey(b, resolveModel(b, models), now),
    );
    return byTier;
  }, [metrics, models]);

  const hero = metrics.find((m) => m.id === HERO_METRIC_ID);
  const sessionMetrics = SESSION_METRIC_IDS.map((id) => metrics.find((m) => m.id === id)).filter(
    (m): m is MetricSummary => m !== undefined,
  );

  const slotFor = (metric: MetricSummary): ModelSlot | undefined => {
    const own = slots[metric.id];
    if (own !== undefined) return own;
    const inputId = metric.derived?.inputs[0]?.id;
    return inputId !== undefined ? slots[inputId] : undefined;
  };

  const counterProps = (metric: MetricSummary) => ({
    metric,
    model: resolveModel(metric, models),
    prev: slotFor(metric)?.prev,
    sessionStart,
  });

  return (
    <div className="mx-auto max-w-6xl px-4 pb-16">
      <header className="sticky top-0 z-30 -mx-4 mb-4 border-b bg-page/85 px-4 py-3 backdrop-blur-md">
        <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
          <div className="flex items-center gap-3">
            <h1 className="text-xl font-semibold tracking-tight text-ink">{t("site.title")}</h1>
            <span className="flex items-center gap-1.5 rounded-full bg-wash px-2.5 py-0.5 text-[11px] font-medium uppercase tracking-wide text-accent">
              <span aria-hidden className="live-dot" />
              {t("live.title")}
            </span>
          </div>
        <div className="flex items-center gap-2">
          <button
            type="button"
            onClick={onToggleTheme}
            className="rounded-full border px-3 py-1 text-xs text-ink2 transition-colors hover:border-ink/30"
          >
            {theme === "light" ? t("theme.toDark") : t("theme.toLight")}
          </button>
          <button
            type="button"
            onClick={() => setLocale(locale === "fr" ? "en" : "fr")}
            className="rounded-full border px-3 py-1 text-xs text-ink2 transition-colors hover:border-ink/30"
          >
            {t("locale.toggle")}
          </button>
        </div>
        </div>
      </header>
      <p className="mb-4 max-w-2xl text-sm text-ink2">{t("site.tagline")}</p>
      <nav aria-label="sections" className="mb-5 flex flex-wrap gap-2 text-xs">
        {[
          ["#section-live", t("live.title")],
          ["#since-arrival", t("since.title")],
          ["#section-countries", t("countries.title")],
          ["#section-realtime", t("realtime.title")],
          ["#section-reference", t("reference.title")],
        ].map(([href, label]) => (
          <a
            key={href}
            href={href}
            className="rounded-full border px-3 py-1 text-ink2 transition-colors hover:border-ink/30 hover:text-ink"
          >
            {label}
          </a>
        ))}
      </nav>
      <YearProgress />

      {hero !== undefined && (
        <section aria-labelledby="hero-figure" className="mb-10">
          <LiveCounter {...counterProps(hero)} window="total" size="display" live />
        </section>
      )}

      {tiers.live.length > 0 && (
        <Section id="live" title={t("live.title")} subtitle={t("live.subtitle")}>
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
            {tiers.live.map((metric) => (
              <LiveCounter
                key={metric.id}
                {...counterProps(metric)}
                window={resolvePrimaryWindow(metric)}
                live
              />
            ))}
          </div>
        </Section>
      )}

      {sessionMetrics.length > 0 && (
        <section
          aria-labelledby="since-arrival"
          className="mb-12 rounded-3xl border bg-gradient-to-br from-wash via-surface to-surface p-5 sm:p-6"
        >
          <h2 id="since-arrival" className="text-lg font-semibold text-ink">
            {t("since.title")}
          </h2>
          <p className="mb-4 mt-1 text-sm text-ink2">{t("since.subtitle")}</p>
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
            {sessionMetrics.map((metric) => (
              <LiveCounter
                key={`session-${metric.id}`}
                {...counterProps(metric)}
                window="session"
                live
              />
            ))}
          </div>
        </section>
      )}

      {tiers.country.filter((m) => !m.id.startsWith(CONTINENT_PREFIX)).length > 0 && (
        <Section
          id="countries"
          title={t("countries.title")}
          subtitle={t("countries.subtitle")}
        >
          <CountryRanking
            metrics={tiers.country.filter((m) => !m.id.startsWith(CONTINENT_PREFIX))}
            models={models}
          />
        </Section>
      )}

      {tiers.country.filter((m) => m.id.startsWith(CONTINENT_PREFIX)).length > 0 && (
        <Section
          id="continents"
          title={t("continents.title")}
          subtitle={t("continents.subtitle")}
        >
          <CountryRanking
            metrics={tiers.country.filter((m) => m.id.startsWith(CONTINENT_PREFIX))}
            models={models}
          />
        </Section>
      )}

      {tiers.realtime.length > 0 && (
        <Section id="realtime" title={t("realtime.title")} subtitle={t("realtime.subtitle")}>
          <QuakesLine />
          <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
            {tiers.realtime.map((metric) => (
              <LiveCounter key={metric.id} {...counterProps(metric)} window="total" />
            ))}
          </div>
        </Section>
      )}

      {tiers.reference.length > 0 && (
        <Section
          id="reference"
          title={t("reference.title")}
          subtitle={t("reference.subtitle")}
        >
          <div className="divide-y divide-line rounded-2xl border bg-surface">
            {tiers.reference.map((metric) => (
              <LiveCounter
                key={metric.id}
                {...counterProps(metric)}
                window={resolvePrimaryWindow(metric)}
                size="row"
              />
            ))}
          </div>
        </Section>
      )}
    </div>
  );
}

function Section({
  id,
  title,
  subtitle,
  children,
}: {
  id: string;
  title: string;
  subtitle: string;
  children: React.ReactNode;
}) {
  return (
    <section aria-labelledby={`section-${id}`} className="mb-12">
      <h2 id={`section-${id}`} className="text-lg font-semibold text-ink">
        {title}
      </h2>
      <p className="mb-4 mt-1 text-sm text-ink2">{subtitle}</p>
      {children}
    </section>
  );
}

/** True event-driven realtime (USGS): one fetch on mount, no interpolation. */
function QuakesLine() {
  const { locale, t } = useI18n();
  const [quakes, setQuakes] = useState<QuakesResponse | null>(null);

  useEffect(() => {
    let cancelled = false;
    fetchQuakes()
      .then((q) => {
        if (!cancelled) setQuakes(q);
      })
      .catch(() => {
        // Realtime extras are best-effort — the counter cards remain authoritative.
      });
    return () => {
      cancelled = true;
    };
  }, []);

  if (quakes === null || quakes.lastMajor === null) return null;
  const magnitude = formatValue(quakes.lastMajor.mag, { decimals: 1, unit: "" }, { locale });
  return (
    <p className="mb-3 text-xs text-ink2">
      {t("quakes.lastMajor")} : M {magnitude} — {quakes.lastMajor.place} (
      {formatDateUtc(quakes.lastMajor.timeIso, locale)})
    </p>
  );
}
