"use client";

import { useState, useEffect, useCallback, useRef } from "react";
import Link from "next/link";
import {
  TrendingUp, AlertTriangle, Bot, Users, Search,
  ShieldCheck, FileText, BarChart3,
  Zap, Lightbulb, Activity, MousePointerClick, Eye, Percent, Hash,
  AlertCircle, ArrowUp, ArrowDown, ChevronRight, Target, Clock,
  Bell, Plug, Database, Megaphone,
} from "lucide-react";
import type { LucideIcon } from "lucide-react";

type Delta = { pct: number; direction: "up" | "down" | "flat" } | null;
import { useBrand } from "@/context/brand-context";
import { DataMaturityNotice } from "@/components/ui/data-maturity-notice";
import ConversionsBySource from "@/components/dashboard/conversions-by-source";
import { Pencil, ChevronDown, RotateCcw, Check, Plus as PlusIcon, RefreshCw } from "lucide-react";

// ── Dashboard section definitions ──
type SectionId = "kpis" | "paid_media" | "technical_issues" | "execution_hub" | "recommendations" | "ai_visibility" | "competitors" | "search_performance" | "quick_actions";
const ALL_SECTIONS: Array<{ id: SectionId; label: string }> = [
  { id: "kpis", label: "KPI Cards" },
  { id: "paid_media", label: "Paid Media" },
  { id: "technical_issues", label: "Technical Issues" },
  { id: "execution_hub", label: "Execution Hub" },
  { id: "recommendations", label: "Recommendations" },
  { id: "ai_visibility", label: "AI Visibility" },
  { id: "competitors", label: "Competitors" },
  { id: "search_performance", label: "Search Performance" },
  { id: "quick_actions", label: "Quick Actions" },
];
const ALL_SECTION_IDS = ALL_SECTIONS.map(s => s.id);
const PRESETS: Record<string, { label: string; visible: SectionId[] }> = {
  full: { label: "Default Layout", visible: ALL_SECTION_IDS },
  executive: { label: "Executive Summary", visible: ["kpis", "recommendations", "quick_actions"] },
  technical: { label: "Technical Focus", visible: ["kpis", "technical_issues", "search_performance"] },
};
const STORAGE_KEY_PRESET = "meseo_dashboard_preset";
const STORAGE_KEY_CUSTOM = "meseo_dashboard_custom_sections";

// ─── Module-scope cache ───────────────────────────────────────────────────────
// Survives component unmount/remount across the user's session, so leaving
// the dashboard and coming back paints the previous response immediately
// instead of waiting on another full fetch. Server-side Cache-Control
// covers cross-session caching; this covers same-session navigation.
//
// Keyed by brandId + range so a brand switch or range change always misses
// and we re-fetch. handleRefresh deletes the key explicitly so the Refresh
// button still forces a fresh server hit even if the entry isn't stale yet.

interface DashboardSnapshot {
  ga4: unknown;
  gsc: unknown;
  audit: unknown;
  deadPages: unknown;
  perfScore: number | null;
  siteTag: unknown;
  execution: unknown;
  brandInfo: unknown;
  aiVisibility: unknown;
  competitors: unknown;
  /** Search Performance state — stored separately from `gsc` because
   *  it's loaded lazily from /api/dashboard/search-performance and
   *  has its own resolution timeline. */
  searchPerf: unknown;
}

const dashboardCache = new Map<string, { data: DashboardSnapshot; timestamp: number }>();
const DASHBOARD_CACHE_TTL_MS = 60_000;

// localStorage persistence: survives page refreshes, tab closures, and
// logins. The module-scope Map only survives unmount/remount within the
// same SPA session — it's empty on cold page loads, which is exactly
// when the dashboard times out and shows 0. This layer ensures the
// second load ALWAYS has data even if the first timed out.
const LS_PREFIX = "meseo_dash_";
const LS_TTL_MS = 12 * 60 * 60 * 1000; // 12 hours — generous, stale data beats zeros

function persistToLS(key: string, data: DashboardSnapshot, timestamp: number) {
  try {
    localStorage.setItem(`${LS_PREFIX}${key}`, JSON.stringify({ data, timestamp }));
  } catch { /* quota exceeded or private mode — ignore */ }
}

function readFromLS(key: string): { data: DashboardSnapshot; timestamp: number } | null {
  try {
    const raw = localStorage.getItem(`${LS_PREFIX}${key}`);
    if (!raw) return null;
    const parsed = JSON.parse(raw) as { data: DashboardSnapshot; timestamp: number };
    if (Date.now() - parsed.timestamp > LS_TTL_MS) return null;
    return parsed;
  } catch { return null; }
}

// ─── Page ─────────────────────────────────────────────────────────────────────

export default function DashboardPage() {
  const { brand, getSourceStatus } = useBrand();

  // Live data from API
  const [range, setRange] = useState<"1d" | "7d" | "28d" | "90d" | "month" | "custom">("28d");
  const [customStart, setCustomStart] = useState("");
  const [customEnd, setCustomEnd] = useState("");
  // GA4 traffic-channel filter. "all" (default) = total traffic as
  // GA4 has always reported; "organic" = sessions whose default
  // channel group is "Organic Search", so SEO-attributed traffic
  // can be compared against the total via a toggle near the date
  // range selector. Only affects New Users / Sessions KPIs —
  // Impressions / Clicks / Avg Position / Avg CTR are already GSC
  // (which is organic-only by definition) and don't change.
  //
  // Included in the cache key so a user who toggles between "All"
  // and "Organic" gets two distinct cached slices rather than one
  // overwriting the other. Reset to "all" on brand switch (see the
  // effect below) because the previous brand's choice carries no
  // meaning for a new site.
  const [channel, setChannel] = useState<"all" | "organic">("all");

  // Lazy-init from the module-scope cache so a remount within TTL
  // paints with real data on the very first render — no "..." flash.
  // The cache key has to be computable at the moment useState's
  // initialiser runs, so we read brand.id / "28d" defaults inline.
  // Subsequent brand or range changes still flow through the effect
  // path that loadDashboard owns.
  // Lazy-init key matches the default state: 28d range + no custom
  // dates + channel=all. Keep this in sync with the defaults above
  // — if either default changes, the cache will silently miss until
  // the first full fetch completes (functional bug only, not a crash).
  const initialCacheKey = `${brand.id}:28d:::all`;
  const initialCached = (() => {
    // 1. Module-scope cache (survives SPA unmount/remount, <60s TTL)
    const c = dashboardCache.get(initialCacheKey);
    if (c && Date.now() - c.timestamp < DASHBOARD_CACHE_TTL_MS) return c.data;
    // 2. localStorage cache (survives page refresh / login, <12h TTL)
    const ls = readFromLS(initialCacheKey);
    if (ls) {
      // Promote to module-scope so the rest of the code path works
      dashboardCache.set(initialCacheKey, ls);
      return ls.data;
    }
    return null;
  })();
  if (initialCached) {
    console.info("[dashboard] lazy-init from cache", { key: initialCacheKey, source: dashboardCache.has(initialCacheKey) ? "module" : "localStorage" });
  }
  const [ga4, setGa4] = useState<{
    connected: boolean;
    sessions?: number; users?: number; newUsers?: number;
    pageViews?: number; conversions?: number;
    deltas?: { sessions?: Delta; users?: Delta; newUsers?: Delta; conversions?: Delta };
    lastSynced?: string | null;
    source?: string;
    // Which GA4 channel slice the server returned. "all" = total
    // traffic, "organic" = Organic Search only. Mirrors the
    // dashboard's channel toggle. Optional because older cached
    // entries pre-toggle won't have it.
    channel?: "all" | "organic";
  } | null>(() => (initialCached?.ga4 ?? null) as React.SetStateAction<unknown> as never);
  const [gsc, setGsc] = useState<{
    connected: boolean;
    clicks?: number; impressions?: number; avgPosition?: number; ctr?: number;
    topPages?: Array<{ pageUrl: string; clicks: number; impressions: number }>;
    topQueries?: Array<{ query: string; clicks: number; position: number }>;
    deltas?: { clicks?: Delta; impressions?: Delta; avgPosition?: Delta; ctr?: Delta };
    coverage?: { daysAvailable: number; daysRequested: number };
    fromDb?: boolean;
    lastSynced?: string | null;
  } | null>(() => (initialCached?.gsc ?? null) as never);
  const [audit, setAudit] = useState<{
    connected: boolean;
    crawlHealth?: number;
    errors?: number;
    warnings?: number;
    // Added: `notices` and `passed` are stored on TechnicalAudit and
    // shown on the Technical Audit page (notices = severity "low",
    // passed = per-check passed count). Dashboard now reads them too
    // so Recommendations/Technical Issues cards never under-count
    // versus what the user sees on /technical-audit.
    notices?: number;
    passed?: number;
    // Renamed semantics: totalIssues is now errorCount+warningCount+noticeCount
    // (the sum of everything found), openIssues is the subset still
    // actionable. Previously totalIssues was `issues[].length` filtered
    // by status=open, which conflated the two.
    totalIssues?: number;
    openIssues?: number;
    pagesScanned?: number;
    lastAuditAt?: string;
  } | null>(() => (initialCached?.audit ?? null) as never);
  const [deadPages, setDeadPages] = useState<{ count: number } | null>(() => (initialCached?.deadPages ?? null) as never);
  const [perfScore, setPerfScore] = useState<number | null>(() => initialCached?.perfScore ?? null);
  const [execution, setExecution] = useState<{ overdue: number; completedThisWeek: number } | null>(() => (initialCached?.execution ?? null) as never);
  // AI Visibility + Competitors counts straight from the dashboard
  // response — the source-status fetch is unreliable enough that the
  // cards were rendering "Set up tracking" on brands that had data.
  const [aiVisData, setAiVisData] = useState<{ tracked: number; lastChecked: string | null } | null>(() => (initialCached?.aiVisibility ?? null) as never);
  const [competitorData, setCompetitorData] = useState<{ tracked: number } | null>(() => (initialCached?.competitors ?? null) as never);
  // Cross-source counts that let the Technical Issues / Recommendations
  // / Execution Hub cards render usefully even on brands that haven't
  // run a technical audit. The cards prefer audit-derived numbers when
  // present; otherwise they fall through to these.
  const [signalsData, setSignalsData] = useState<{ count: number } | null>(null);
  const [tasksData, setTasksData] = useState<{ total: number; overdue: number; completedThisWeek: number } | null>(null);
  const [paidMediaData, setPaidMediaData] = useState<{
    paidSessions: number; organicSessions: number;
    paidConversions: number; organicConversions: number;
    assistedConversions: number; channelBackfillTriggered: boolean;
  } | null>(null);
  // Search Performance (top GSC pages + queries) lives in its own state
  // because the lazy /api/dashboard/search-performance endpoint resolves
  // independently of the main dashboard race. Previously this merged
  // into `gsc` via setGsc(prev => prev ? {...} : prev) — but when the
  // lazy fetch resolved BEFORE the main dashboard fetch (common — the
  // search-perf endpoint is faster), prev was null and the merge
  // discarded the data entirely. Decoupling makes the card rendering
  // independent of any gsc race.
  const [searchPerf, setSearchPerf] = useState<{
    topQueries: Array<{ query: string; clicks: number; position: number }>;
    topPages: Array<{ pageUrl: string; clicks: number; impressions: number }>;
    source?: string;
  } | null>(() => (initialCached?.searchPerf ?? null) as never);
  const [brandInfo, setBrandInfo] = useState<{ name: string; domain: string } | null>(() => (initialCached?.brandInfo ?? null) as never);
  // dataLoading starts false when we hit the lazy-init cache so the
  // KPI cards skip their "..." loading state on the very first paint.
  const [dataLoading, setDataLoading] = useState(!initialCached);
  // Site Tag Machine Enhancing attribution — only renders if conversions exist
  const [siteTagAttribution, setSiteTagAttribution] = useState<{
    total: number;
    channels: Array<{ channel: string; total: number; firstTouch: number; assisted: number }>;
  } | null>(null);
  // Site Tag install metadata — drives the data-maturity notice on the dashboard
  const [siteTag, setSiteTag] = useState<{ installed: boolean; ageDays: number; installedAt: string | null } | null>(() => (initialCached?.siteTag ?? null) as never);

  // Compute the active date range for display. Uses UTC throughout to
  // match the server-side computation in /api/dashboard getDateRange().
  const activeDateRange = (() => {
    if (range === "custom" && customStart && customEnd) return { start: customStart, end: customEnd };
    const now = new Date();
    const end = now.toISOString().split("T")[0];
    if (range === "month") {
      const firstOfMonthMs = Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1);
      return { start: new Date(firstOfMonthMs).toISOString().split("T")[0], end };
    }
    const days = range === "1d" ? 1 : range === "7d" ? 7 : range === "90d" ? 90 : 28;
    // Subtract (days - 1) so "28 Days" shows a 28-day window inclusive of today
    return { start: new Date(Date.now() - (days - 1) * 86400000).toISOString().split("T")[0], end };
  })();

  // Reset all state on brand change to prevent cross-brand carryover
  useEffect(() => {
    setGa4(null);
    setGsc(null);
    setAudit(null);
    setDeadPages(null);
    setPerfScore(null);
    setExecution(null);
    setBrandInfo(null);
    setSiteTagAttribution(null);
    setAiVisData(null);
    setCompetitorData(null);
    setSignalsData(null);
    setTasksData(null);
    setPaidMediaData(null);
    setSearchPerf(null);
    // Reset traffic-channel toggle on brand switch — the previous
    // brand's choice (especially "organic") carries no relevant
    // context for a different site, and the "All Traffic" default
    // matches the KPI-card behaviour users expect on first paint.
    setChannel("all");
  }, [brand.id]);

  // Site Tag attribution — fetched independently so a slow/empty feed
  // never blocks the main dashboard data. Only renders the card if the
  // brand has any Machine Enhancing conversions in the selected range.
  useEffect(() => {
    if (!brand.id) return;
    const days = range === "7d" ? 7 : range === "90d" ? 90 : range === "month" ? 30 : 28;
    let cancelled = false;
    fetch(`/api/conversion-intelligence/attribution?brandId=${brand.id}&days=${days}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => { if (!cancelled && data) setSiteTagAttribution(data); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [brand.id, range]);

  // Search Performance (GSC top pages + top queries) — lazy-loaded
  // from /api/dashboard/search-performance AFTER the main dashboard
  // resolves. Stored in its own `searchPerf` state, NOT merged into
  // `gsc`, because:
  //   1. The lazy fetch usually resolves before the main dashboard
  //      fetch — when it tried to merge into a still-null gsc state
  //      the data got discarded by the `prev ? {...} : prev` guard.
  //   2. The dashboardCache snapshot stores `gsc` from the main
  //      dashboard response, which always sets topQueries=[] /
  //      topPages=[] (because they're lazy-loaded). Merging into
  //      gsc and then snapshotting it overwrote the real data with
  //      empty arrays on every cache write.
  // searchPerf has its own cache slot so reload paints from cached
  // data, then the fetch refreshes it independently.
  useEffect(() => {
    if (!brand.id) return;
    let cancelled = false;
    const params = new URLSearchParams({ brandId: brand.id, range });
    if (range === "custom" && customStart && customEnd) {
      params.set("start", customStart);
      params.set("end", customEnd);
    }
    fetch(`/api/dashboard/search-performance?${params}`)
      .then((r) => (r.ok ? r.json() : null))
      .then((data) => {
        if (cancelled || !data) return;
        const next = {
          topQueries: data.topQueries ?? [],
          topPages: data.topPages ?? [],
          source: data.source,
        };
        setSearchPerf(next);
        // Persist the lazy result into the dashboardCache slot so a
        // remount within TTL paints Search Performance instantly via
        // lazy-init instead of flashing "Awaiting Data" until the
        // lazy fetch resolves again.
        const key = `${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`;
        const existing = dashboardCache.get(key);
        if (existing) {
          dashboardCache.set(key, {
            data: { ...existing.data, searchPerf: next },
            timestamp: existing.timestamp,
          });
        }
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, [brand.id, range, customStart, customEnd]);

  // Tracked so the Refresh button can show a "Last updated: Xs ago" label
  // and so we can drive a spinner state independent of the initial load.
  const [lastUpdatedAt, setLastUpdatedAt] = useState<number | null>(null);
  const [refreshing, setRefreshing] = useState(false);
  // Re-render every 5s so the relative timestamp ("Xs ago") stays accurate
  // without hitting the network.
  const [, setTick] = useState(0);
  useEffect(() => {
    const t = setInterval(() => setTick((n) => n + 1), 5000);
    return () => clearInterval(t);
  }, []);
  const loadingRef = useRef(false);
  // Holds the AbortController for the currently in-flight Phase 1 live
  // fetch. When the user toggles the channel (or changes brand/range)
  // while a fetch is running, we abort it and start fresh rather than
  // silently ignoring the new request (which left the "Organic" chip
  // visually selected but showing all-traffic numbers).
  const liveAbortRef = useRef<AbortController | null>(null);
  // Controllers aborted intentionally (channel change, not 20s timeout)
  // are added here so the AbortError handler can skip the timeout banner.
  const intentionalAbortsRef = useRef(new Set<AbortController>());

  const [loadError, setLoadError] = useState<string | null>(null);

  const loadDashboard = useCallback(async (force = false) => {
    if (!brand.id) return;
    // Don't fetch custom range until both dates are set
    if (range === "custom" && (!customStart || !customEnd)) return;
    if (loadingRef.current) {
      // A live fetch is in flight. If it has a registered controller,
      // abort it so this new request (different channel, brand, or range)
      // can proceed immediately. Without this, toggling "Organic" while
      // the initial "All Traffic" fetch is running silently no-ops and
      // leaves the chip selected but showing all-traffic numbers.
      if (liveAbortRef.current) {
        intentionalAbortsRef.current.add(liveAbortRef.current);
        liveAbortRef.current.abort();
        liveAbortRef.current = null;
        loadingRef.current = false;
      } else {
        // Phase 1 hasn't started yet (still in Phase 0 snapshot fetch).
        // Can't abort — skip to avoid overlapping.
        return;
      }
    }

    // Module-scope cache hit — paint the previous response instantly,
    // skip the network fetch entirely. Survives unmount/remount so
    // navigating away and back to the dashboard within 60s is free.
    // `force=true` (Refresh button, brand switch reset) bypasses.
    const cacheKey = `${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`;
    if (!force) {
      const cached = dashboardCache.get(cacheKey);
      const ageMs = cached ? Date.now() - cached.timestamp : null;
      console.info("[dashboard] cache check", { cacheKey, hit: !!cached, ageMs, ttlMs: DASHBOARD_CACHE_TTL_MS });
      if (cached && Date.now() - cached.timestamp < DASHBOARD_CACHE_TTL_MS) {
        const d = cached.data;
        setBrandInfo(d.brandInfo as { name: string; domain: string } | null);
        setGa4(d.ga4 as Parameters<typeof setGa4>[0]);
        setGsc(d.gsc as Parameters<typeof setGsc>[0]);
        setAudit(d.audit as Parameters<typeof setAudit>[0]);
        setDeadPages(d.deadPages as Parameters<typeof setDeadPages>[0]);
        setPerfScore(d.perfScore);
        setSiteTag(d.siteTag as Parameters<typeof setSiteTag>[0]);
        setExecution(d.execution as Parameters<typeof setExecution>[0]);
        setAiVisData(d.aiVisibility as Parameters<typeof setAiVisData>[0]);
        setCompetitorData(d.competitors as Parameters<typeof setCompetitorData>[0]);
        setLastUpdatedAt(cached.timestamp);
        setDataLoading(false);
        return;
      }
    }

    loadingRef.current = true;
    setDataLoading(true);
    setLoadError(null);
    const loadStart = Date.now();

    // ── Phase -1: localStorage fallback (instant, no network) ──────
    // On cold page load (fresh tab, login, refresh), the module-scope
    // cache is empty. Check localStorage for the last-known-good
    // dashboard data and paint it BEFORE any network requests.
    const SNAP_FALLBACK = { connected: true, source: "snapshot" } as const;
    if (!force) {
      const lsCacheKey = `${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`;
      const lsCached = readFromLS(lsCacheKey);
      if (lsCached) {
        const d = lsCached.data;
        setBrandInfo((d.brandInfo as { name: string; domain: string } | null) ?? null);
        setGa4((d.ga4 as Parameters<typeof setGa4>[0]) ?? SNAP_FALLBACK);
        setGsc((d.gsc as Parameters<typeof setGsc>[0]) ?? SNAP_FALLBACK);
        setAudit((d.audit as Parameters<typeof setAudit>[0]) ?? SNAP_FALLBACK);
        setDeadPages((d.deadPages as Parameters<typeof setDeadPages>[0]) ?? null);
        setPerfScore(d.perfScore ?? null);
        setSiteTag((d.siteTag as Parameters<typeof setSiteTag>[0]) ?? null);
        setExecution((d.execution as Parameters<typeof setExecution>[0]) ?? null);
        setAiVisData((d.aiVisibility as Parameters<typeof setAiVisData>[0]) ?? null);
        setCompetitorData((d.competitors as Parameters<typeof setCompetitorData>[0]) ?? null);
        setLastUpdatedAt(lsCached.timestamp);
        setDataLoading(false);
        console.info("[dashboard] localStorage hit", { key: lsCacheKey, ageMinutes: Math.round((Date.now() - lsCached.timestamp) / 60_000) });
        // DON'T return — continue to Phase 0/1 for fresh data in background
      }
    }

    // ── Phase 0: Server-side snapshot (instant, one DB read) ────────
    if (!force) {
      try {
        const snapRes = await fetch(`/api/dashboard/summary?brandId=${brand.id}&range=${range}`);
        if (snapRes.ok) {
          const snap = await snapRes.json();
          if (snap.cached && snap.ga4) {
            setBrandInfo(snap.brand ?? null);
            setGa4(snap.ga4 ?? SNAP_FALLBACK);
            setGsc(snap.gsc ?? SNAP_FALLBACK);
            setAudit(snap.audit ?? SNAP_FALLBACK);
            setDeadPages(snap.deadPages ?? null);
            setPerfScore(snap.performance?.score ?? null);
            setSiteTag(snap.siteTag ?? null);
            setExecution(snap.execution ?? null);
            setAiVisData(snap.aiVisibility ?? null);
            setCompetitorData(snap.competitors ?? null);
            setSignalsData(snap.signals ?? null);
            setTasksData(snap.tasks ?? null);
            setPaidMediaData(snap.paidMedia ?? null);
            setLastUpdatedAt(new Date(snap.computedAt).getTime());
            setDataLoading(false);
            console.info("[dashboard] snapshot loaded", { brandId: brand.id, range, elapsed: Date.now() - loadStart, ageMinutes: snap.ageMinutes, source: "snapshot" });
            // Continue to Phase 1 in background — don't return here.
            // The user sees cached data; fresh data will update tiles
            // as it arrives.
          }
        }
      } catch { /* snapshot unavailable — fall through to live fetch */ }
    }

    // ── Phase 1: Live fetch ─────────────────────────────────────────
    const controller = new AbortController();
    liveAbortRef.current = controller; // register so a new call can abort us
    const abortTimer = setTimeout(() => controller.abort(), 20_000);

    // Sentinel used when the fetch fails or aborts. Cards need a
    // non-null value to exit their loading state. We mark connected:
    // true with source: "timeout" rather than connected: false —
    // a 30s server-side timeout almost always implies the brand has
    // a connected integration that's currently slow, NOT that the
    // user never set it up. Setting connected:false on timeout
    // produced a false "Connect Google Search Console" prompt on
    // the Search Performance card right after a successful sync.
    // Brands without an actual GSC connection respond instantly
    // (no Google fetch to time out on), so this fallback never
    // fires in their case.
    const FALLBACK = { connected: true, source: "timeout" } as const;
    let ok = false;
    try {
      const params = new URLSearchParams({ brandId: brand.id, range });
      if (range === "custom" && customStart && customEnd) {
        params.set("start", customStart);
        params.set("end", customEnd);
      }
      // Only send channel=organic when non-default. Keeps the query
      // string clean for the common "All Traffic" case — and the
      // server treats a missing param as "all" (same default).
      if (channel !== "all") params.set("channel", channel);
      const res = await fetch(`/api/dashboard?${params}`, { cache: "no-store", signal: controller.signal });
      if (res.ok) {
        const data = await res.json();
        if (data) {
          setBrandInfo(data.brand ?? null);
          setGa4(data.ga4 ?? FALLBACK);
          setGsc(data.gsc ?? FALLBACK);
          setAudit(data.audit ?? FALLBACK);
          setDeadPages(data.deadPages ?? null);
          setPerfScore(data.performance?.score ?? null);
          setSiteTag(data.siteTag ?? null);
          setExecution(data.execution ?? null);
          setAiVisData(data.aiVisibility ?? null);
          setCompetitorData(data.competitors ?? null);
          setSignalsData(data.signals ?? null);
          setTasksData(data.tasks ?? null);
          setPaidMediaData(data.paidMedia ?? null);
          const now = Date.now();
          setLastUpdatedAt(now);
          // Snapshot the response into the module-scope cache so the
          // next mount paints instantly. Keyed identically to the
          // cache lookup above so the keys match deterministically.
          dashboardCache.set(`${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`, {
            data: {
              ga4: data.ga4 ?? FALLBACK,
              gsc: data.gsc ?? FALLBACK,
              audit: data.audit ?? FALLBACK,
              deadPages: data.deadPages ?? null,
              perfScore: data.performance?.score ?? null,
              siteTag: data.siteTag ?? null,
              execution: data.execution ?? null,
              brandInfo: data.brand ?? null,
              aiVisibility: data.aiVisibility ?? null,
              competitors: data.competitors ?? null,
              // searchPerf is loaded lazily — leave it null here and
              // let the lazy effect's cache update fill it in. The
              // existing cached searchPerf (if any) survives this
              // write because lazy-init also reads it.
              searchPerf: dashboardCache.get(`${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`)?.data.searchPerf ?? null,
            },
            timestamp: now,
          });
          ok = true;
          // Persist to localStorage so the NEXT cold page load (refresh,
          // login, new tab) has data instantly. This is the key fix for
          // the "Retry every login" bug — module-scope cache is empty on
          // cold load, but localStorage survives.
          const cacheKey = `${brand.id}:${range}:${customStart ?? ""}:${customEnd ?? ""}:${channel}`;
          const cacheEntry = dashboardCache.get(cacheKey);
          if (cacheEntry) persistToLS(cacheKey, cacheEntry.data, cacheEntry.timestamp);
          console.info("[dashboard] live fetch OK", { brandId: brand.id, range, elapsed: Date.now() - loadStart, source: "live" });
        }
      } else {
        console.warn("[dashboard] live fetch failed", { brandId: brand.id, range, status: res.status, elapsed: Date.now() - loadStart });
        setLoadError(`Dashboard request failed (HTTP ${res.status}). Some metrics are temporarily unavailable.`);
      }
    } catch (err) {
      // AbortError means we hit the 20s client timeout — surface a
      // friendlier message instead of leaving the UI on "...". Every
      // other error is swallowed silently as before, since the
      // connection-status cards convey the failure.
      if (err instanceof DOMException && err.name === "AbortError") {
        if (intentionalAbortsRef.current.has(controller)) {
          // Aborted because a new channel/brand/range was requested — not a timeout.
          intentionalAbortsRef.current.delete(controller);
        } else {
          console.warn("[dashboard] request exceeded 20s client-side timeout");
          if (!lastUpdatedAt) {
            setLoadError("Dashboard data is taking longer than expected. Some metrics may be temporarily unavailable.");
          }
        }
      }
    }
    finally {
      clearTimeout(abortTimer);
      if (liveAbortRef.current === controller) liveAbortRef.current = null;
      // On any failure path (abort, non-OK, network error) seed the
      // KPI state with the FALLBACK sentinel if it's still null so the
      // cards stop showing "..." and drop to their "not connected"
      // / empty state rendering. Existing cards already handle
      // connected:false correctly.
      if (!ok) {
        setGa4((prev) => prev ?? FALLBACK);
        setGsc((prev) => prev ?? FALLBACK);
        setAudit((prev) => prev ?? FALLBACK);
      }
      setDataLoading(false);
      loadingRef.current = false;
    }
  }, [brand.id, range, customStart, customEnd, channel]);

  useEffect(() => { loadDashboard(); }, [loadDashboard]);

  async function handleRefresh() {
    setRefreshing(true);
    // force=true bypasses the module-scope cache so the user always
    // gets a fresh server response when they explicitly ask for one.
    try { await loadDashboard(true); } finally {
      // Small delay so the spinner is visibly perceptible even on fast fetches
      setTimeout(() => setRefreshing(false), 300);
    }
  }

  const lastUpdatedLabel = (() => {
    if (!lastUpdatedAt) return null;
    const diff = Date.now() - lastUpdatedAt;
    const secs = Math.floor(diff / 1000);
    if (secs < 5) return "Last updated: just now";
    if (secs < 60) return `Last updated: ${secs}s ago`;
    const mins = Math.floor(secs / 60);
    if (mins < 60) return `Last updated: ${mins}m ago`;
    const hours = Math.floor(mins / 60);
    return `Last updated: ${hours}h ago`;
  })();

  // Layout presets + customize
  const [activePreset, setActivePreset] = useState<string>(() => {
    if (typeof window === "undefined") return "full";
    const custom = localStorage.getItem(STORAGE_KEY_CUSTOM);
    if (custom) return "custom";
    return localStorage.getItem(STORAGE_KEY_PRESET) || "full";
  });
  const [visibleSections, setVisibleSections] = useState<Set<SectionId>>(() => {
    if (typeof window === "undefined") return new Set(PRESETS.full.visible);
    const custom = localStorage.getItem(STORAGE_KEY_CUSTOM);
    if (custom) try { return new Set(JSON.parse(custom) as SectionId[]); } catch { /* fall through */ }
    const preset = localStorage.getItem(STORAGE_KEY_PRESET) || "full";
    return new Set(PRESETS[preset]?.visible ?? PRESETS.full.visible);
  });
  const [presetDropdownOpen, setPresetDropdownOpen] = useState(false);
  const [customizeOpen, setCustomizeOpen] = useState(false);

  function applyPreset(key: string) {
    const p = PRESETS[key];
    if (!p) return;
    setVisibleSections(new Set(p.visible));
    setActivePreset(key);
    localStorage.setItem(STORAGE_KEY_PRESET, key);
    localStorage.removeItem(STORAGE_KEY_CUSTOM);
    setPresetDropdownOpen(false);
  }
  function toggleSection(id: SectionId) {
    setVisibleSections(prev => {
      const next = new Set(prev);
      next.has(id) ? next.delete(id) : next.add(id);
      localStorage.setItem(STORAGE_KEY_CUSTOM, JSON.stringify([...next]));
      localStorage.removeItem(STORAGE_KEY_PRESET);
      setActivePreset("custom");
      return next;
    });
  }
  const show = (id: SectionId) => visibleSections.has(id);

  const siteName = brandInfo?.name ?? brand.name;
  const siteDomain = brandInfo?.domain ?? brand.domain;
  const healthScore = audit?.connected ? (audit.crawlHealth ?? 0) : 0;

  // Source statuses for sections without direct API data
  // Source-status from brand context is the historical input but it's
  // unreliable enough (the /api/source-status fetch sometimes returns
  // empty or fails silently) that the cards were rendering "Set up
  // tracking" on brands with active queries / competitors. Prefer the
  // counts the dashboard API now ships directly: if either count is >0
  // we override the status to "connected" so the card renders properly.
  const sourceAiStatus = getSourceStatus("ai_visibility");
  const sourceCompetitorStatus = getSourceStatus("competitors");
  const aiStatus = aiVisData && aiVisData.tracked > 0
    ? {
        status: "connected",
        detail: `${aiVisData.tracked} ${aiVisData.tracked === 1 ? "query" : "queries"} tracked`,
        lastUpdated: aiVisData.lastChecked,
      }
    : sourceAiStatus;
  const competitorStatus = competitorData && competitorData.tracked > 0
    ? {
        status: "connected",
        detail: `${competitorData.tracked} tracked`,
        lastUpdated: null,
      }
    : sourceCompetitorStatus;

  return (
    <div className="space-y-5">

      {/* ══ 1. COMMAND HEADER ══════════════════════════════════════════════════ */}
      <div className="bg-gradient-to-r from-slate-900 to-slate-800 rounded-2xl px-6 py-5 flex flex-col sm:flex-row sm:items-center gap-4">
        <div className="flex-1">
          <div className="flex items-center gap-2 mb-1">
            <span className="w-2 h-2 rounded-full bg-green-400 animate-pulse" />
            <span className="text-slate-400 text-xs font-medium uppercase tracking-widest">Dashboard</span>
          </div>
          <div className="flex items-center gap-3">
            <h1 className="text-2xl font-bold text-white">{siteName}</h1>
            <div className="flex items-center gap-2">
              {ga4?.connected && (
                <span className="flex items-center gap-1.5 text-[11px] font-semibold text-emerald-300 bg-emerald-500/15 border border-emerald-500/25 px-2.5 py-1 rounded-lg">
                  <Activity className="w-3 h-3" />
                  {(ga4.sessions ?? 0).toLocaleString()} sessions
                  <span className="text-emerald-400/60 font-normal">({range === "7d" ? "7d" : range === "90d" ? "90d" : range === "month" ? "MTD" : range === "custom" ? "custom" : "28d"})</span>
                </span>
              )}
              {audit?.connected && (
                <span className="flex items-center gap-1.5 text-[11px] font-semibold text-amber-300 bg-amber-500/15 border border-amber-500/25 px-2.5 py-1 rounded-lg">
                  <Bell className="w-3 h-3" />
                  {audit.totalIssues ?? 0} issues
                </span>
              )}
            </div>
          </div>
          <div className="flex flex-wrap items-center gap-4 mt-2">
            <span className="flex items-center gap-1.5 text-slate-400 text-xs">
              <Clock className="w-3.5 h-3.5" />
              {siteDomain}
            </span>
            {audit?.connected && (
              <span className="flex items-center gap-1.5 text-slate-400 text-xs">
                <Target className="w-3.5 h-3.5" />
                Site health: <span className="text-white font-bold">{healthScore}/100</span>
              </span>
            )}
          </div>
        </div>
        <Link href="/technical-audit" className="self-start sm:self-auto flex items-center gap-2 px-4 py-2.5 rounded-lg bg-brand-600 text-white text-sm font-semibold hover:bg-brand-700 transition-colors shadow-lg shadow-brand-900/30">
          <ShieldCheck className="w-4 h-4" />
          Run Audit
        </Link>
      </div>

      {/* ══ DATE RANGE + LAST SYNCED ═══════════════════════════════════════ */}
      <div className="flex items-center justify-between flex-wrap gap-2">
        <div className="flex items-center gap-1 flex-wrap">
          <div className="flex items-center bg-slate-100 rounded-lg p-1 gap-1">
            {(["1d", "7d", "28d", "90d", "month"] as const).map((r) => (
              <button key={r} onClick={() => setRange(r)} className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-colors ${range === r ? "bg-white text-slate-900 shadow-sm" : "text-slate-500 hover:text-slate-700"}`}>
                {r === "1d" ? "Today" : r === "7d" ? "7 Days" : r === "28d" ? "28 Days" : r === "90d" ? "90 Days" : "This Month"}
              </button>
            ))}
            <button onClick={() => setRange("custom")} className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-colors ${range === "custom" ? "bg-white text-slate-900 shadow-sm" : "text-slate-500 hover:text-slate-700"}`}>
              Custom
            </button>
          </div>
          {range === "custom" && (
            <div className="flex items-center gap-1.5">
              <input type="date" value={customStart} onChange={e => setCustomStart(e.target.value)} className="text-xs border border-slate-200 rounded-md px-2 py-1.5" />
              <span className="text-xs text-slate-400">to</span>
              <input type="date" value={customEnd} onChange={e => setCustomEnd(e.target.value)} className="text-xs border border-slate-200 rounded-md px-2 py-1.5" />
            </div>
          )}
          {/* GA4 channel toggle. Segmented control that flips the
              GA4-backed KPI cards (New Users, Sessions) between total
              traffic and Organic-Search-only traffic. Only the GA4
              cards change — GSC metrics (Impressions, Clicks, Avg
              Position, Avg CTR) are already organic-only by definition
              and stay put. Grouped visually next to the date range so
              "what window + what slice" reads as a single control. */}
          <div className="flex items-center bg-slate-100 rounded-lg p-1 gap-1" role="tablist" aria-label="Traffic channel">
            {(["all", "organic"] as const).map((c) => (
              <button
                key={c}
                role="tab"
                aria-selected={channel === c}
                onClick={() => setChannel(c)}
                className={`px-3 py-1.5 rounded-md text-xs font-semibold transition-colors ${channel === c ? "bg-white text-slate-900 shadow-sm" : "text-slate-500 hover:text-slate-700"}`}
                title={c === "all" ? "All GA4 traffic" : "GA4 sessions from Organic Search only"}
              >
                {c === "all" ? "All Traffic" : "Organic"}
              </button>
            ))}
          </div>
          <button
            onClick={handleRefresh}
            disabled={refreshing || dataLoading}
            className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-semibold text-slate-600 bg-white border border-slate-200 hover:bg-slate-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
            title="Refresh all dashboard data"
          >
            <RefreshCw className={`w-3.5 h-3.5 ${refreshing || dataLoading ? "animate-spin" : ""}`} />
            Refresh
          </button>
        </div>
        <div className="flex items-center gap-4 text-[11px] text-slate-400">
          <span className="font-medium text-slate-500">{activeDateRange.start} → {activeDateRange.end}</span>
          {lastUpdatedLabel && <span>{lastUpdatedLabel}</span>}
          {dataLoading && !refreshing && <span className="text-brand-500 animate-pulse">Loading...</span>}
          {ga4?.connected && ga4.lastSynced && (
            <span>GA4 synced: {new Date(ga4.lastSynced).toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}</span>
          )}
          {gsc?.connected && gsc.lastSynced && (
            <span>GSC synced: {new Date(gsc.lastSynced).toLocaleDateString("en-US", { month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}</span>
          )}
        </div>
      </div>

      {/* Inline error banner — shown on timeout / non-OK response so the
          user understands why KPI cards are stuck on their "not
          connected" states after a slow fetch. */}
      {loadError && (
        <div className="flex items-start gap-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-800">
          <span className="font-semibold">Heads up:</span>
          <span>{loadError}</span>
          <button
            onClick={handleRefresh}
            className="ml-auto font-semibold text-amber-900 hover:underline"
          >
            Retry
          </button>
        </div>
      )}

      {/* ══ LAYOUT CUSTOMIZER ═══════════════════════════════════════════════ */}
      {/* ── Layout presets + customize ── */}
      <div className="flex items-center gap-2">
        <div className="relative">
          <button onClick={() => setPresetDropdownOpen(!presetDropdownOpen)} className="flex items-center gap-1.5 px-3 py-1.5 border border-slate-200 rounded-lg text-xs font-semibold text-slate-600 hover:bg-slate-50">
            {activePreset === "custom" ? "Custom Layout" : PRESETS[activePreset]?.label ?? "Default Layout"}
            <ChevronDown className="w-3 h-3 text-slate-400" />
          </button>
          {presetDropdownOpen && (
            <>
              <div className="fixed inset-0 z-30" onClick={() => setPresetDropdownOpen(false)} />
              <div className="absolute top-full left-0 mt-1 bg-white border border-slate-200 rounded-lg shadow-lg z-40 py-1 w-48">
                {Object.entries(PRESETS).map(([key, p]) => (
                  <button key={key} onClick={() => applyPreset(key)} className={`w-full flex items-center gap-2 px-3 py-2 text-xs font-medium hover:bg-slate-50 ${activePreset === key ? "text-brand-600 bg-brand-50" : "text-slate-600"}`}>
                    <span className="w-3.5">{activePreset === key && <Check className="w-3.5 h-3.5" />}</span>
                    {p.label}
                  </button>
                ))}
                {activePreset === "custom" && (
                  <>
                    <div className="border-t border-slate-100 my-1" />
                    <div className="flex items-center gap-2 px-3 py-2 text-xs font-medium text-brand-600 bg-brand-50">
                      <span className="w-3.5"><Check className="w-3.5 h-3.5" /></span>
                      Custom
                    </div>
                  </>
                )}
              </div>
            </>
          )}
        </div>
        <button onClick={() => setCustomizeOpen(true)} className="flex items-center gap-1 px-3 py-1.5 border border-slate-200 rounded-lg text-xs font-semibold text-slate-600 hover:bg-slate-50">
          <Pencil className="w-3 h-3" /> Customize
        </button>
      </div>

      {/* Customize modal */}
      {customizeOpen && (
        <>
          <div className="fixed inset-0 z-40 bg-slate-900/30" onClick={() => setCustomizeOpen(false)} />
          <div className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 bg-white rounded-xl shadow-xl border border-slate-200 w-[340px] p-5" onClick={(e) => e.stopPropagation()}>
            <h3 className="text-sm font-bold text-slate-800">Customize Dashboard</h3>
            <p className="text-[11px] text-slate-400 mb-4">Choose which sections to display</p>
            <div className="space-y-0.5">
              {ALL_SECTIONS.map((s) => {
                const isKpi = s.id === "kpis";
                return (
                  <div key={s.id} className="flex items-center justify-between min-h-[48px] px-3 py-2.5 rounded-lg hover:bg-slate-50">
                    <span className="text-[13px] font-medium text-slate-700">
                      {s.label}
                      {isKpi && <span className="ml-2 text-[10px] text-slate-400 font-normal">Always visible</span>}
                    </span>
                    <button
                      onClick={() => !isKpi && toggleSection(s.id)}
                      disabled={isKpi}
                      className={`relative w-10 h-[22px] rounded-full transition-colors shrink-0 ml-3 ${isKpi ? "bg-brand-600 opacity-40 cursor-not-allowed" : visibleSections.has(s.id) ? "bg-brand-600" : "bg-slate-200"}`}
                    >
                      <span className={`absolute top-[3px] w-4 h-4 bg-white rounded-full shadow-sm transition-all ${isKpi || visibleSections.has(s.id) ? "left-[22px]" : "left-[3px]"}`} />
                    </button>
                  </div>
                );
              })}
            </div>
            <div className="mt-3 pt-3 border-t border-slate-100">
              <button disabled className="flex items-center gap-1.5 w-full px-3 py-2.5 rounded-lg text-xs font-medium text-slate-400 hover:bg-slate-50 cursor-default">
                <PlusIcon className="w-3.5 h-3.5" /> Add Section <span className="ml-auto text-[9px] text-slate-300">Coming soon</span>
              </button>
            </div>
            <div className="flex items-center justify-between mt-3 pt-3 border-t border-slate-100">
              <button onClick={() => applyPreset("full")} className="flex items-center gap-1 text-[11px] text-slate-400 hover:text-slate-600"><RotateCcw className="w-3 h-3" /> Reset to Default</button>
              <button onClick={() => setCustomizeOpen(false)} className="px-4 py-2 bg-slate-900 text-white text-xs font-semibold rounded-lg hover:bg-slate-800">Done</button>
            </div>
          </div>
        </>
      )}

      {/* ══ 2. KPI CARDS ══════════════════════════════════════════════════════
          Two rows:
            Row 1 — Traffic & Search: New Users · Sessions · Impressions · Clicks
            Row 2 — Search Perf & Health: Avg Position · Avg CTR · Crawl Health
          The old top-level "Performance" card (mobile web vitals) moves to
          the Technical section below — it's a subsystem metric, not a KPI.
      */}
      {(() => {
        const rangeLabel = range === "1d" ? "Today" : range === "7d" ? "7d" : range === "90d" ? "90d" : range === "month" ? "MTD" : range === "custom" ? "Custom" : "28d";
        // GA4-card subtitle. Adds "Organic" when the user has flipped
        // the traffic-channel toggle, and swaps in a helpful hint
        // when the server returned `source: "organic-live-only"` —
        // that state means the live GA4 fetch didn't come back and
        // we couldn't fall back to MetricSnapshot (which is
        // all-traffic). Telling the user "live sync required" is
        // more actionable than letting them read "0 Sessions" and
        // assume the toggle is broken.
        const ga4SourceLabel = (() => {
          if (ga4?.source === "organic-live-only") return "GA4 · Organic · live sync required";
          if (channel === "organic") return `GA4 · Organic · ${rangeLabel}`;
          return `GA4 · ${rangeLabel}`;
        })();
        const rangeRequested = totalDaysForRange(range, activeDateRange.start, activeDateRange.end);
        const gscSourceLabel = gsc?.fromDb ? `GSC · Synced data` : `GSC · ${rangeLabel}`;

        const isRecentRange = range === "1d" || range === "7d";
        const ga4Empty = ga4?.connected && (ga4.sessions ?? 0) === 0 && (ga4.newUsers ?? 0) === 0;
        const gscEmpty = gsc?.connected && (gsc.impressions ?? 0) === 0 && (gsc.clicks ?? 0) === 0;
        const ga4Hint = isRecentRange && ga4Empty ? "GA4 data may be delayed up to 48 hrs" : null;
        const gscHint = isRecentRange && gscEmpty ? "GSC data is typically 48-72 hrs behind" : null;

        return (
          <div className={`space-y-4 transition-opacity ${dataLoading ? "opacity-60" : ""}`}>
            {/* Row 1 — Traffic & Search Metrics (4 cards) */}
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
              <KpiCard
                label="New Users"
                value={formatNumber(ga4, (g) => g.newUsers)}
                source={ga4SourceLabel}
                delta={ga4?.deltas?.newUsers ?? null}
                icon={Users}
                loading={ga4 === null}
                notConnected={!!ga4 && !ga4.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={ga4Hint}
              />
              <KpiCard
                label="Sessions"
                value={formatNumber(ga4, (g) => g.sessions)}
                source={ga4SourceLabel}
                delta={ga4?.deltas?.sessions ?? null}
                icon={Activity}
                loading={ga4 === null}
                notConnected={!!ga4 && !ga4.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={ga4Hint}
              />
              <KpiCard
                label="Impressions"
                value={formatNumber(gsc, (g) => g.impressions)}
                source={gscSourceLabel}
                delta={gsc?.deltas?.impressions ?? null}
                icon={Eye}
                loading={gsc === null}
                notConnected={!!gsc && !gsc.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={gscHint}
              />
              <KpiCard
                label="Clicks"
                value={formatNumber(gsc, (g) => g.clicks)}
                source={gscSourceLabel}
                delta={gsc?.deltas?.clicks ?? null}
                icon={MousePointerClick}
                loading={gsc === null}
                notConnected={!!gsc && !gsc.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={gscHint}
              />
            </div>

            {/* Row 2 — Search Performance & Health (3 cards) */}
            <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
              <KpiCard
                label="Avg Position"
                value={gsc?.connected && (gsc.avgPosition ?? 0) > 0 ? (gsc.avgPosition ?? 0).toFixed(1) : "—"}
                source={gscSourceLabel}
                delta={gsc?.deltas?.avgPosition ?? null}
                icon={Hash}
                loading={gsc === null}
                notConnected={!!gsc && !gsc.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={gscHint}
              />
              <KpiCard
                label="Avg CTR"
                value={gsc?.connected ? `${(gsc.ctr ?? 0).toFixed(2)}%` : "—"}
                source={gscSourceLabel}
                delta={gsc?.deltas?.ctr ?? null}
                icon={Percent}
                loading={gsc === null}
                notConnected={!!gsc && !gsc.connected}
                connectHref="/integrations"
                href="/site-tag-analytics"
                hint={gscHint}
              />
              <KpiCard
                label="Crawl Health"
                value={audit?.connected ? `${audit.crawlHealth ?? 0}%` : "—"}
                source={audit?.connected ? `${audit.totalIssues ?? 0} open issues` : "Run audit"}
                delta={null}
                icon={ShieldCheck}
                loading={audit === null}
                notConnected={!audit?.connected}
                connectHref="/technical-audit"
                href="/technical-audit"
              />
            </div>
          </div>
        );
      })()}

      {/* ══ 2B. PAID MEDIA ═══════════════════════════════════════════════════ */}
      {show("paid_media") && paidMediaData && (() => {
        const pm = paidMediaData;
        const totalSess = pm.paidSessions + pm.organicSessions;
        const paidPct = totalSess > 0 ? Math.round((pm.paidSessions / totalSess) * 100) : 0;
        return (
          <div className="space-y-2">
            <div className="flex items-center gap-2">
              <Megaphone className="w-4 h-4 text-amber-600" />
              <h2 className="text-xs font-bold text-slate-800 uppercase tracking-widest">Paid Media</h2>
              <a href="/paid-media" className="ml-auto text-[10px] font-semibold text-brand-600 hover:underline flex items-center gap-1">
                View details <ChevronRight className="w-3 h-3" />
              </a>
            </div>
            <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
              <div className="bg-white rounded-xl border border-slate-200 px-4 py-3">
                <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Paid Sessions</p>
                <p className="text-xl font-bold text-slate-900 tabular-nums mt-0.5">{pm.paidSessions.toLocaleString()}</p>
                <p className="text-[10px] text-slate-500">{paidPct}% of traffic</p>
              </div>
              <div className="bg-white rounded-xl border border-slate-200 px-4 py-3">
                <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Assisted Conv.</p>
                <p className="text-xl font-bold text-violet-700 tabular-nums mt-0.5">{pm.assistedConversions}</p>
                <p className="text-[10px] text-slate-500">paid + organic paths</p>
              </div>
              <div className="bg-white rounded-xl border border-slate-200 px-4 py-3">
                <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Paid vs Organic</p>
                <p className="text-xl font-bold text-slate-900 tabular-nums mt-0.5">{pm.paidConversions} / {pm.organicConversions}</p>
                <p className="text-[10px] text-slate-500">paid / organic conv.</p>
              </div>
              <div className="bg-white rounded-xl border border-slate-200 px-4 py-3">
                <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Channel Mix</p>
                <div className="flex items-center gap-1 mt-1.5">
                  <div className="h-2 rounded-full bg-amber-500" style={{ width: `${paidPct}%`, minWidth: paidPct > 0 ? "4px" : "0" }} />
                  <div className="h-2 rounded-full bg-emerald-500 flex-1" />
                </div>
                <p className="text-[10px] text-slate-500 mt-1">
                  <span className="text-amber-600 font-semibold">{paidPct}%</span> paid · <span className="text-emerald-600 font-semibold">{100 - paidPct}%</span> organic
                </p>
              </div>
            </div>
          </div>
        );
      })()}

      {/* ══ 3. PROBLEMS / EXECUTION / RECOMMENDATIONS ═════════════════════════ */}
      {(show("technical_issues") || show("execution_hub") || show("recommendations")) && <div className="grid grid-cols-1 lg:grid-cols-3 gap-4">

        {/* Problems — from real audit */}
        {show("technical_issues") && <Link href="/technical-audit" className="bg-white rounded-xl border border-slate-200 overflow-hidden hover:border-slate-300 hover:shadow-sm transition-all">
          <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2 bg-red-50/60">
            <AlertTriangle className="w-4 h-4 text-red-500" />
            <h2 className="font-bold text-slate-800 text-sm">Technical Issues</h2>
            <ChevronRight className="w-3.5 h-3.5 text-slate-300 ml-auto" />
          </div>
          {(() => {
            // Prefer the audit-derived breakdown when an audit row exists;
            // otherwise compose a useful summary from cross-source counts
            // (signals + broken links). Only falls through to the
            // empty-state CTA when none of those data sources have rows.
            const hasAudit = audit?.connected ?? false;
            const signalCount = signalsData?.count ?? 0;
            const brokenCount = deadPages?.count ?? 0;
            const hasAnyData = hasAudit || signalCount > 0 || brokenCount > 0;
            if (!hasAnyData) {
              return (
                <div className="px-5 py-6 text-center">
                  <p className="text-xs text-slate-400">No issues detected yet — run a technical audit or wait for the next signal scan.</p>
                </div>
              );
            }
            return (
              <div className="px-5 py-4">
                {hasAudit && (
                  <>
                    <div className="flex items-center gap-3 mb-2">
                      <span className="w-2 h-2 rounded-full bg-red-500" />
                      <span className="flex-1 text-xs text-slate-700">Errors</span>
                      <span className="text-sm font-bold text-red-600">{audit?.errors ?? 0}</span>
                    </div>
                    <div className="flex items-center gap-3 mb-2">
                      <span className="w-2 h-2 rounded-full bg-amber-400" />
                      <span className="flex-1 text-xs text-slate-700">Warnings</span>
                      <span className="text-sm font-bold text-amber-600">{audit?.warnings ?? 0}</span>
                    </div>
                    {/* Notices row rendered when the audit has any — matches
                        the Technical Audit page's issue breakdown line-for-
                        line so users don't see different numbers in two
                        places. Hidden when noticeCount=0 to keep the card
                        visually balanced with the other summary cards. */}
                    {(audit?.notices ?? 0) > 0 && (
                      <div className="flex items-center gap-3 mb-2">
                        <span className="w-2 h-2 rounded-full bg-sky-400" />
                        <span className="flex-1 text-xs text-slate-700">Notices</span>
                        <span className="text-sm font-bold text-sky-600">{audit?.notices ?? 0}</span>
                      </div>
                    )}
                  </>
                )}
                {!hasAudit && signalCount > 0 && (
                  <div className="flex items-center gap-3 mb-2">
                    <span className="w-2 h-2 rounded-full bg-amber-400" />
                    <span className="flex-1 text-xs text-slate-700">Active signals</span>
                    <span className="text-sm font-bold text-amber-600">{signalCount}</span>
                  </div>
                )}
                {brokenCount > 0 && (
                  <div className="flex items-center gap-3">
                    <span className="w-2 h-2 rounded-full bg-slate-400" />
                    <span className="flex-1 text-xs text-slate-700">Broken links</span>
                    <span className="text-sm font-bold text-slate-600">{brokenCount}</span>
                  </div>
                )}
              </div>
            );
          })()}
        </Link>}

        {/* Execution Hub — from real tasks */}
        {show("execution_hub") && <Link href="/execution-hub" className="bg-white rounded-xl border border-slate-200 overflow-hidden hover:border-slate-300 hover:shadow-sm transition-all">
          <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2 bg-amber-50/60">
            <Zap className="w-4 h-4 text-amber-600" />
            <h2 className="font-bold text-slate-800 text-sm">Execution Hub</h2>
            <ChevronRight className="w-3.5 h-3.5 text-slate-300 ml-auto" />
          </div>
          {(() => {
            // Always render the breakdown when ANY task exists for the
            // brand — previously the card hid both rows whenever
            // overdue + completedThisWeek were both 0, which on a
            // brand with 30 in-progress (but not overdue) tasks read
            // as "No tasks yet" and looked broken. The total count
            // from the API distinguishes "0 tasks ever" from "tasks
            // exist but none in either of these specific buckets".
            const totalTasks = tasksData?.total ?? 0;
            const overdue = execution?.overdue ?? 0;
            const completedThisWeek = execution?.completedThisWeek ?? 0;
            // Audit-issue fallback. When the brand has run a technical
            // audit with findings but hasn't created tasks yet (most
            // brands never manually convert audit issues into tasks),
            // the card previously showed "No tasks yet" even though
            // there was clearly actionable work on the Technical Audit
            // page. Surface audit warnings+errors as an executable
            // unit so the card always matches what the user sees
            // elsewhere in the app.
            const auditOpenIssues = audit?.connected
              ? (audit?.openIssues ?? ((audit?.errors ?? 0) + (audit?.warnings ?? 0)))
              : 0;
            if (totalTasks === 0 && overdue === 0 && completedThisWeek === 0 && auditOpenIssues === 0) {
              return (
                <div className="px-5 py-6 text-center">
                  <p className="text-xs text-slate-400">No tasks yet</p>
                </div>
              );
            }
            if (totalTasks === 0 && auditOpenIssues > 0) {
              return (
                <div className="px-5 py-4">
                  <div className="flex items-center gap-3 mb-2">
                    <span className="w-2 h-2 rounded-full bg-amber-400" />
                    <span className="flex-1 text-xs text-slate-700">Open issues from last audit</span>
                    <span className="text-sm font-bold text-amber-600">{auditOpenIssues}</span>
                  </div>
                  <p className="text-[11px] text-slate-400 mt-2">Create tasks from the Technical Audit page to track fixes here.</p>
                </div>
              );
            }
            return (
              <div className="px-5 py-4">
                <div className="flex items-center gap-3 mb-2">
                  <span className={`w-2 h-2 rounded-full ${overdue > 0 ? "bg-red-500" : "bg-slate-300"}`} />
                  <span className="flex-1 text-xs text-slate-700">Overdue</span>
                  <span className={`text-sm font-bold ${overdue > 0 ? "text-red-600" : "text-slate-500"}`}>{overdue}</span>
                </div>
                <div className="flex items-center gap-3 mb-2">
                  <span className={`w-2 h-2 rounded-full ${completedThisWeek > 0 ? "bg-green-500" : "bg-slate-300"}`} />
                  <span className="flex-1 text-xs text-slate-700">Completed this week</span>
                  <span className={`text-sm font-bold ${completedThisWeek > 0 ? "text-green-600" : "text-slate-500"}`}>{completedThisWeek}</span>
                </div>
                <div className="flex items-center gap-3">
                  <span className="w-2 h-2 rounded-full bg-slate-400" />
                  <span className="flex-1 text-xs text-slate-700">Total tasks</span>
                  <span className="text-sm font-bold text-slate-600">{totalTasks}</span>
                </div>
              </div>
            );
          })()}
        </Link>}

        {/* Recommendations — from real audit data */}
        {show("recommendations") && <Link href="/recommendations" className="bg-white rounded-xl border border-slate-200 overflow-hidden hover:border-slate-300 hover:shadow-sm transition-all">
          <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2 bg-blue-50/60">
            <Lightbulb className="w-4 h-4 text-brand-600" />
            <h2 className="font-bold text-slate-800 text-sm">Recommendations</h2>
            <ChevronRight className="w-3.5 h-3.5 text-slate-300 ml-auto" />
          </div>
          {(() => {
            // Audit-derived "issues to review" is the strongest signal.
            // Without an audit we fall back to the active-signals count
            // — every signal is itself a recommended action — so the
            // card stays useful for brands relying on the signal-
            // detection cron rather than scheduled audits.
            //
            // totalIssues = errors + warnings + notices (same sum the
            // Technical Audit page displays in its summary row). The
            // previous version read audit.totalIssues as "open issues"
            // from the joined issues[] array, which sometimes returned
            // 0 even though the audit had findings — because the join
            // filtered on status="open" and issues may have flipped
            // past that state. Reading the stored aggregate fixes the
            // mismatch with /technical-audit.
            const hasAudit = audit?.connected ?? false;
            const auditCount = audit?.totalIssues ?? 0;
            const openCount = audit?.openIssues ?? auditCount;
            const signalCount = signalsData?.count ?? 0;
            if (hasAudit) {
              // Show the open count when it's a strict subset of total
              // (so users see "1 open of 3 total" instead of just "3"
              // when two were already acknowledged). Falls back to the
              // single number when open == total.
              const primary = openCount > 0 ? openCount : auditCount;
              const tail = openCount > 0 && openCount < auditCount
                ? `${openCount} open of ${auditCount} total`
                : "Based on latest technical audit";
              return (
                <div className="px-5 py-4">
                  <p className="text-sm text-slate-700">{primary} issue{primary === 1 ? "" : "s"} to review</p>
                  <p className="text-xs text-slate-400 mt-1">{tail}</p>
                </div>
              );
            }
            if (signalCount > 0) {
              return (
                <div className="px-5 py-4">
                  <p className="text-sm text-slate-700">{signalCount} active signal{signalCount === 1 ? "" : "s"}</p>
                  <p className="text-xs text-slate-400 mt-1">Each signal is a recommended action</p>
                </div>
              );
            }
            return (
              <div className="px-5 py-6 text-center">
                <p className="text-xs text-slate-400">No recommendations yet — run a technical audit or wait for the next signal scan.</p>
              </div>
            );
          })()}
        </Link>}

      </div>}

      {/* ══ 4. SOURCE STATUS GRID ════════════════════════════════════════════ */}
      {(show("ai_visibility") || show("competitors")) && <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">

        {/* AI Visibility — real source status */}
        {show("ai_visibility") && <SourceCard
          title="AI Visibility"
          icon={Bot}
          href="/ai-visibility"
          status={aiStatus?.status}
          detail={aiStatus?.detail}
          lastUpdated={aiStatus?.lastUpdated}
          emptyText="Set up AI visibility tracking"
          emptyHref="/integrations"
        />}

        {/* Competitors — real source status */}
        {show("competitors") && <SourceCard
          title="Competitors"
          icon={Users}
          href="/competitors"
          status={competitorStatus?.status}
          detail={competitorStatus?.detail}
          lastUpdated={competitorStatus?.lastUpdated}
          emptyText="Set up competitor tracking"
          emptyHref="/competitors"
        />}

      </div>}

      {/* Data maturity notice — auto-hides at 30+ days. Sits next to the
          Site Tag conversion / attribution cards so the user understands why
          early-install metrics may look thin. */}
      {siteTag?.installed && <DataMaturityNotice installedDays={siteTag.ageDays} installedAt={siteTag.installedAt} />}

      {/* ══ 4b. CONVERSIONS BY SOURCE (classified) ═══════════════════════════
          Full channel breakdown with drill-down drawer. Uses classifiedChannel
          from the channel classifier, not the raw Site Tag channel field. */}
      <ConversionsBySource range={range} />

      {/* ══ 5. SEARCH PERFORMANCE ══════════════════════════════════════════ */}
      {/* Reads from `searchPerf` (loaded lazily from
          /api/dashboard/search-performance), NOT gsc. The previous
          implementation merged topQueries/topPages into gsc.* — but
          when the lazy fetch resolved before the main dashboard fetch,
          the merge bailed because gsc was still null, and the data
          was lost. searchPerf has its own state + cache slot so
          rendering is decoupled from any provider race. */}
      {show("search_performance") && (() => {
        const topPages = searchPerf?.topPages ?? [];
        const topQueries = searchPerf?.topQueries ?? [];
        const hasData = topPages.length > 0 || topQueries.length > 0;
        // GSC connection status still drives the "Connect GSC" empty
        // state — searchPerf only resolves to data when the integration
        // is connected, so its absence (combined with gsc.connected =
        // false) means "not connected" rather than "loading".
        const gscConnected = gsc?.connected ?? false;
        const lazyLoading = searchPerf === null;
        return (
          <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
            <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2">
              <BarChart3 className="w-4 h-4 text-brand-600" />
              <h2 className="font-bold text-slate-800 text-sm">Search Performance</h2>
              {gscConnected && (
                <span className="ml-auto text-xs text-slate-400">
                  From GSC · {range === "7d" ? "Last 7 days" : range === "90d" ? "Last 90 days" : range === "month" ? "This month" : range === "custom" ? `${customStart} to ${customEnd}` : "Last 28 days"}
                  {searchPerf?.source === "stale" && " · stale"}
                </span>
              )}
            </div>

            {hasData ? (
              <div className="grid grid-cols-2 divide-x divide-slate-100">
                <div className="px-4 py-4">
                  <p className="text-[10px] font-bold uppercase tracking-widest text-green-600 mb-3 flex items-center gap-1">
                    <TrendingUp className="w-3 h-3" /> Top Pages
                  </p>
                  {topPages.length > 0 ? (
                    <ul className="space-y-3">
                      {topPages.map((p) => (
                        <li key={p.pageUrl}>
                          <div className="flex justify-between text-xs mb-1">
                            <span className="font-mono text-brand-600 truncate max-w-[70%]">{p.pageUrl}</span>
                            <span className="text-green-600 font-semibold">{p.clicks} clicks</span>
                          </div>
                          <div className="h-1 bg-slate-100 rounded-full overflow-hidden">
                            <div className="h-full bg-green-500 rounded-full" style={{ width: `${Math.min(100, (p.clicks / (topPages[0]?.clicks || 1)) * 100)}%` }} />
                          </div>
                        </li>
                      ))}
                    </ul>
                  ) : (
                    <p className="text-xs text-slate-400 text-center py-4">No page data yet</p>
                  )}
                </div>
                <div className="px-4 py-4">
                  <p className="text-[10px] font-bold uppercase tracking-widest text-brand-600 mb-3 flex items-center gap-1">
                    <Search className="w-3 h-3" /> Top Queries
                  </p>
                  {topQueries.length > 0 ? (
                    <ul className="space-y-3">
                      {topQueries.map((q) => (
                        <li key={q.query}>
                          <div className="flex justify-between text-xs mb-1">
                            <span className="text-slate-700 truncate max-w-[70%]">&ldquo;{q.query}&rdquo;</span>
                            <span className="text-brand-600 font-semibold">Pos {q.position}</span>
                          </div>
                          <div className="flex items-center gap-2">
                            <div className="h-1 flex-1 bg-slate-100 rounded-full overflow-hidden">
                              <div className="h-full bg-brand-500 rounded-full" style={{ width: `${Math.min(100, (q.clicks / (topQueries[0]?.clicks || 1)) * 100)}%` }} />
                            </div>
                            <span className="text-[10px] text-slate-400">{q.clicks}</span>
                          </div>
                        </li>
                      ))}
                    </ul>
                  ) : (
                    <p className="text-xs text-slate-400 text-center py-4">No query data yet</p>
                  )}
                </div>
              </div>
            ) : lazyLoading ? (
              <div className="px-5 py-8 text-center">
                <div className="w-6 h-6 border-2 border-brand-100 border-t-brand-600 rounded-full animate-spin mx-auto mb-2" />
                <p className="text-xs text-slate-400">Loading search performance…</p>
              </div>
            ) : gscConnected ? (
              <div className="px-5 py-8 text-center">
                <Database className="w-8 h-8 text-blue-200 mx-auto mb-2" />
                <p className="text-xs text-slate-500 font-semibold">{range === "custom" ? "No GSC data for this date range" : "GSC Connected — Awaiting Data"}</p>
                <p className="text-xs text-slate-400 mt-1">{range === "custom" ? "Try a different date range or check if GSC data has been synced for this period." : "Search performance data will appear after the first sync completes."}</p>
              </div>
            ) : (
              <div className="px-5 py-8 text-center">
                <Search className="w-8 h-8 text-slate-200 mx-auto mb-2" />
                <p className="text-xs text-slate-400">Connect Google Search Console to see search performance</p>
                <Link href="/integrations" className="text-xs text-brand-600 font-semibold hover:text-brand-700 mt-2 inline-block">
                  Connect GSC <ChevronRight className="w-3 h-3 inline" />
                </Link>
              </div>
            )}
          </div>
        );
      })()}

      {/* ══ 6. QUICK ACTIONS ══════════════════════════════════════════════════ */}
      {show("quick_actions") && <div className="bg-white rounded-xl border border-slate-200 overflow-hidden">
        <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2">
          <Zap className="w-4 h-4 text-brand-600" />
          <h2 className="font-bold text-slate-800 text-sm">Quick Actions</h2>
        </div>
        <div className="px-5 py-4 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
          {[
            { label: "Technical Audit",     icon: ShieldCheck, href: "/technical-audit" },
            { label: "Fix Issues",          icon: AlertCircle, href: "/technical-audit" },
            { label: "Create Content",      icon: FileText,    href: "/content-brief" },
            { label: "AI Visibility",       icon: Bot,         href: "/ai-visibility" },
            { label: "Signals Center",      icon: Bell,        href: "/signals" },
          ].map((a) => (
            <Link
              key={a.label}
              href={a.href}
              className="flex flex-col items-center gap-2 px-3 py-4 rounded-xl border border-slate-200 bg-slate-50 text-slate-600 text-xs font-semibold hover:bg-slate-100 hover:border-slate-300 transition-colors"
            >
              <a.icon className="w-5 h-5 text-slate-500" />
              {a.label}
            </Link>
          ))}
        </div>
      </div>}

    </div>
  );
}

// ─── Source Status Card ──────────────────────────────────────────────────────

function SourceCard({ title, icon: Icon, href, status, detail, lastUpdated, emptyText, emptyHref }: {
  title: string;
  icon: typeof Bot;
  href: string;
  status?: string;
  detail?: string | null;
  lastUpdated?: string | null;
  emptyText: string;
  emptyHref: string;
}) {
  const isConnected = status === "connected" || status === "stale";
  return (
    <Link href={isConnected ? href : emptyHref} className="bg-white rounded-xl border border-slate-200 overflow-hidden hover:border-slate-300 hover:shadow-sm transition-all">
      <div className="px-5 py-3.5 border-b border-slate-100 flex items-center gap-2">
        <Icon className="w-4 h-4 text-brand-600" />
        <h2 className="font-bold text-slate-800 text-sm">{title}</h2>
        {isConnected && (
          <span className="ml-auto text-[10px] font-semibold text-green-600 bg-green-50 border border-green-200 px-2 py-0.5 rounded-full">Connected</span>
        )}
        <ChevronRight className="w-3.5 h-3.5 text-slate-300 ml-auto" />
      </div>
      {isConnected ? (
        <div className="px-5 py-4">
          {detail && <p className="text-sm text-slate-700">{detail}</p>}
          {lastUpdated && (
            <p className="text-xs text-slate-400 mt-1">
              Last updated: {new Date(lastUpdated).toLocaleDateString("en-US", { month: "short", day: "numeric" })}
            </p>
          )}
          {!detail && !lastUpdated && <p className="text-xs text-slate-400">Data available</p>}
        </div>
      ) : (
        <div className="px-5 py-6 text-center">
          <Database className="w-6 h-6 text-slate-200 mx-auto mb-2" />
          <p className="text-xs text-slate-400">{status === "running" ? "Collecting data..." : emptyText}</p>
        </div>
      )}
    </Link>
  );
}

// ─── KPI Card ──────────────────────────────────────────────────────────────
// Single unified card used by the top-of-dashboard KPI rows. Renders label,
// value, source badge, delta arrow (when prev-period data is available), and
// graceful loading / not-connected / empty states.

function KpiCard({
  label,
  value,
  source,
  delta,
  icon: Icon,
  loading,
  notConnected,
  connectHref,
  href,
  hint,
}: {
  label: string;
  value: string;
  source: string;
  delta: Delta;
  icon: LucideIcon;
  loading: boolean;
  notConnected: boolean;
  connectHref: string;
  href: string;
  hint?: string | null;
}) {
  const effectiveHref = notConnected ? connectHref : href;
  return (
    <Link
      href={effectiveHref}
      className="bg-white rounded-xl border border-slate-200 px-5 py-4 flex flex-col gap-2 hover:border-slate-300 hover:shadow-sm transition-all"
    >
      <div className="flex items-center justify-between">
        <p className="text-xs font-semibold text-slate-500 uppercase tracking-wide">{label}</p>
        <Icon className="w-4 h-4 text-slate-300" />
      </div>
      <p className="text-3xl font-bold text-slate-900 leading-none tabular-nums">
        {loading ? "…" : value}
      </p>
      {hint && <p className="text-[10px] text-slate-400 leading-snug -mt-1">{hint}</p>}
      <div className="flex items-center justify-between gap-2">
        <span className="text-[11px] font-medium text-slate-400 truncate">{source}</span>
        {delta && delta.direction !== "flat" ? (
          <span
            className={`flex items-center gap-0.5 text-[11px] font-bold ${
              delta.direction === "up" ? "text-green-600" : "text-red-500"
            }`}
          >
            {delta.direction === "up" ? <ArrowUp className="w-3 h-3" /> : <ArrowDown className="w-3 h-3" />}
            {delta.pct}%
          </span>
        ) : (
          <ChevronRight className="w-3.5 h-3.5 text-slate-300 shrink-0" />
        )}
      </div>
    </Link>
  );
}

// ─── Small helpers ─────────────────────────────────────────────────────────

/** Format a numeric metric from a source object. Returns "—" when the source
 *  is disconnected, "…" when still loading, or the formatted value otherwise. */
function formatNumber<T extends { connected: boolean }>(
  source: T | null,
  getter: (s: T) => number | undefined,
): string {
  if (source === null) return "…";
  if (!source.connected) return "—";
  const n = getter(source);
  if (n === undefined || n === null) return "0";
  return n.toLocaleString();
}

/** Compute the requested day count for the active range — used to build the
 *  "X of Y days available" coverage label. */
function totalDaysForRange(
  range: "1d" | "7d" | "28d" | "90d" | "month" | "custom",
  customStart: string,
  customEnd: string,
): number {
  if (range === "1d") return 1;
  if (range === "7d") return 7;
  if (range === "28d") return 28;
  if (range === "90d") return 90;
  if (range === "month") {
    const now = new Date();
    return Math.ceil((now.getTime() - new Date(now.getFullYear(), now.getMonth(), 1).getTime()) / 86400000) + 1;
  }
  // custom
  if (!customStart || !customEnd) return 0;
  return Math.max(1, Math.round((new Date(customEnd).getTime() - new Date(customStart).getTime()) / 86400000) + 1);
}
