"use client";

import {
  TrendingUp, Eye, Users, Percent, DollarSign, BarChart3,
  Info, Activity, Clock, FileText, Share2, ExternalLink,
  Code2, Database, Layers, ChevronDown, ChevronRight,
  Phone, MessageSquare, Mail, ShoppingCart, CalendarCheck,
  ClipboardCheck, Package, MailPlus,
  Target, Download, CreditCard, Bell, CalendarPlus, CalendarClock, Pencil,
  Plug, Hourglass, Settings2,
} from "lucide-react";
import Link from "next/link";
import { useState } from "react";
import {
  ResponsiveContainer, AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid,
  Tooltip as RTooltip,
} from "recharts";
import { SummaryCard, CardShell, SectionState, Skeleton } from "./_shared";
import { DataMaturityNotice } from "@/components/ui/data-maturity-notice";
import { eventTypeLabel } from "@/lib/event-type-labels";
import { ConfidenceBadge } from "@/components/site-tag/confidence-badge";

// ─── Types ──────────────────────────────────────────────────────────────────

interface ConversionData {
  dataOrigin: "conversion_event" | "metric_snapshot" | "none";
  totals: {
    conversions: number;
    revenue: number;
    avgValue: number;
    sessions: number;
    conversionRate: number;
  };
  changes: {
    conversions: { pct: number; direction: string };
    revenue: { pct: number; direction: string };
    conversionRate: { pct: number; direction: string };
    sessions: { pct: number; direction: string };
  };
  dailyTrend: Array<{ date: string; count: number | null; revenue: number; siteTagCount?: number }>;
  freshness?: {
    ga4: { lastSyncDate: string | null; lastUpdatedAt: string | null; staleHours: number | null; isStale: boolean };
    siteTag: { lastEventAt: string | null; isStale: boolean };
  };
}

interface TrafficData {
  pageViews: number;
  sessions: number;
  pagesPerSession: number;
  topPages: Array<{ url: string; views: number }>;
  byReferrer: Array<{ source: string; views: number }>;
  byUtmSource?: Array<{ source: string; views: number }>;
}

interface ConversionSummary {
  totalConversions: number;
  /** Raw row count across the window (pre-dedup). When > totalConversions,
   *  the UI renders "237 conversions (579 total interactions)" so the
   *  engagement depth signal stays visible without inflating the KPI. */
  totalInteractions?: number;
  byType: Record<string, number>;
  byTypeInteractions?: Record<string, number>;
  conversionRate: number;
}

interface TagStatus {
  installed: boolean;
  status: string | null;
  totalEvents: number;
  /** Real COUNT(*) when resolved inside the 5s race window on the
   *  analytics API; null when the count timed out and totalEvents
   *  fell back to a capped 10-row sample length. Prefer this for
   *  "lifetime total" displays so a site with millions of events
   *  doesn't render as 10. */
  lifetimeEventsExact?: number | null;
  lastEventAt: string | null;
}

interface DailyPoint { date: string; pageViews: number; sessions: number }
interface RecentEvent { id: string; eventType: string; pageUrl: string; timestamp: string; synthetic?: boolean }
interface OutboundDestination {
  domain: string;
  url: string;
  text: string;
  count: number;
  /** The most-common brand page users were on when they clicked
   *  this destination. Relative path ("/providers/dr-smith"), null
   *  when the aggregator couldn't resolve a source. Optional because
   *  legacy cached responses shipped before commit a9b42d8 don't
   *  carry it. */
  sourcePage?: string | null;
}
interface OutboundSummary { totalClicks: number; topDestinations: OutboundDestination[] }

export interface ReconciliationData {
  totalEvents: number;
  duplicatesRemoved: number;
  reconciledEvents: number;
  unmatchedEvents: number;
  confidenceScore: number;
  lastReconciledAt: string;
  bySource: Array<{ source: string; label: string; events: number; matchRate: number; status: string }>;
  reconciledStats: { matched: number; matchedPct: number; unmatched: number; unmatchedPct: number; duplicates: number; duplicatesPct: number };
  eventTable: Array<{ eventType: string; sources: string[]; canonical: string; confidence: number; journey: string; count: number }>;
}

export interface DetectedPlugin {
  name: string;
  key: string;
  firstSeenAt: string;
  lastSeenAt: string;
  trackingLevel: "full" | "limited";
  trackingNote: string;
}

export interface FormBookingFunnel {
  intent: number;
  started: number;
  submitted: number;
  confirmed: number;
  dropoffStarted: number | null;
  dropoffSubmitted: number | null;
  dropoffConfirmed: number | null;
  // 3-stage conversion-funnel rollup aligned with the platform
  // taxonomy (Engagement → Soft Conversions → Hard Conversions).
  // Optional so older cached payloads still render the legacy
  // 4-stage card when the new fields are missing.
  engagement?: number;
  actions?: number;
  conversions?: number;
  dropoffToActions?: number | null;
  dropoffToConversions?: number | null;
  channels?: {
    // Engagement tier — TrackedEvent raw counts (intent signals
    // that never write a SiteConversion side-effect).
    appointmentIntents: number;
    bookingOpens: number;
    bookingSteps: number;
    formStarts: number;
    productViews: number;
    // Soft conversions tier — SiteConversion deduped counts.
    addToCarts: number;
    checkoutStarteds: number;
    fileDownloads: number;
    newsletterSignups: number;
    waitlistSignups: number;
    bookingSubmits: number;
    // Hard conversions tier — SiteConversion deduped counts.
    formSubmits: number;
    phoneCalls: number;
    bookingConfirmed: number;
    purchases: number;
    smsClicks: number;
    emailClicks: number;
    // AI-detected counts per type from SiteConversion.detectionMethod
    aiDetected?: {
      formSubmits: number;
      phoneCalls: number;
      bookingConfirmed: number;
      purchases: number;
      smsClicks: number;
      emailClicks: number;
      bookingSubmits: number;
    };
  };
}

export interface OverviewProps {
  ga4: ConversionData | null;
  traffic: TrafficData | null;
  conversions: ConversionSummary | null;
  hasTag: boolean;
  hasGa4: boolean;
  tagStatus: TagStatus | null;
  dailyPageViews: DailyPoint[];
  recentEvents: RecentEvent[];
  eventsToday: number;
  eventsThisWeek: number;
  activePagesCount: number;
  outboundClicks: OutboundSummary;
  todayByType?: Array<{ type: string; count: number }>;
  /** Count of distinct custom event types observed for this brand */
  customEventsCount?: number;
  /** Reconciliation summary across Site Tag / GA4 / GSC / GTM / CRM */
  reconciliation?: ReconciliationData | null;
  /** Days since the Site Tag first captured data for this brand */
  siteTagAge?: number;
  /** ISO timestamp of the first captured event — drives the "since DATE"
   *  string in the data-maturity banner. Null when no events have been
   *  captured yet (in that case render the "waiting for first event"
   *  state using siteTagDeployedAt). */
  siteTagInstalledAt?: string | null;
  /** TrackedSite row-creation timestamp — when the user deployed the
   *  Site Tag, regardless of whether any events have been captured.
   *  Used to render the "Site Tag deployed X ago — waiting for first
   *  event" banner for brands whose tag is installed but hasn't yet
   *  recorded data. Null when no TrackedSite exists. */
  siteTagDeployedAt?: string | null;
  range?: string;
  /** Brand cuid — powers the self-fetching ConfidenceBadge. Optional
   *  so older callers that don't pass it degrade to no badge rather
   *  than crash. */
  brandId?: string;
  /** Booking / form plugins detected on the site via plugin_detected
   *  events. Surfaces near the Site Tag Active badge as a quick "we
   *  see your booking stack" signal. */
  pluginsDetected?: DetectedPlugin[];
  /** Intent → Started → Submitted → Confirmed funnel for the active
   *  range. Renders the "Form & Booking Funnel" card. */
  formFunnel?: FormBookingFunnel;
  /** Per-brand tier breakdown (completed / intent / signal) for the
   *  Conversions card. Null until the analytics response arrives. */
  conversionTiers?: {
    counted: number;
    byTier: Record<string, { total: number; counted: number }>;
    byType: Record<string, { count: number; tier: string; counted: boolean; label: string }>;
  } | null;
  /** Callback to open the conversion counting settings modal. */
  onOpenConvSettings?: () => void;
  /** True while the heavy analytics fetch is still in-flight (server returned
   *  a partial response). Gates empty-state messages so sections that are
   *  empty only because data hasn't arrived yet show skeleton loaders instead. */
  isAnalyticsLoading?: boolean;
  /** True while the post-save analytics refetch is in-flight after the
   *  user configures conversions. Shows a subtle loading overlay on the
   *  conversions card only. */
  convUpdating?: boolean;
}

// ─── Tooltip copy ───────────────────────────────────────────────────────────

// Both the Conversions KPI card and the Conversion Funnel's
// Hard + Soft totals now read from the SiteConversion table (the
// resolver-deduped source). The funnel's Hard + Soft should sum to
// this card's value (or be extremely close — off-by-one can happen
// when a conversion lands on a boundary second). Engagement is
// separate — it counts pre-conversion intent signals that never
// write a SiteConversion row.
const TAG_CONV_TOOLTIP =
  "Unique conversions in the SiteConversion table across this window, deduplicated by the 30-second resolver (same session + same page + same canonical type = one conversion). Includes every Tier 1 (Hard) and Tier 2 (Soft) conversion: form_submit, click_to_call, booking_confirmed, purchase, email_click, sms_click, booking_submit, add_to_cart, checkout_started, file_download, newsletter_signup, waitlist_signup.\n\nThe Conversion Funnel below uses the SAME source for its Hard + Soft totals — the two numbers reconcile. Engagement in the funnel counts pre-conversion intent signals (appointment_intent, booking_opened, form_start, etc.) that never write a SiteConversion row.";

const GA4_CONV_TOOLTIP = "GA4 conversion events synced from Google Analytics.";

// ─── Overview Tab ───────────────────────────────────────────────────────────

export function OverviewTab({
  ga4, traffic, conversions, hasTag, hasGa4, tagStatus,
  dailyPageViews, recentEvents, eventsToday, eventsThisWeek, activePagesCount,
  outboundClicks, todayByType = [], customEventsCount = 0, reconciliation = null,
  siteTagAge = 0, siteTagInstalledAt = null, siteTagDeployedAt = null, range,
  pluginsDetected = [], formFunnel, brandId, conversionTiers, onOpenConvSettings,
  isAnalyticsLoading = false, convUpdating = false,
}: OverviewProps) {
  const tagSessions = traffic?.sessions ?? 0;
  const tagPageViews = traffic?.pageViews ?? 0;
  const tagConversions = conversions?.totalConversions ?? 0;
  // Raw-row count across the window — when > tagConversions, dedup has
  // collapsed repeat clicks (9 phone clicks in one session = 1 unique
  // conversion + 9 interactions). Falls back to tagConversions so the
  // "N total interactions" subtitle is suppressed on a fresh-shape API
  // response that doesn't carry the field.
  const tagInteractions = conversions?.totalInteractions ?? tagConversions;
  const tagConvRate = conversions?.conversionRate ?? 0;

  const ga4Conversions = ga4?.totals.conversions ?? 0;
  const ga4Revenue = ga4?.totals.revenue ?? 0;
  const ga4ConvRate = ga4?.totals.conversionRate ?? 0;
  const ga4Sessions = ga4?.totals.sessions ?? 0;
  // Only show Revenue / Avg Value when GA4 actually reports non-zero revenue.
  // Otherwise swap in Sessions + a hint so the grid isn't wasted on $0 cards.
  const hasRevenue = ga4Revenue > 0 || (ga4?.totals.avgValue ?? 0) > 0;

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

      {/* Source badges — canonical layout: every badge slot always renders.
          State-based labels keep the page consistent across brands so the
          row never collapses, never reorders, and never silently drops a
          slot. Disconnected reads neutral slate, not red. */}
      <div className="flex items-center gap-2 flex-wrap">
        {(() => {
          // Slot 1: Site Tag status — Active / Calibrating / Not Deployed
          if (!hasTag) {
            return (
              <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-500 border border-slate-200 rounded-full px-2.5 py-1">
                <span className="w-1.5 h-1.5 rounded-full bg-slate-300" /> Site Tag Not Deployed
              </span>
            );
          }
          const lastEvent = tagStatus?.lastEventAt ? new Date(tagStatus.lastEventAt) : null;
          const hoursAgo = lastEvent ? (Date.now() - lastEvent.getTime()) / (1000 * 60 * 60) : Infinity;
          if (!lastEvent) {
            return (
              <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-600 border border-slate-200 rounded-full px-2.5 py-1">
                <span className="w-1.5 h-1.5 rounded-full bg-slate-400" /> Site Tag Calibrating
              </span>
            );
          }
          if (hoursAgo > 48) {
            return (
              <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-amber-50 text-amber-700 border border-amber-200 rounded-full px-2.5 py-1">
                <span className="w-1.5 h-1.5 rounded-full bg-amber-500" /> Site Tag Inactive — no data received recently
              </span>
            );
          }
          if (hoursAgo <= 24) {
            return (
              <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-emerald-50 text-emerald-700 border border-emerald-200 rounded-full px-2.5 py-1">
                <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" /> Site Tag Active
              </span>
            );
          }
          return (
            <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-600 border border-slate-200 rounded-full px-2.5 py-1">
              <span className="w-1.5 h-1.5 rounded-full bg-slate-400" /> Site Tag Installed
            </span>
          );
        })()}

        {/* Slot 2: GA4 status — Connected / Not Connected. Click-through
            to /integrations on the disconnected pill so the user can
            wire it up without hunting through nav. */}
        {hasGa4 ? (
          <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-blue-50 text-blue-700 border border-blue-200 rounded-full px-2.5 py-1">
            <span className="w-1.5 h-1.5 rounded-full bg-blue-500" /> GA4 Connected
          </span>
        ) : (
          <Link
            href="/integrations"
            className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-500 border border-slate-200 rounded-full px-2.5 py-1 hover:bg-slate-100 hover:text-slate-700 transition-colors"
          >
            <Plug className="w-3 h-3" /> GA4 Not Connected
          </Link>
        )}

        {/* Slot 3: CRM / booking-stack detection */}
        {pluginsDetected.length > 0 ? (
          <span
            className="inline-flex items-center gap-1.5 text-[11px] font-semibold bg-violet-50 text-violet-700 border border-violet-200 rounded-full px-2.5 py-1"
            title={pluginsDetected.map((p) => `${p.name} — ${p.trackingNote}`).join("\n")}
          >
            <span className="w-1.5 h-1.5 rounded-full bg-violet-500" />
            Detected:{" "}
            <span className="font-bold">
              {pluginsDetected.slice(0, 3).map((p) => p.name).join(", ")}
              {pluginsDetected.length > 3 ? ` +${pluginsDetected.length - 3}` : ""}
            </span>
          </span>
        ) : (
          <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-500 border border-slate-200 rounded-full px-2.5 py-1">
            <span className="w-1.5 h-1.5 rounded-full bg-slate-300" /> No CRM Detected
          </span>
        )}

        {/* Slot 4: Data confidence — ConfidenceBadge self-renders a
            "Calibrating" state when score is null, so it's safe to
            always render once we have a brandId. Without one (e.g.
            initial render before brand context resolves), we ship a
            placeholder so the slot still occupies its space. */}
        {brandId ? (
          <ConfidenceBadge
            brandId={brandId}
            range={(range === "1d" || range === "7d" || range === "30d" || range === "90d") ? range : "30d"}
          />
        ) : (
          <span className="inline-flex items-center gap-1 text-[11px] font-semibold bg-slate-50 text-slate-500 border border-slate-200 rounded-full px-2.5 py-1">
            <span className="w-1.5 h-1.5 rounded-full bg-slate-300" /> Confidence: Calibrating
          </span>
        )}
      </div>

      {/* History bar — DataMaturityNotice handles 0-29 day stages and
          renders nothing once data is mature (30+ days) or when the
          install timestamp hasn't resolved yet. We always reserve the
          slot, falling back to a "since" line for mature brands and a
          calibrating state when the tag isn't installed yet, so the
          row is part of the canonical layout for every brand. */}
      {hasTag && siteTagAge > 0 && siteTagAge < 30 && siteTagInstalledAt && (
        <DataMaturityNotice installedDays={siteTagAge} installedAt={siteTagInstalledAt} />
      )}
      {hasTag && siteTagAge >= 30 && siteTagInstalledAt && (
        <div className="flex items-start gap-3 rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-3" role="status">
          <Hourglass className="w-4 h-4 mt-0.5 shrink-0 text-emerald-600" />
          <div className="flex-1 min-w-0">
            <div className="flex items-center justify-between gap-3 flex-wrap">
              <p className="text-sm font-semibold text-emerald-900">
                Site Tag has been capturing data since {new Date(siteTagInstalledAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })} ({siteTagAge} days)
              </p>
              <span className="text-[10px] font-bold uppercase tracking-widest text-emerald-900 opacity-70">
                Mature
              </span>
            </div>
            <p className="text-xs mt-1 leading-relaxed text-emerald-900 opacity-90">
              Day-over-day, week-over-week, and month-over-month comparisons are all available.
            </p>
            <div className="mt-2.5">
              <div className="flex items-center gap-2">
                <div className="flex-1 h-1.5 rounded-full bg-white/70 overflow-hidden">
                  <div className="h-full bg-emerald-500 transition-all" style={{ width: "100%" }} />
                </div>
                <span className="text-[10px] font-semibold tabular-nums text-emerald-900 shrink-0">
                  {siteTagAge}/30 days · 100%
                </span>
              </div>
            </div>
          </div>
        </div>
      )}
      {/* Tag deployed but no events yet — bridge state between
          "not deployed" and the data-maturity stages. siteTagAge
          is 0 here because getSiteTagInstalledAt returns null when
          no events exist; siteTagDeployedAt comes from
          TrackedSite.createdAt and tells us how long the tag has
          been installed. Without this state the banner would either
          disappear entirely or backdate to TrackedSite.createdAt
          as the pretend-first-event date (the prior bug). */}
      {!isAnalyticsLoading && hasTag && siteTagAge === 0 && siteTagDeployedAt && (() => {
        const deployedAt = new Date(siteTagDeployedAt);
        const ageMs = Date.now() - deployedAt.getTime();
        const ageMins = Math.floor(ageMs / 60_000);
        const ageHrs = Math.floor(ageMs / 3_600_000);
        const ageDays = Math.floor(ageMs / 86_400_000);
        const ageLabel =
          ageMins < 1 ? "just now" :
          ageMins < 60 ? `${ageMins} ${ageMins === 1 ? "minute" : "minutes"} ago` :
          ageHrs < 24 ? `${ageHrs} ${ageHrs === 1 ? "hour" : "hours"} ago` :
          `${ageDays} ${ageDays === 1 ? "day" : "days"} ago`;
        return (
          <div className="flex items-start gap-3 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3" role="status">
            <Hourglass className="w-4 h-4 mt-0.5 shrink-0 text-blue-600" />
            <div className="flex-1 min-w-0">
              <div className="flex items-center justify-between gap-3 flex-wrap">
                <p className="text-sm font-semibold text-blue-900">
                  Site Tag installed {ageLabel} — waiting for first event
                </p>
                <span className="text-[10px] font-bold uppercase tracking-widest text-blue-900 opacity-70">
                  Calibrating
                </span>
              </div>
              <p className="text-xs mt-1 leading-relaxed text-blue-900 opacity-90">
                Once a visitor triggers an event on your site, this banner will switch to tracking baseline maturity (7 / 14 / 30 day milestones).
              </p>
            </div>
          </div>
        );
      })()}
      {!hasTag && (
        <div className="flex items-start gap-3 rounded-xl border border-slate-200 bg-slate-50 px-4 py-3" role="status">
          <Hourglass className="w-4 h-4 mt-0.5 shrink-0 text-slate-500" />
          <div className="flex-1 min-w-0">
            <p className="text-sm font-semibold text-slate-700">Site Tag not yet capturing data</p>
            <p className="text-xs mt-1 leading-relaxed text-slate-500">
              Once the meSEO Site Tag is installed, this banner will track baseline maturity (7 / 14 / 30 day milestones).
            </p>
          </div>
        </div>
      )}

      {/* Health summary row — 6 equal cards always rendered. When the
          tag isn't deployed each card shows a clean zero so the layout
          never collapses. */}
      <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4">
        <SummaryCard label="Events Today" value={hasTag ? eventsToday.toLocaleString() : "0"} icon={Activity} iconColor="text-emerald-500" />
        <SummaryCard label="This Week" value={hasTag ? eventsThisWeek.toLocaleString() : "0"} icon={TrendingUp} iconColor="text-blue-500" />
        <SummaryCard label="Active Pages" value={hasTag ? activePagesCount.toLocaleString() : "0"} icon={FileText} iconColor="text-violet-500" />
        <SummaryCard
          label="Outbound Clicks"
          value={hasTag ? outboundClicks.totalClicks.toLocaleString() : "0"}
          icon={ExternalLink}
          iconColor="text-amber-500"
        />
        <SummaryCard
          label="Event Types Firing"
          value={hasTag ? todayByType.length.toLocaleString() : "0"}
          icon={Code2}
          iconColor="text-fuchsia-500"
          subtitle={
            !hasTag
              ? "Site Tag not deployed"
              : customEventsCount > 0
                ? `${customEventsCount} custom`
                : todayByType.length === 1 ? "distinct type" : "distinct types"
          }
        />
        <SummaryCard
          label="Last Event"
          value={tagStatus?.lastEventAt ? formatRelative(tagStatus.lastEventAt) : "—"}
          icon={Clock}
          iconColor="text-slate-400"
        />
      </div>

      {/* Event Types Firing — merged view of TrackedEvent counts and
          SiteConversion-based intelligent detections. For conversion
          types (form_submit, click_to_call, etc.) we use the HIGHER
          of the two sources so the numbers here are always >= the
          Conversion Breakdown below. This prevents the counter-
          intuitive "deduped > raw" display that confused customers.
          Always rendered; empty state when no events have fired yet. */}
      {isAnalyticsLoading && todayByType.length === 0 ? (
        <div className="space-y-1.5">
          <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">Event Types Firing</p>
          <div className="flex items-center gap-2 pb-1">
            {[0, 1, 2].map((i) => (
              <div key={i} className="bg-white rounded-lg border border-slate-200 px-3 py-2 min-w-[110px] shrink-0">
                <Skeleton className="h-2 w-14 mb-2" />
                <Skeleton className="h-5 w-10" />
              </div>
            ))}
          </div>
        </div>
      ) : (!hasTag || todayByType.length === 0) ? (
        <div className="space-y-1.5">
          <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">Event Types Firing</p>
          <SectionState
            variant={hasTag ? "no_data" : "calibrating"}
            title={hasTag ? "No events fired today yet" : "Site Tag not deployed"}
            description={
              hasTag
                ? "When events fire, each type will appear here as a chip with its share of today's volume."
                : "Once the meSEO Site Tag is installed, the event types it captures will appear here."
            }
            cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
          />
        </div>
      ) : (() => {
        // Merge intelligent detections into the raw event counts.
        // SiteConversion includes form-phone.js detections (tel: links,
        // phone patterns) that never create a TrackedEvent row.
        const channels = formFunnel?.channels;
        const convOverrides: Record<string, number> = {};
        if (channels) {
          if (channels.formSubmits > 0) convOverrides["form_submit"] = channels.formSubmits;
          if (channels.phoneCalls > 0) {
            convOverrides["click_to_call"] = channels.phoneCalls;
            convOverrides["phone_call"] = channels.phoneCalls;
          }
          if (channels.bookingConfirmed > 0) convOverrides["booking_confirmed"] = channels.bookingConfirmed;
        }

        const mergedTypes = todayByType.map(({ type, count }) => ({
          type,
          count: convOverrides[type] != null ? Math.max(count, convOverrides[type]) : count,
        }));
        const total = mergedTypes.reduce((s, t) => s + t.count, 0);

        return (
          <div className="space-y-1.5">
            <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">Event Types Firing</p>
            <p className="text-[10px] text-slate-400">
              Includes AI-detected conversions from phone number patterns and form elements.
            </p>
            <div className="flex items-center gap-2 overflow-x-auto pb-1">
              {mergedTypes.map(({ type, count }) => {
                const pct = total > 0 ? Math.round((count / total) * 100) : 0;
                return (
                  <div
                    key={type}
                    className="bg-white rounded-lg border border-slate-200 px-3 py-2 min-w-[110px] shrink-0"
                    title={`${eventTypeLabel(type)}: ${count.toLocaleString()}`}
                  >
                    <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide truncate">{eventTypeLabel(type)}</p>
                    <div className="flex items-baseline gap-1.5 mt-0.5">
                      <span className="text-lg font-bold text-slate-900 tabular-nums">{count.toLocaleString()}</span>
                      <span className="text-[10px] text-slate-400 tabular-nums">{pct}%</span>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })()}

      {/* Conversions — Total + duplicates filtered + per-type breakdown.
          Replaces the legacy 4-stage form-only funnel so visitors who
          convert via phone / SMS / email / cart paths actually appear.
          Always rendered; when there's no tag or no conversions yet,
          shows an empty state in the same card chrome. */}
      {isAnalyticsLoading && !formFunnel ? (
        <CardShell title="Conversions" icon={Activity}>
          <div className="px-5 py-5 space-y-3.5">
            {[0, 1, 2].map((i) => (
              <div key={i} className="flex items-center justify-between gap-4">
                <Skeleton className="h-3 w-28" />
                <Skeleton className="h-3 w-14" />
              </div>
            ))}
          </div>
        </CardShell>
      ) : (!hasTag || !formFunnel || (tagConversions <= 0 && tagInteractions <= 0)) ? (
        <CardShell title="Conversions" icon={Activity}>
          <SectionState
            chrome="bare"
            variant={hasTag ? "no_data" : "calibrating"}
            title={hasTag ? "No conversions captured yet" : "Site Tag not deployed"}
            description={
              hasTag
                ? "When the Site Tag captures form submits, phone calls, bookings, or other conversions, the totals and per-type breakdown will appear here."
                : "Install the meSEO Site Tag to start capturing form submits, phone calls, bookings, and other conversions."
            }
            cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
          />
        </CardShell>
      ) : (() => {
        const channels = formFunnel.channels;
        const dupsFiltered = tagInteractions > tagConversions ? tagInteractions - tagConversions : 0;

        // Build per-type breakdown from channels (hard + soft combined)
        // and compute per-type duplicate counts.
        //
        // dupsFiltered = totalInteractions - totalConversions, where both
        // come from getConversionSummary()'s in-memory (session+page+canonical)
        // dedup over SiteConversion rows. Per-type dupes must use the SAME
        // source: byTypeInteractions[canonical] - byType[canonical] (the raw
        // SiteConversion row count minus the in-memory-deduped count per type).
        //
        // The previous formula used todayByType (TrackedEvent counts) as the
        // "raw" side. TrackedEvent can be orders of magnitude larger than
        // SiteConversion (e.g. booking_submit fires for every widget open/step,
        // but only one SiteConversion is written per submission) — so the gap
        // between them is NOT duplicates; it's events that never became
        // conversions. That gap (e.g. 558 for Booking Submit) showed up
        // in the "duplicate interactions filtered" banner, inflating it to
        // a sum (572) that contradicted the correct aggregate total (100).
        const byTypeInteractions = conversions?.byTypeInteractions ?? {};
        const typeRows: Array<{ label: string; count: number; icon: typeof Phone; dupes: number; aiDetected: number }> = [];
        if (channels) {
          const ai = channels.aiDetected ?? ({} as Record<string, number>);
          const channelDefs: Array<{ label: string; count: number; icon: typeof Phone; canonicalKey: string; aiKey: string }> = [
            { label: "Form Submit",       count: channels.formSubmits,       icon: ClipboardCheck, canonicalKey: "form_submit",       aiKey: "formSubmits" },
            { label: "Phone Call",        count: channels.phoneCalls,        icon: Phone,          canonicalKey: "click_to_call",     aiKey: "phoneCalls" },
            { label: "Booking Confirmed", count: channels.bookingConfirmed,  icon: CalendarCheck,  canonicalKey: "booking_confirmed", aiKey: "bookingConfirmed" },
            { label: "Purchase",          count: channels.purchases,         icon: Package,        canonicalKey: "purchase",          aiKey: "purchases" },
            { label: "SMS Click",         count: channels.smsClicks,         icon: MessageSquare,  canonicalKey: "sms_click",         aiKey: "smsClicks" },
            { label: "Email Click",       count: channels.emailClicks,       icon: Mail,           canonicalKey: "email_click",       aiKey: "emailClicks" },
            { label: "Booking Submit",    count: channels.bookingSubmits,    icon: CalendarClock,  canonicalKey: "booking_submit",    aiKey: "bookingSubmits" },
            { label: "Add to Cart",       count: channels.addToCarts,        icon: ShoppingCart,   canonicalKey: "add_to_cart",       aiKey: "" },
            { label: "File Download",     count: channels.fileDownloads,     icon: Download,       canonicalKey: "file_download",     aiKey: "" },
            { label: "Newsletter Signup", count: channels.newsletterSignups, icon: MailPlus,       canonicalKey: "newsletter_signup", aiKey: "" },
            { label: "Checkout Started",  count: channels.checkoutStarteds,  icon: CreditCard,     canonicalKey: "checkout_started",  aiKey: "" },
            { label: "Waitlist Signup",   count: channels.waitlistSignups,   icon: Bell,           canonicalKey: "waitlist_signup",   aiKey: "" },
          ];
          for (const ch of channelDefs) {
            if (ch.count <= 0) continue;
            const interactions = byTypeInteractions[ch.canonicalKey] ?? 0;
            const dupes = Math.max(0, interactions - ch.count);
            const aiDetectedForType = ch.aiKey ? ((ai as Record<string, number>)[ch.aiKey] ?? 0) : 0;
            typeRows.push({ label: ch.label, count: ch.count, icon: ch.icon, dupes, aiDetected: aiDetectedForType });
          }
        }
        typeRows.sort((a, b) => b.count - a.count);

        // Per-type duplicate breakdown for the dedup banner.
        // Now sourced from byTypeInteractions (same data as dupsFiltered),
        // so the per-type sum correctly reconciles with the aggregate.
        // "Other" catches any remainder from canonical types not in channelDefs.
        const perTypeDupeSum = typeRows.reduce((s, r) => s + r.dupes, 0);
        const dupRemainder = dupsFiltered - perTypeDupeSum;
        const dupBreakdown: Array<{ label: string; count: number }> = typeRows
          .filter((r) => r.dupes > 0)
          .map((r) => ({ label: r.label, count: r.dupes }));
        if (dupRemainder > 0) dupBreakdown.push({ label: "Other", count: dupRemainder });

        const aiBreakdown = typeRows.filter((r) => r.aiDetected > 0);
        const totalAiDetected = typeRows.reduce((s, r) => s + r.aiDetected, 0);

        const countedConversions = conversionTiers?.counted ?? tagConversions;
        const countedRate = traffic?.sessions
          ? ((countedConversions / traffic.sessions) * 100).toFixed(2)
          : tagConvRate.toFixed ? tagConvRate.toFixed(2) : String(tagConvRate);

        return (
          <CardShell
            title="Conversions"
            icon={Activity}
            right={
              <div className="flex items-center gap-2">
                <span className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">{range || "current range"}</span>
                {onOpenConvSettings && (
                  <button
                    onClick={onOpenConvSettings}
                    className="p-1 rounded-md hover:bg-slate-100 text-slate-400 hover:text-slate-600 transition-colors"
                    title="Configure conversion counting"
                  >
                    <Settings2 className="w-3.5 h-3.5" />
                  </button>
                )}
              </div>
            }
          >
            <div className="px-5 py-4">
              {/* Counted conversions headline */}
              <div className="flex items-baseline gap-3 mb-1">
                <span className="text-3xl font-bold text-slate-900 tabular-nums">{countedConversions.toLocaleString()}</span>
                <span className="text-sm font-semibold text-slate-500">counted conversions</span>
                <span className="text-xs font-semibold text-emerald-600 ml-auto">{countedRate}% conv. rate</span>
              </div>

              {/* Tier breakdown — only shown when tier data is available */}
              {conversionTiers && (
                <div className={`relative transition-opacity duration-300 ${convUpdating ? "opacity-50 pointer-events-none" : "opacity-100"}`}>
                  {convUpdating && (
                    <div className="absolute inset-0 flex items-center justify-center z-10">
                      <span className="text-[10px] font-semibold text-slate-500 bg-white/80 px-2 py-1 rounded-md shadow-sm">
                        Updating...
                      </span>
                    </div>
                  )}
                  <div className="grid grid-cols-3 gap-2 mt-3 mb-3">
                    {(["completed", "intent", "signal"] as const).map((tier) => {
                      const t = conversionTiers.byTier[tier] ?? { total: 0, counted: 0 };
                      if (t.total === 0) return null;
                      const tierColors: Record<string, string> = {
                        completed: "border-emerald-200 bg-emerald-50",
                        intent: "border-amber-200 bg-amber-50",
                        signal: "border-slate-200 bg-slate-50",
                      };
                      const tierTextColors: Record<string, string> = {
                        completed: "text-emerald-700",
                        intent: "text-amber-700",
                        signal: "text-slate-600",
                      };
                      // "Action" = completed final conversions (form submit, booking confirmed, etc.)
                      // "Signal" = intent-level events (phone click, form start, etc.) that also count
                      // "Engagement" = broad engagement signals
                      const tierLabels: Record<string, string> = {
                        completed: "Action",
                        intent: "Signal",
                        signal: "Engagement",
                      };
                      return (
                        <div key={tier} className={`rounded-lg border px-3 py-2.5 ${tierColors[tier]}`}>
                          <div className={`text-lg font-bold tabular-nums ${tierTextColors[tier]}`}>
                            {t.total.toLocaleString()}
                          </div>
                          <div className="text-[10px] font-semibold text-slate-500 uppercase tracking-wider mt-0.5">
                            {tierLabels[tier]}
                          </div>
                          <div className={`text-[10px] mt-0.5 ${t.counted > 0 ? "text-emerald-600 font-semibold" : "text-slate-400"}`}>
                            {t.counted > 0 ? "counted" : "not counted"}
                          </div>
                        </div>
                      );
                    })}
                  </div>

                  {onOpenConvSettings && (
                    <div className="flex items-center gap-1 text-[10px] text-slate-400 mb-3">
                      <Info className="w-3 h-3" />
                      <span>
                        Action-tier events are counted by default. Signal-tier events count when enabled.{" "}
                        <button onClick={onOpenConvSettings} className="text-brand-600 hover:underline font-medium">
                          Configure which events count
                        </button>
                      </span>
                    </div>
                  )}
                </div>
              )}

              {(dupsFiltered > 0 || totalAiDetected > 0) && (
                <div className="text-xs mb-4 space-y-2">
                  {/* Duplicates filtered — red, clearly "removed" */}
                  {dupsFiltered > 0 && (
                    <div className="flex items-start gap-2 px-3 py-2 bg-red-50 border border-red-200 rounded-lg">
                      <span className="text-red-500 shrink-0 mt-0.5">🛡️</span>
                      <div>
                        <p className="font-bold text-red-700">
                          {dupsFiltered.toLocaleString()} duplicate interactions filtered out
                        </p>
                        {dupBreakdown.length > 0 && (
                          <p className="text-red-600 font-normal mt-0.5">
                            {dupBreakdown.map((d) => `${d.count} ${d.label}`).join(", ")}
                          </p>
                        )}
                      </div>
                    </div>
                  )}
                  {dupsFiltered === 0 && totalAiDetected === 0 && (
                    <p className="text-emerald-600 font-semibold">No duplicates detected — all conversions are unique</p>
                  )}
                  {/* AI detections — green/blue, clearly "additional" */}
                  {totalAiDetected > 0 && (
                    <div className="flex items-start gap-2 px-3 py-2 bg-blue-50 border border-blue-200 rounded-lg">
                      <span className="text-blue-500 shrink-0 mt-0.5">🔍</span>
                      <div>
                        <p className="font-bold text-blue-700">
                          +{totalAiDetected.toLocaleString()} additional conversions detected by AI
                        </p>
                        <p className="text-blue-600 font-normal mt-0.5">
                          {aiBreakdown.map((r) => `+${r.aiDetected} ${r.label}${r.aiDetected !== 1 ? "s" : ""}`).join(", ")}
                        </p>
                        <p className="text-[10px] text-blue-500 mt-1">
                          Found by form-phone.js pattern analysis (tel: links, phone number text patterns) — real conversions beyond standard tracking.
                        </p>
                      </div>
                    </div>
                  )}
                </div>
              )}

              {/* Per-type breakdown */}
              {typeRows.length > 0 && (
                <div className="flex flex-wrap gap-2">
                  {typeRows.map((r) => {
                    const Icon = r.icon;
                    return (
                      <div key={r.label} className="inline-flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg border border-slate-200 bg-white">
                        <Icon className="w-3.5 h-3.5 text-brand-600" />
                        <span className="text-[11px] font-semibold text-slate-700">{r.label}</span>
                        <span className="text-[11px] font-bold text-slate-900 tabular-nums">{r.count.toLocaleString()}</span>
                      </div>
                    );
                  })}
                </div>
              )}
            </div>

            <div className="px-5 py-2.5 border-t border-slate-100">
              <p className="text-xs font-semibold text-slate-500">
                Total Conversions shows unique, verified conversion events after our dedup resolver filters out repeat clicks and duplicate form submissions.
              </p>
            </div>
          </CardShell>
        );
      })()}

      {/* Site Tag Tracking + GA4 Conversions — canonical 2-column grid.
          Both columns always render; missing data falls back to a
          calibrating / not-connected SectionState in the same column
          slot so the row never collapses to a single-column layout. */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <div className="space-y-3">
          <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">Site Tag Tracking</p>
          {hasTag ? (
            <div className="grid grid-cols-2 gap-4">
              <SummaryCard label="Page Views" value={tagPageViews.toLocaleString()} icon={Eye} />
              <SummaryCard label="Sessions" value={tagSessions.toLocaleString()} icon={Users} />
              <SummaryCard
                label="Conversions"
                value={tagConversions.toLocaleString()}
                subtitle={tagInteractions > tagConversions ? `${tagInteractions.toLocaleString()} raw interactions · resolver deduped to ${tagConversions.toLocaleString()} unique` : undefined}
                icon={TrendingUp}
                tooltip={TAG_CONV_TOOLTIP}
              />
              <SummaryCard label="Conv. Rate" value={`${tagConvRate}%`} icon={Percent} tooltip={TAG_CONV_TOOLTIP} />
            </div>
          ) : (
            <SectionState
              variant="calibrating"
              title="Site Tag not deployed"
              description="Install the meSEO Site Tag to start tracking page views, sessions, and conversions."
              cta={{ label: "Set up Site Tag", href: "/integrations" }}
            />
          )}
        </div>
        <div className="space-y-3">
          <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">GA4 Conversions</p>
          {hasGa4 && ga4 ? (
            <div className="grid grid-cols-2 gap-4">
              <SummaryCard label="GA4 Key Events" value={ga4Conversions.toLocaleString()} change={ga4.changes.conversions} icon={TrendingUp} tooltip={GA4_CONV_TOOLTIP} />
              <SummaryCard label="GA4 Sessions" value={ga4Sessions.toLocaleString()} change={ga4.changes.sessions} icon={Users} />
              {hasRevenue ? (
                <>
                  <SummaryCard label="Revenue" value={`$${ga4Revenue.toLocaleString()}`} change={ga4.changes.revenue} icon={DollarSign} />
                  <SummaryCard label="Avg Value" value={`$${(ga4.totals.avgValue ?? 0).toLocaleString()}`} icon={BarChart3} />
                </>
              ) : (
                <>
                  <SummaryCard label="Conv. Rate" value={`${ga4ConvRate.toFixed(2)}%`} change={ga4.changes.conversionRate} icon={Percent} />
                  <SummaryCard label="Revenue" value="—" subtitle="Connect e-commerce tracking" icon={DollarSign} iconColor="text-slate-300" />
                </>
              )}
            </div>
          ) : (
            <SectionState
              variant="not_connected"
              title="GA4 Not Connected"
              description="Connect Google Analytics 4 to compare key events and sessions side-by-side with first-party Site Tag data."
              cta={{ label: "Connect GA4", href: "/integrations" }}
            />
          )}
        </div>
      </div>

      {/* GA4 metric snapshot note */}
      {ga4?.dataOrigin === "metric_snapshot" && (
        <div className="flex items-start gap-2 rounded-xl border border-blue-200 bg-blue-50 px-4 py-3">
          <Info className="w-4 h-4 text-blue-600 mt-0.5 shrink-0" />
          <div className="text-xs text-blue-900 leading-relaxed">
            Showing aggregate GA4 conversion totals from daily snapshots.
            Connect GA4 conversion events for per-event breakdown by type, channel, and landing page.
          </div>
        </div>
      )}

      {/* Daily trend charts — canonical side-by-side 2-col grid that
          ALWAYS renders both panels. Missing data falls back to an
          inline empty/disconnected state inside the same CardShell so
          the layout doesn't shift between brands. */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        {(() => {
          const isHourly = range === "1d";
          const formatTick = isHourly
            ? (v: string) => { const h = parseInt(v.slice(-2), 10); return h === 0 ? "12 AM" : h < 12 ? `${h} AM` : h === 12 ? "12 PM" : `${h - 12} PM`; }
            : (v: string) => new Date(v).toLocaleDateString(undefined, { month: "short", day: "numeric" });
          const formatLabel = isHourly
            ? (v: unknown) => { const s = String(v); const h = parseInt(s.slice(-2), 10); return `${h === 0 ? "12" : h > 12 ? String(h - 12) : String(h)}:00 ${h < 12 ? "AM" : "PM"}`; }
            : (v: unknown) => new Date(String(v)).toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" });
          const showChart = hasTag && dailyPageViews.length > 0;
          return (
          <CardShell
            title={isHourly ? "Hourly Page Views" : "Daily Page Views"}
            icon={TrendingUp}
            right={<span className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Site Tag{isHourly ? " · Today" : ""}</span>}
          >
            {isAnalyticsLoading && !showChart ? (
              <div className="px-2 pt-3 pb-2" style={{ height: 300 }}>
                <Skeleton className="w-full h-full rounded-lg" />
              </div>
            ) : showChart ? (
              <div className="px-2 pt-3 pb-2" style={{ height: 300 }}>
                <ResponsiveContainer width="100%" height="100%" minWidth={0} minHeight={120}>
                  <AreaChart data={dailyPageViews}>
                    <defs>
                      <linearGradient id="pvGradient" x1="0" y1="0" x2="0" y2="1">
                        <stop offset="0%" stopColor="#10b981" stopOpacity={0.35} />
                        <stop offset="100%" stopColor="#10b981" stopOpacity={0.02} />
                      </linearGradient>
                    </defs>
                    <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" vertical={false} />
                    <XAxis dataKey="date" tick={{ fontSize: 10 }} stroke="#94a3b8" tickFormatter={formatTick} interval={isHourly ? 2 : undefined} />
                    <YAxis tick={{ fontSize: 10 }} stroke="#94a3b8" allowDecimals={false} />
                    <RTooltip
                      contentStyle={{ fontSize: "11px", borderRadius: "8px", border: "1px solid #e2e8f0" }}
                      labelFormatter={formatLabel}
                      formatter={(v: unknown, name: unknown) => [typeof v === "number" ? v.toLocaleString() : String(v), String(name) === "pageViews" ? "Page Views" : "Sessions"]}
                    />
                    <Area type="monotone" dataKey="pageViews" stroke="#10b981" strokeWidth={2} fill="url(#pvGradient)" />
                    <Area type="monotone" dataKey="sessions" stroke="#6366f1" strokeWidth={1.5} fill="none" />
                  </AreaChart>
                </ResponsiveContainer>
              </div>
            ) : (
              <SectionState
                chrome="bare"
                minHeight={300}
                variant={hasTag ? "no_data" : "calibrating"}
                title={hasTag ? "No page views captured yet" : "Site Tag not deployed"}
                description={
                  hasTag
                    ? "Daily page views and sessions will plot here once the Site Tag starts capturing traffic."
                    : "Install the meSEO Site Tag to chart daily page views."
                }
                cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
              />
            )}
          </CardShell>
          );
        })()}

        {(() => {
          // Daily Conversions chart — always rendered. Solid Site Tag
          // line when data exists, dashed GA4 line when GA4 is connected.
          // When GA4 is missing we still draw the chart frame and show a
          // "Connect GA4 to compare" overlay so the slot never collapses.
          const trend = ga4?.dailyTrend ?? [];
          const hasSiteTag = trend.some((d) => (d.siteTagCount ?? 0) > 0);
          const hasGa4Events = trend.some((d) => (d.count ?? 0) > 0);
          const ga4Stale = ga4?.freshness?.ga4?.isStale ?? false;
          const siteTagStale = ga4?.freshness?.siteTag?.isStale ?? false;
          const ga4LastSync = ga4?.freshness?.ga4?.lastUpdatedAt;
          const siteTagLastEvent = ga4?.freshness?.siteTag?.lastEventAt;
          const formatAgo = (iso: string | null | undefined) => {
            if (!iso) return "never";
            const hrs = Math.round((Date.now() - new Date(iso).getTime()) / 3_600_000);
            if (hrs < 1) return "just now";
            if (hrs < 24) return `${hrs}h ago`;
            const days = Math.round(hrs / 24);
            return `${days}d ago`;
          };
          // Synthesise a placeholder window so the chart frame always
          // renders X/Y axes even when neither source has data. Prefer
          // dailyPageViews dates (real Site Tag axis) and fall back to
          // the last 30 days if even that's empty.
          const fallbackTrend: Array<{ date: string; count: number | null; siteTagCount?: number }> = (() => {
            if (trend.length > 0) return trend;
            if (dailyPageViews.length > 0) {
              return dailyPageViews.map((d) => ({ date: d.date, count: null, siteTagCount: 0 }));
            }
            const out: Array<{ date: string; count: number | null; siteTagCount?: number }> = [];
            const today = new Date();
            for (let i = 29; i >= 0; i--) {
              const d = new Date(today);
              d.setDate(d.getDate() - i);
              out.push({ date: d.toISOString().slice(0, 10), count: null, siteTagCount: 0 });
            }
            return out;
          })();
          const showSiteTagSeries = hasTag;
          const showGa4Series = hasGa4;
          return (
            <CardShell
              title="Daily Conversions"
              icon={TrendingUp}
            >
              <div className="relative">
                <div className="px-2 pt-3 pb-2" style={{ height: 300 }}>
                  <ResponsiveContainer width="100%" height="100%" minWidth={0} minHeight={120}>
                    <AreaChart data={fallbackTrend}>
                      <defs>
                        <linearGradient id="convGradient" x1="0" y1="0" x2="0" y2="1">
                          <stop offset="0%" stopColor="#10b981" stopOpacity={0.3} />
                          <stop offset="100%" stopColor="#10b981" stopOpacity={0.02} />
                        </linearGradient>
                        <linearGradient id="ga4Gradient" x1="0" y1="0" x2="0" y2="1">
                          <stop offset="0%" stopColor="#4f46e5" stopOpacity={0.15} />
                          <stop offset="100%" stopColor="#4f46e5" stopOpacity={0.02} />
                        </linearGradient>
                      </defs>
                      <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" vertical={false} />
                      <XAxis dataKey="date" tick={{ fontSize: 10 }} stroke="#94a3b8"
                        tickFormatter={(v) => new Date(v).toLocaleDateString(undefined, { month: "short", day: "numeric" })}
                      />
                      <YAxis tick={{ fontSize: 10 }} stroke="#94a3b8" allowDecimals={false} />
                      <RTooltip
                        contentStyle={{ fontSize: "11px", borderRadius: "8px", border: "1px solid #e2e8f0" }}
                        labelFormatter={(v) => new Date(String(v)).toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" })}
                        formatter={(v: unknown, name: unknown) => {
                          if (v === null || v === undefined) return ["No data — sync in progress", String(name)];
                          return [typeof v === "number" ? v.toLocaleString() : String(v), String(name)];
                        }}
                      />
                      {showSiteTagSeries && (
                        <Area type="monotone" dataKey="siteTagCount" name="Site Tag Conversions" stroke="#10b981" strokeWidth={2} fill="url(#convGradient)" />
                      )}
                      {showGa4Series && (
                        <Area type="monotone" dataKey="count" name="GA4 Key Events" stroke="#4f46e5" strokeWidth={1.5} strokeDasharray="4 3" fill="url(#ga4Gradient)" connectNulls={false} />
                      )}
                    </AreaChart>
                  </ResponsiveContainer>
                </div>
                {!hasGa4 && (
                  <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
                    <div className="pointer-events-auto bg-white/95 backdrop-blur-sm rounded-xl border border-slate-200 shadow-sm px-4 py-3 max-w-xs text-center">
                      <div className="w-8 h-8 rounded-lg bg-slate-100 flex items-center justify-center mb-2 mx-auto">
                        <Plug className="w-3.5 h-3.5 text-slate-500" />
                      </div>
                      <p className="text-xs font-bold text-slate-700">Connect GA4 to compare</p>
                      <p className="text-[10px] text-slate-500 mt-1">
                        See GA4 key events alongside first-party Site Tag conversions.
                      </p>
                      <Link
                        href="/integrations"
                        className="inline-flex items-center gap-1 mt-2 px-2.5 py-1 rounded-md text-[11px] font-semibold text-white bg-brand-600 hover:bg-brand-700 transition-colors"
                      >
                        Connect GA4 <ExternalLink className="w-3 h-3" />
                      </Link>
                    </div>
                  </div>
                )}
              </div>
              {/* Legend — always shows both series with status labels so
                  the row stays visually balanced regardless of which
                  data sources are connected. */}
              <div className="px-5 pb-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-[10px]">
                <span className="flex items-center gap-1">
                  <span className="w-3 h-0.5 bg-emerald-500 rounded" />
                  Site Tag Conversions
                  {!hasTag && <span className="text-slate-400 ml-1 italic">(not deployed)</span>}
                  {hasTag && !hasSiteTag && <span className="text-slate-400 ml-1 italic">(no data)</span>}
                  {hasTag && hasSiteTag && siteTagLastEvent && <span className="text-slate-400 ml-1">synced {formatAgo(siteTagLastEvent)}</span>}
                  {siteTagStale && <span className="ml-1 px-1.5 py-0.5 rounded bg-amber-50 text-amber-600 font-semibold">stale</span>}
                </span>
                <span className="flex items-center gap-1">
                  <span className="w-3 h-0.5" style={{ borderTop: "1.5px dashed #4f46e5", height: 0 }} />
                  GA4 Key Events
                  {!hasGa4 && <span className="text-slate-400 ml-1 italic">(not connected)</span>}
                  {hasGa4 && !hasGa4Events && <span className="text-slate-400 ml-1 italic">(no data)</span>}
                  {hasGa4 && hasGa4Events && ga4LastSync && <span className="text-slate-400 ml-1">synced {formatAgo(ga4LastSync)}</span>}
                  {ga4Stale && <span className="ml-1 px-1.5 py-0.5 rounded bg-amber-50 text-amber-600 font-semibold">delayed — refreshing</span>}
                </span>
              </div>
            </CardShell>
          );
        })()}
      </div>

      {/* Top Pages + Traffic Sources — canonical 2-col grid, always
          rendered. Each panel falls back to an inline SectionState
          when its dataset is empty so the row never collapses. */}
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
        <CardShell title="Top Pages" icon={Eye}>
          {isAnalyticsLoading && !(hasTag && traffic && traffic.topPages.length > 0) ? (
            <div className="px-2 pt-3 pb-2" style={{ height: 320 }}>
              <Skeleton className="w-full h-full rounded-lg" />
            </div>
          ) : hasTag && traffic && traffic.topPages.length > 0 ? (
            <div className="px-2 pt-3 pb-2" style={{ height: 320 }}>
              <ResponsiveContainer width="100%" height="100%">
                <BarChart data={traffic.topPages.slice(0, 10)} layout="vertical" margin={{ left: 8, right: 20 }}>
                  <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" horizontal={false} />
                  <XAxis type="number" tick={{ fontSize: 10 }} stroke="#94a3b8" />
                  <YAxis
                    type="category" dataKey="url" tick={{ fontSize: 10 }} stroke="#94a3b8"
                    width={140}
                    tickFormatter={(v: string) => v.length > 20 ? v.slice(0, 19) + "…" : v}
                  />
                  <RTooltip contentStyle={{ fontSize: "11px", borderRadius: "8px", border: "1px solid #e2e8f0" }} />
                  <Bar dataKey="views" fill="#6366f1" radius={[0, 4, 4, 0]} />
                </BarChart>
              </ResponsiveContainer>
            </div>
          ) : (
            <SectionState
              chrome="bare"
              minHeight={320}
              variant={hasTag ? "no_data" : "calibrating"}
              title={hasTag ? "No page views captured yet" : "Site Tag not deployed"}
              description={
                hasTag
                  ? "Top page paths will rank here as the Site Tag captures traffic."
                  : "Install the meSEO Site Tag to start capturing page views."
              }
              cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
            />
          )}
        </CardShell>

        <CardShell title="Traffic Sources" icon={Share2}>
          {isAnalyticsLoading && !(hasTag && traffic && traffic.byReferrer.length > 0) ? (
            <div style={{ maxHeight: 320 }}>
              {[0, 1, 2, 3, 4].map((i) => (
                <div key={i} className="px-5 py-3 border-b border-slate-50 last:border-0">
                  <div className="flex items-center justify-between mb-1.5">
                    <Skeleton className="h-3 w-28" />
                    <Skeleton className="h-3 w-16" />
                  </div>
                  <Skeleton className="h-2 w-full rounded-full" />
                </div>
              ))}
            </div>
          ) : hasTag && traffic && traffic.byReferrer.length > 0 ? (
            <div className="divide-y divide-slate-100 overflow-y-auto" style={{ maxHeight: 320 }}>
              {traffic.byReferrer.slice(0, 10).map((r) => {
                const total = traffic.byReferrer.reduce((a, b) => a + b.views, 0) || 1;
                const pct = Math.round((r.views / total) * 100);
                return (
                  <div key={r.source} className="px-5 py-3">
                    <div className="flex items-center justify-between mb-1.5">
                      <span className="text-xs font-medium text-slate-700 truncate">{r.source}</span>
                      <span className="text-xs font-bold text-slate-900 tabular-nums">{r.views.toLocaleString()} · {pct}%</span>
                    </div>
                    <div className="w-full bg-slate-100 rounded-full h-2 overflow-hidden">
                      <div className="h-full bg-brand-500 rounded-full transition-all" style={{ width: `${pct}%` }} />
                    </div>
                  </div>
                );
              })}
            </div>
          ) : (
            <SectionState
              chrome="bare"
              minHeight={320}
              variant={hasTag ? "no_data" : "calibrating"}
              title={hasTag ? "No referrers captured yet" : "Site Tag not deployed"}
              description={
                hasTag
                  ? "Referrer breakdown (Google, direct, social, etc.) will appear here as the Site Tag captures sessions."
                  : "Install the meSEO Site Tag to attribute traffic to its source."
              }
              cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
            />
          )}
        </CardShell>
      </div>

      {/* Top Outbound Destinations — paginated table. Sourced from
          SiteEvent rows of type "outbound_click" aggregated by full URL.
          Always rendered; empty state when no outbound clicks are
          captured for the current range. */}
      {isAnalyticsLoading && outboundClicks.topDestinations.length === 0 ? (
        <CardShell title="Top Outbound Destinations" icon={ExternalLink}>
          <div className="divide-y divide-slate-100">
            {[0, 1, 2, 3].map((i) => (
              <div key={i} className="px-5 py-3 flex items-center gap-3">
                <Skeleton className="h-3 flex-1" />
                <Skeleton className="h-3 w-10" />
              </div>
            ))}
          </div>
        </CardShell>
      ) : hasTag && outboundClicks.topDestinations.length > 0 ? (
        <OutboundDestinationsTable destinations={outboundClicks.topDestinations} />
      ) : (
        <CardShell title="Top Outbound Destinations" icon={ExternalLink}>
          <SectionState
            chrome="bare"
            variant={hasTag ? "no_data" : "calibrating"}
            title={hasTag ? "No outbound clicks captured yet" : "Site Tag not deployed"}
            description={
              hasTag
                ? "External links your visitors click (third-party booking, social, partner sites) will rank here once the Site Tag captures them."
                : "Install the meSEO Site Tag to track outbound clicks."
            }
            cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
          />
        </CardShell>
      )}

      {/* Recent Activity feed — always rendered. */}
      <CardShell
        title="Recent Activity"
        icon={Activity}
        right={
          hasTag && recentEvents.length > 0
            ? <span className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">Last {recentEvents.length}</span>
            : undefined
        }
      >
        {isAnalyticsLoading && recentEvents.length === 0 ? (
          <div className="divide-y divide-slate-100">
            {[0, 1, 2, 3, 4].map((i) => (
              <div key={i} className="px-5 py-2.5 flex items-center gap-3">
                <Skeleton className="h-5 w-20 rounded" />
                <Skeleton className="h-3 flex-1" />
                <Skeleton className="h-3 w-12" />
              </div>
            ))}
          </div>
        ) : hasTag && recentEvents.length > 0 ? (
          <div className="divide-y divide-slate-100">
            {recentEvents.map((e) => (
              <div key={e.id} className="px-5 py-2.5 flex items-center gap-3 hover:bg-slate-50 transition-colors">
                <span className={`text-[10px] font-bold px-1.5 py-0.5 rounded border shrink-0 ${eventColor(e.eventType)}`}>
                  {e.eventType.replace(/_/g, " ")}
                </span>
                <span className="font-mono text-xs text-slate-600 truncate flex-1">{extractPath(e.pageUrl)}</span>
                {e.synthetic && (
                  <span className="text-[9px] font-semibold px-1.5 py-0.5 rounded border bg-amber-50 border-amber-200 text-amber-700 shrink-0">Test</span>
                )}
                <span className="text-[10px] text-slate-400 tabular-nums shrink-0">
                  {formatRelative(e.timestamp)}
                </span>
              </div>
            ))}
          </div>
        ) : (
          <SectionState
            chrome="bare"
            variant={hasTag ? "no_data" : "calibrating"}
            title={hasTag ? "No recent events" : "Site Tag not deployed"}
            description={
              hasTag
                ? "Live event activity (page views, clicks, form submits) will stream in here as visitors interact with your site."
                : "Install the meSEO Site Tag to see live event activity."
            }
            cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
          />
        )}
      </CardShell>

      {/* Data Quality — total events captured, type breakdown, freshness.
          Always rendered. The DataQuality block internally handles the
          "no tag" state via zeros + a calibrating Active Sources row. */}
      <DataQuality
        tagStatus={tagStatus}
        eventsToday={eventsToday}
        eventsThisWeek={eventsThisWeek}
        todayByType={todayByType}
        customEventsCount={customEventsCount}
        hasTag={hasTag}
        hasGa4={hasGa4}
        isAnalyticsLoading={isAnalyticsLoading}
      />
    </div>
  );
}

// ─── Data Quality block ────────────────────────────────────────────────────
//
// Replaces the prior "Data Reconciliation" block which painted a
// cross-source dedup/match story without the source ingestion to
// back it up. Until GA4/GSC/CRM source-event ingestion is wired
// (it isn't), every canonical event sat at the hard-coded 85%
// Site-Tag baseline with 0 duplicates and a 100% self-match rate
// — fake numbers that undermined trust in the real Site Tag
// metrics above.
//
// This block surfaces only metrics we can prove from real data:
//   • Total events captured (TrackedEvent count, today + week)
//   • Distinct event types observed + count per type
//   • Time since last event ("data freshness")
//   • Active source list (Site Tag + GA4 when connected)

function DataQuality({
  tagStatus, eventsToday, eventsThisWeek, todayByType = [], customEventsCount = 0, hasTag, hasGa4, isAnalyticsLoading = false,
}: {
  tagStatus: TagStatus | null;
  eventsToday: number;
  eventsThisWeek: number;
  todayByType: Array<{ type: string; count: number }>;
  customEventsCount: number;
  hasTag: boolean;
  hasGa4: boolean;
  isAnalyticsLoading?: boolean;
}) {
  // Total lifetime + last-event timestamp come straight from
  // tagStatus. Prefer lifetimeEventsExact (real COUNT(*)) and fall
  // back to totalEvents (which can be a capped 10-row sample when
  // the count query timed out on a massive-event site). When the
  // count is unavailable we render an em-dash rather than the
  // misleading sample cap.
  const lifetimeExact = tagStatus?.lifetimeEventsExact ?? null;
  const totalLifetime = lifetimeExact ?? tagStatus?.totalEvents ?? 0;
  const lifetimeDisplay = lifetimeExact != null
    ? totalLifetime.toLocaleString()
    : (tagStatus?.totalEvents ?? 0) > 0 ? "—" : "0";
  const lifetimeSub = lifetimeExact != null
    ? "All time, this brand"
    : "Exact count unavailable — count query timed out";
  const lastEventAt = tagStatus?.lastEventAt ?? null;
  const distinctTypes = todayByType.length;

  // Freshness band — drives the colour of the "Last event" cell.
  // Anything older than 6h on a live tag indicates a delivery
  // problem worth surfacing.
  const freshnessBand = (() => {
    if (!lastEventAt) return { label: "No events yet", color: "text-slate-400" };
    const ageMs = Date.now() - new Date(lastEventAt).getTime();
    if (ageMs < 5 * 60_000) return { label: formatRelative(lastEventAt), color: "text-emerald-600" };
    if (ageMs < 6 * 3_600_000) return { label: formatRelative(lastEventAt), color: "text-slate-700" };
    if (ageMs < 24 * 3_600_000) return { label: formatRelative(lastEventAt), color: "text-amber-600" };
    return { label: formatRelative(lastEventAt), color: "text-red-600" };
  })();

  const summaryCards = [
    { label: "Events today", value: eventsToday.toLocaleString(), color: "text-slate-900", sub: "From the meSEO Site Tag" },
    { label: "Events this week", value: eventsThisWeek.toLocaleString(), color: "text-slate-900", sub: "Last 7 days" },
    { label: "Total lifetime", value: lifetimeDisplay, color: "text-slate-900", sub: lifetimeSub },
    { label: "Event types", value: distinctTypes.toLocaleString(), color: "text-slate-900", sub: distinctTypes === 1 ? "1 distinct type today" : `${distinctTypes} distinct types today` },
    { label: "Last event", value: freshnessBand.label, color: freshnessBand.color, sub: "Data freshness" },
  ];

  return (
    <div className="space-y-4 pt-2">
      <div className="flex items-center gap-3">
        <div className="w-9 h-9 rounded-xl bg-emerald-50 flex items-center justify-center">
          <Database className="w-4 h-4 text-emerald-600" />
        </div>
        <div>
          <h2 className="text-base font-bold text-slate-900">Data Quality</h2>
          <p className="text-xs text-slate-500 mt-0.5">
            Real metrics on what the Site Tag has captured for this brand.
          </p>
        </div>
      </div>

      <div className="grid grid-cols-2 sm:grid-cols-5 gap-3">
        {summaryCards.map((c) => (
          <div key={c.label} className="bg-white rounded-xl border border-slate-200 px-4 py-3.5">
            <p className="text-[10px] font-semibold text-slate-400 uppercase tracking-wide">{c.label}</p>
            <p className={`text-2xl font-bold mt-1 ${c.color}`}>{c.value}</p>
            <p className="text-[10px] text-slate-400 mt-0.5">{c.sub}</p>
          </div>
        ))}
      </div>

      {/* Active sources — canonical layout: every source row always
          renders with a status badge so the list stays consistent
          across brands. The "X sources" count includes both rows.
          Future: GSC, Google Ads, etc. slot in here as the same row
          shape with their own status. */}
      <CardShell
        title="Active sources"
        icon={Layers}
        right={<span className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">2 sources</span>}
      >
        <div className="divide-y divide-slate-100">
          {/* meSEO Site Tag row — Active / Calibrating / Not Deployed */}
          <div className="px-5 py-3.5 flex items-center gap-4">
            <div className="w-9 h-9 rounded-lg bg-brand-50 flex items-center justify-center shrink-0">
              <Code2 className="w-4 h-4 text-brand-600" />
            </div>
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2">
                <p className="text-sm font-semibold text-slate-800">meSEO Site Tag</p>
                {hasTag ? (
                  tagStatus?.lastEventAt ? (
                    <span className="text-[10px] font-semibold rounded-full px-2 py-0.5 border bg-green-50 text-green-700 border-green-200">
                      active
                    </span>
                  ) : (
                    <span className="text-[10px] font-semibold rounded-full px-2 py-0.5 border bg-slate-50 text-slate-600 border-slate-200">
                      calibrating
                    </span>
                  )
                ) : (
                  <span className="text-[10px] font-semibold rounded-full px-2 py-0.5 border bg-slate-50 text-slate-500 border-slate-200">
                    not deployed
                  </span>
                )}
              </div>
              <p className="text-xs text-slate-500 mt-0.5">
                First-party events captured directly from the visitor&apos;s browser.
              </p>
            </div>
            <div className="text-right shrink-0">
              {hasTag ? (
                <>
                  <p className="text-sm font-bold text-slate-900 tabular-nums">{lifetimeDisplay}</p>
                  <p className="text-[10px] text-slate-400">events lifetime</p>
                </>
              ) : (
                <Link href="/integrations" className="text-[11px] font-semibold text-brand-600 hover:text-brand-700">
                  Set up →
                </Link>
              )}
            </div>
          </div>
          {/* Google Analytics 4 row — Connected / Not Connected */}
          <div className="px-5 py-3.5 flex items-center gap-4">
            <div className="w-9 h-9 rounded-lg bg-orange-50 flex items-center justify-center shrink-0">
              <BarChart3 className="w-4 h-4 text-orange-600" />
            </div>
            <div className="flex-1 min-w-0">
              <div className="flex items-center gap-2">
                <p className="text-sm font-semibold text-slate-800">Google Analytics 4</p>
                {hasGa4 ? (
                  <span className="text-[10px] font-semibold rounded-full px-2 py-0.5 border bg-blue-50 text-blue-700 border-blue-200">
                    connected
                  </span>
                ) : (
                  <span className="text-[10px] font-semibold rounded-full px-2 py-0.5 border bg-slate-50 text-slate-500 border-slate-200">
                    not connected
                  </span>
                )}
              </div>
              <p className="text-xs text-slate-500 mt-0.5">
                {hasGa4
                  ? "Synced sessions + key events. Cross-source reconciliation lands in a future release."
                  : "Connect GA4 to sync sessions and key events alongside first-party Site Tag data."}
              </p>
            </div>
            <div className="text-right shrink-0">
              {hasGa4 ? (
                <span className="text-[10px] text-slate-400">read-only</span>
              ) : (
                <Link href="/integrations" className="text-[11px] font-semibold text-brand-600 hover:text-brand-700">
                  Connect →
                </Link>
              )}
            </div>
          </div>
        </div>
      </CardShell>

      {/* Events today by type — always rendered. Empty state when no
          events have fired yet today. Default-open Core group keeps
          the always-on signals visible at a glance. */}
      <CardShell
        title="Events today by type"
        icon={Activity}
        right={
          todayByType.length > 0
            ? <span className="text-[10px] text-slate-400">{distinctTypes} type{distinctTypes === 1 ? "" : "s"}{customEventsCount > 0 ? ` · ${customEventsCount} custom` : ""}</span>
            : undefined
        }
      >
        {isAnalyticsLoading && todayByType.length === 0 ? (
          <div className="px-5 py-3 space-y-2.5">
            {[0, 1, 2, 3].map((i) => (
              <div key={i} className="flex items-center gap-3">
                <Skeleton className="h-3 w-24" />
                <Skeleton className="h-2 flex-1 rounded-full" />
                <Skeleton className="h-3 w-10" />
              </div>
            ))}
          </div>
        ) : todayByType.length > 0 ? (
          <CategoryBreakdown rows={todayByType} totalEvents={eventsToday} />
        ) : (
          <SectionState
            chrome="bare"
            variant={hasTag ? "no_data" : "calibrating"}
            title={hasTag ? "No events today yet" : "Site Tag not deployed"}
            description={
              hasTag
                ? "As visitors interact with your site, event counts will be grouped by category here."
                : "Install the meSEO Site Tag to start capturing events."
            }
            cta={hasTag ? undefined : { label: "Set up Site Tag", href: "/integrations" }}
          />
        )}
      </CardShell>
    </div>
  );
}

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

// ─── Event-type category taxonomy ──────────────────────────────
//
// Single source of truth for the collapsible-group renderer below.
// Edit here to regroup / rename / add a new event — the strip + the
// CardShell breakdown both read from this table. Any event not
// listed in a group's `types` falls through to the synthetic
// "Other" bucket so new types ship visible even before they're
// properly categorised.
//
// `color` maps to a semantic palette used by the header chip +
// the per-row progress bar. Kept in sync with the Session Explorer
// timeline palette (TIMELINE_COLORS in
// /admin/sessions/[sessionId]/page.tsx) so the same event reads
// as the same colour on both surfaces.

interface EventCategory {
  key: string;
  label: string;
  types: string[];
  color: "blue" | "emerald" | "violet" | "fuchsia" | "amber" | "red" | "orange" | "slate" | "teal" | "indigo" | "cyan";
}

const EVENT_CATEGORIES: EventCategory[] = [
  {
    key: "core",
    label: "Core",
    color: "blue",
    types: ["page_view", "session_start", "outbound_click"],
  },
  {
    key: "conversions",
    label: "Conversions",
    color: "emerald",
    types: [
      "form_submit", "click_to_call", "booking_submit", "booking_confirmed",
      "purchase", "purchase_completed", "sms_click", "sms_submit",
      "newsletter_signup", "waitlist_signup", "email_click", "file_download",
    ],
  },
  {
    key: "engagement",
    label: "Engagement",
    color: "teal",
    types: [
      "form_start", "appointment_intent", "form_intent",
      "booking_widget_opened", "booking_step",
      "add_to_cart", "cart_view", "cart_update", "cart_abandoned",
      "checkout_started", "checkout_step", "payment_method_selected",
      "calendar_add", "appointment_modify_intent", "availability_check",
      "return_visit",
    ],
  },
  {
    key: "video",
    label: "Video",
    color: "violet",
    types: ["video_play", "video_progress", "video_complete", "video_engagement"],
  },
  {
    key: "content",
    label: "Content",
    color: "cyan",
    types: [
      "content_engagement", "site_search", "social_share",
      "text_selected", "content_copied", "email_copied",
      "tab_pattern", "review_widget_visible", "review_widget_clicked",
      "social_proof_visible", "social_proof_attention",
      "product_view", "product_click", "phone_number_visible",
      "pdf_opened", "pdf_time_spent", "page_printed",
    ],
  },
  {
    key: "ux_signals",
    label: "UX Signals",
    color: "red",
    types: [
      "rage_click", "dead_click", "form_abandoned",
      "scroll_bounce", "error_shown",
      "keyboard_navigation", "navigation_pattern",
    ],
  },
  {
    key: "performance",
    label: "Performance",
    color: "orange",
    types: [
      "web_vitals", "slow_interaction", "js_error", "resource_error",
      "page_not_found",
    ],
  },
  {
    key: "detection",
    label: "Detection",
    color: "indigo",
    types: [
      "plugin_detected", "call_tracking_detected",
      "competitor_visit", "competitor_referral", "competitive_journey",
      "accessibility_context", "conversion_path_complete",
      "multi_session_conversion",
    ],
  },
];

const TYPE_TO_CATEGORY: Record<string, EventCategory> = (() => {
  const map: Record<string, EventCategory> = {};
  for (const cat of EVENT_CATEGORIES) {
    for (const t of cat.types) map[t] = cat;
  }
  return map;
})();

const OTHER_CATEGORY: EventCategory = { key: "other", label: "Other", types: [], color: "slate" };

function categoryFor(type: string): EventCategory {
  return TYPE_TO_CATEGORY[type] ?? OTHER_CATEGORY;
}

interface CategoryGroup extends EventCategory {
  total: number;
  rows: Array<{ type: string; count: number }>;
}

function groupByCategory(rows: Array<{ type: string; count: number }>): CategoryGroup[] {
  const buckets: Record<string, CategoryGroup> = {};
  for (const cat of EVENT_CATEGORIES) {
    buckets[cat.key] = { ...cat, total: 0, rows: [] };
  }
  buckets.other = { ...OTHER_CATEGORY, total: 0, rows: [] };
  for (const row of rows) {
    const cat = categoryFor(row.type);
    const bucket = buckets[cat.key] ?? buckets.other;
    bucket.rows.push(row);
    bucket.total += row.count;
  }
  // Sort rows inside each bucket by volume desc, filter out empty
  // buckets so a brand only-using-core-events doesn't see seven
  // zero-count headers cluttering the view.
  return Object.values(buckets)
    .map((b) => ({ ...b, rows: [...b.rows].sort((a, b2) => b2.count - a.count) }))
    .filter((b) => b.total > 0);
}

// Palette mirrors the lightweight chip/bar colours used elsewhere
// on the tab. Stays in lockstep with the EventCategory color union.
const CATEGORY_PALETTE: Record<EventCategory["color"], { chip: string; bar: string; text: string }> = {
  blue:    { chip: "bg-blue-50 text-blue-700 border-blue-200",       bar: "bg-blue-500",    text: "text-blue-700" },
  emerald: { chip: "bg-emerald-50 text-emerald-700 border-emerald-200", bar: "bg-emerald-500", text: "text-emerald-700" },
  violet:  { chip: "bg-violet-50 text-violet-700 border-violet-200", bar: "bg-violet-500",  text: "text-violet-700" },
  fuchsia: { chip: "bg-fuchsia-50 text-fuchsia-700 border-fuchsia-200", bar: "bg-fuchsia-500", text: "text-fuchsia-700" },
  amber:   { chip: "bg-amber-50 text-amber-700 border-amber-200",   bar: "bg-amber-500",   text: "text-amber-700" },
  red:     { chip: "bg-red-50 text-red-700 border-red-200",         bar: "bg-red-500",     text: "text-red-700" },
  orange:  { chip: "bg-orange-50 text-orange-700 border-orange-200", bar: "bg-orange-500",  text: "text-orange-700" },
  slate:   { chip: "bg-slate-50 text-slate-600 border-slate-200",   bar: "bg-slate-400",   text: "text-slate-600" },
  teal:    { chip: "bg-teal-50 text-teal-700 border-teal-200",      bar: "bg-teal-500",    text: "text-teal-700" },
  indigo:  { chip: "bg-indigo-50 text-indigo-700 border-indigo-200", bar: "bg-indigo-500",  text: "text-indigo-700" },
  cyan:    { chip: "bg-cyan-50 text-cyan-700 border-cyan-200",      bar: "bg-cyan-500",    text: "text-cyan-700" },
};

// ─── Categorized collapsible breakdown ─────────────────────────
//
// Renders grouped category headers with total counts + progress
// bars. Each header toggles a panel of per-type rows keyed to the
// same palette. Core defaults open (always-on signal group,
// useful to see first); every other category defaults closed so
// a multi-category brand doesn't land on a wall of numbers.

function CategoryBreakdown({
  rows, totalEvents,
}: {
  rows: Array<{ type: string; count: number }>;
  totalEvents: number;
}) {
  const groups = groupByCategory(rows);
  const [openKeys, setOpenKeys] = useState<Set<string>>(() => new Set(["core"]));
  const toggle = (k: string) => {
    setOpenKeys((prev) => {
      const next = new Set(prev);
      if (next.has(k)) next.delete(k); else next.add(k);
      return next;
    });
  };

  if (groups.length === 0) return null;

  const grandTotal = groups.reduce((s, g) => s + g.total, 0);

  return (
    <div className="divide-y divide-slate-100">
      {groups.map((g) => {
        const isOpen = openKeys.has(g.key);
        const palette = CATEGORY_PALETTE[g.color];
        const groupPct = grandTotal > 0 ? (g.total / grandTotal) * 100 : 0;
        return (
          <div key={g.key}>
            <button
              onClick={() => toggle(g.key)}
              className="w-full px-5 py-3 flex items-center gap-4 hover:bg-slate-50 transition-colors text-left"
            >
              {isOpen
                ? <ChevronDown className="w-3.5 h-3.5 text-slate-400 shrink-0" />
                : <ChevronRight className="w-3.5 h-3.5 text-slate-400 shrink-0" />}
              <span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded border shrink-0 min-w-[120px] text-center ${palette.chip}`}>
                {g.label}
              </span>
              <span className="text-[10px] text-slate-400 tabular-nums shrink-0 min-w-[48px]">
                {g.rows.length} type{g.rows.length === 1 ? "" : "s"}
              </span>
              <div className="flex-1 h-1.5 bg-slate-100 rounded-full overflow-hidden">
                <div className={`h-full rounded-full ${palette.bar}`} style={{ width: `${Math.min(100, groupPct)}%` }} />
              </div>
              <p className={`text-sm font-bold tabular-nums w-20 text-right ${palette.text}`}>{g.total.toLocaleString()}</p>
              <p className="text-[10px] text-slate-400 tabular-nums w-12 text-right">{groupPct.toFixed(1)}%</p>
            </button>
            {isOpen && (
              <div className="bg-slate-50/40 border-t border-slate-100 divide-y divide-slate-100">
                {g.rows.map((row) => {
                  const pct = totalEvents > 0 ? (row.count / totalEvents) * 100 : 0;
                  return (
                    <div key={row.type} className="px-5 py-2 flex items-center gap-4 pl-12">
                      <span className="text-[11px] text-slate-700 font-mono truncate min-w-[180px]" title={row.type}>
                        {eventTypeLabel(row.type)}
                      </span>
                      <div className="flex-1 h-1 bg-slate-200 rounded-full overflow-hidden">
                        <div className={`h-full rounded-full ${palette.bar} opacity-70`} style={{ width: `${Math.min(100, pct)}%` }} />
                      </div>
                      <p className="text-[11px] font-semibold text-slate-700 tabular-nums w-16 text-right">{row.count.toLocaleString()}</p>
                      <p className="text-[10px] text-slate-400 tabular-nums w-12 text-right">{pct.toFixed(1)}%</p>
                    </div>
                  );
                })}
              </div>
            )}
          </div>
        );
      })}
    </div>
  );
}

function eventColor(type: string): string {
  const map: Record<string, string> = {
    page_view: "bg-blue-50 text-blue-700 border-blue-200",
    session_start: "bg-emerald-50 text-emerald-700 border-emerald-200",
    form_submit: "bg-violet-50 text-violet-700 border-violet-200",
    form_start: "bg-violet-50 text-violet-600 border-violet-200",
    form_intent: "bg-fuchsia-50 text-fuchsia-700 border-fuchsia-200",
    appointment_intent: "bg-fuchsia-50 text-fuchsia-700 border-fuchsia-200",
    form_iframe_present: "bg-slate-50 text-slate-500 border-slate-200",
    booking_widget_opened: "bg-teal-50 text-teal-700 border-teal-200",
    booking_step: "bg-teal-50 text-teal-600 border-teal-200",
    booking_submit: "bg-emerald-50 text-emerald-700 border-emerald-200",
    booking_confirmed: "bg-emerald-100 text-emerald-800 border-emerald-300",
    plugin_detected: "bg-indigo-50 text-indigo-700 border-indigo-200",
    click_to_call: "bg-amber-50 text-amber-700 border-amber-200",
    sms_submit: "bg-amber-50 text-amber-700 border-amber-200",
    outbound_click: "bg-slate-50 text-slate-600 border-slate-200",
  };
  return map[type] ?? "bg-slate-50 text-slate-600 border-slate-200";
}

// ─── Form & Booking Funnel helper ────────────────────────────────────
//
// Renders one stage of the funnel — count + caption + a small drop-off
// pill that previews "X% lost to <next>". Drop-off is hidden on the
// final stage (no next).

function FunnelStage({
  label, caption, count, color, dropoffToNext, nextLabel,
}: {
  label: string;
  caption: string;
  count: number;
  color: string;
  dropoffToNext?: number | null;
  nextLabel?: string;
}) {
  return (
    <div className="rounded-lg border border-slate-200 bg-slate-50/40 p-4 flex flex-col">
      <div className="flex items-center gap-2 mb-1">
        <span className={`w-2 h-2 rounded-full ${color}`} />
        <p className="text-[10px] font-bold uppercase tracking-widest text-slate-500">{label}</p>
      </div>
      <p className="text-2xl font-bold text-slate-900 tabular-nums leading-tight">{count.toLocaleString()}</p>
      <p className="text-[11px] text-slate-500 mt-1.5 leading-snug">{caption}</p>
      {dropoffToNext != null && nextLabel && (
        <p className="text-[10px] text-slate-400 mt-2">
          {dropoffToNext > 0
            ? <span><span className="font-semibold text-amber-600">{dropoffToNext.toFixed(1)}%</span> drop-off to {nextLabel}</span>
            : <span className="font-semibold text-emerald-600">No drop-off to {nextLabel}</span>}
        </p>
      )}
    </div>
  );
}

// ─── Conversion-channel breakdown row ────────────────────────────────────────
//
// Cells are grouped by funnel tier (Engagement / Soft / Hard) so
// every stage total on the funnel card above reconciles exactly
// with the sum of its tier's cells. Zero-count cells still render
// in muted grey so the operator can confirm the channel is being
// tracked even if no one's using it yet. Raw TrackedEvent volume —
// NOT the deduped SiteConversion count in the Conversions KPI
// card (that one uses the 30-second resolver).

interface ChannelBreakdown {
  // Engagement tier — TrackedEvent raw counts.
  appointmentIntents: number;
  bookingOpens: number;
  bookingSteps: number;
  formStarts: number;
  productViews: number;
  // Soft conversions tier — SiteConversion deduped.
  addToCarts: number;
  checkoutStarteds: number;
  fileDownloads: number;
  newsletterSignups: number;
  waitlistSignups: number;
  bookingSubmits: number;
  // Hard conversions tier — SiteConversion deduped.
  formSubmits: number;
  phoneCalls: number;
  bookingConfirmed: number;
  purchases: number;
  smsClicks: number;
  emailClicks: number;
}

interface TierGroup {
  label: string;
  accent: string;
  cells: Array<{ icon: typeof Phone; label: string; value: number; subtitle?: string }>;
}

function ChannelBreakdownRow({ channels }: { channels: ChannelBreakdown }) {
  const tiers: TierGroup[] = [
    {
      label: "Engagement",
      accent: "text-violet-600",
      cells: [
        { icon: Target,        label: "Appointment Intent", value: channels.appointmentIntents },
        { icon: CalendarPlus,  label: "Booking Opened",     value: channels.bookingOpens },
        { icon: Activity,      label: "Booking Step",       value: channels.bookingSteps },
        { icon: Pencil,        label: "Form Start",         value: channels.formStarts },
        { icon: Eye,           label: "Product View",       value: channels.productViews },
      ],
    },
    {
      label: "Soft Conversions",
      accent: "text-amber-600",
      cells: [
        { icon: ShoppingCart,  label: "Add to Cart",        value: channels.addToCarts },
        { icon: CreditCard,    label: "Checkout Started",   value: channels.checkoutStarteds },
        { icon: Download,      label: "File Download",      value: channels.fileDownloads },
        { icon: MailPlus,      label: "Newsletter Signup",  value: channels.newsletterSignups },
        { icon: Bell,          label: "Waitlist Signup",    value: channels.waitlistSignups },
        { icon: CalendarClock, label: "Booking Submit",     value: channels.bookingSubmits },
      ],
    },
    {
      label: "Hard Conversions",
      accent: "text-emerald-700",
      cells: [
        { icon: ClipboardCheck, label: "Form Submit",       value: channels.formSubmits, subtitle: "native + plugin + gtag forms" },
        { icon: Phone,          label: "Phone Call",        value: channels.phoneCalls, subtitle: "tel: clicks + phone-number text patterns + gtag" },
        { icon: CalendarCheck,  label: "Booking Confirmed", value: channels.bookingConfirmed },
        { icon: Package,        label: "Purchase",          value: channels.purchases, subtitle: "gtag purchase + URL confirmation" },
        { icon: MessageSquare,  label: "SMS Click",         value: channels.smsClicks },
        { icon: Mail,           label: "Email Click",       value: channels.emailClicks },
      ],
    },
  ];
  return (
    <div className="space-y-2.5">
      <div className="space-y-1.5">
        <p className="text-[10px] font-semibold uppercase tracking-widest text-slate-400">Conversion Breakdown</p>
        <p className="text-xs font-semibold text-slate-500 bg-slate-50 border border-slate-200 rounded-md px-3 py-1.5">
          <span className="text-slate-700">Engagement</span> = raw TrackedEvent counts · <span className="text-slate-700">Soft + Hard</span> = SiteConversion deduped counts (same source as the Conversions KPI above).
        </p>
      </div>
      <div className="space-y-2">
        {tiers.map(({ label, accent, cells }) => {
          const tierTotal = cells.reduce((sum, c) => sum + c.value, 0);
          return (
            <div key={label} className="space-y-1">
              <div className="flex items-baseline gap-2">
                <span className={`text-[10px] font-bold uppercase tracking-wider ${accent}`}>{label}</span>
                <span className="text-[10px] tabular-nums text-slate-500">{tierTotal.toLocaleString()} events</span>
              </div>
              <div className="flex items-center gap-2 flex-wrap">
                {cells.map(({ icon: Icon, label: cellLabel, value, subtitle }) => {
                  const active = value > 0;
                  return (
                    <div
                      key={cellLabel}
                      className={`inline-flex items-center gap-1.5 px-2 py-1 rounded-md border ${
                        active ? "bg-white border-slate-200" : "bg-slate-50/60 border-slate-100"
                      }`}
                      title={subtitle ? `${cellLabel}: ${value.toLocaleString()} (includes ${subtitle})` : `${cellLabel}: ${value.toLocaleString()}`}
                    >
                      <Icon className={`w-3.5 h-3.5 ${active ? "text-brand-600" : "text-slate-300"}`} />
                      <span className={`text-[11px] font-semibold ${active ? "text-slate-700" : "text-slate-400"}`}>{cellLabel}</span>
                      <span className={`text-[11px] tabular-nums font-bold ${active ? "text-slate-900" : "text-slate-300"}`}>
                        {value.toLocaleString()}
                      </span>
                    </div>
                  );
                })}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function extractPath(url: string): string {
  try { return new URL(url).pathname; } catch { return url; }
}

// ─── Outbound destinations table ────────────────────────────────────────────

/**
 * Ranked list of outbound click targets for the selected date range. The
 * service layer caps the input at 100 rows; this component paginates at
 * 25 per page so users can drill into the long tail while keeping the
 * initial render compact.
 */
function OutboundDestinationsTable({ destinations }: { destinations: OutboundDestination[] }) {
  const PAGE_SIZE = 25;
  const [page, setPage] = useState(0);
  const pageCount = Math.max(1, Math.ceil(destinations.length / PAGE_SIZE));
  const rows = destinations.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
  const maxCount = destinations[0]?.count ?? 1;

  return (
    <CardShell
      title="Top Outbound Destinations"
      icon={ExternalLink}
      right={
        <span className="text-[10px] font-semibold text-slate-400 uppercase tracking-widest">
          {destinations.length} destination{destinations.length === 1 ? "" : "s"}
        </span>
      }
    >
      <div className="overflow-x-auto">
        <table className="w-full text-sm">
          <thead>
            <tr className="text-left text-[11px] font-semibold uppercase tracking-wider text-slate-400 border-b border-slate-100 bg-slate-50">
              {/* Source Page column — surfaces which of the brand's
                  pages drive the outbound traffic (the page the user
                  was on when they clicked). Backed by
                  OutboundClickDestination.sourcePage, aggregated
                  server-side from SiteEvent.pageUrl + the
                  source_page_url field t.js now writes into
                  eventData. */}
              <th className="px-5 py-2.5">Source Page</th>
              <th className="px-5 py-2.5">Target Domain</th>
              <th className="px-5 py-2.5">Full URL</th>
              <th className="px-5 py-2.5">Link Text</th>
              <th className="px-5 py-2.5 text-right">Clicks</th>
            </tr>
          </thead>
          <tbody className="divide-y divide-slate-100">
            {rows.map((d) => {
              const pct = Math.max((d.count / maxCount) * 100, 3);
              return (
                <tr key={d.url} className="hover:bg-slate-50 transition-colors">
                  <td className="px-5 py-2.5">
                    {/* Source page — relative path only. Shows "—"
                        when the destination was clicked from
                        multiple pages with no dominant source OR
                        when legacy rows didn't carry pageUrl. */}
                    <span className="font-mono text-xs text-slate-700 truncate max-w-[200px] inline-block" title={d.sourcePage ?? "Unknown source"}>
                      {d.sourcePage || <span className="text-slate-300 italic">—</span>}
                    </span>
                  </td>
                  <td className="px-5 py-2.5">
                    <span className="font-mono text-xs text-slate-800 truncate max-w-[180px] inline-block" title={d.domain}>
                      {d.domain || "—"}
                    </span>
                  </td>
                  <td className="px-5 py-2.5">
                    <a
                      href={d.url}
                      target="_blank"
                      rel="noopener noreferrer"
                      className="font-mono text-xs text-brand-600 hover:text-brand-700 hover:underline truncate max-w-[280px] inline-block"
                      title={d.url}
                    >
                      {d.url}
                    </a>
                  </td>
                  <td className="px-5 py-2.5">
                    <span className="text-xs text-slate-600 truncate max-w-[180px] inline-block" title={d.text}>
                      {d.text || <span className="text-slate-300 italic">no text</span>}
                    </span>
                  </td>
                  <td className="px-5 py-2.5 text-right">
                    <div className="flex items-center gap-2 justify-end">
                      <div className="w-16 bg-slate-100 rounded-full h-1.5 overflow-hidden">
                        <div className="h-full rounded-full bg-amber-500" style={{ width: `${pct}%` }} />
                      </div>
                      <span className="text-xs font-bold text-slate-900 tabular-nums w-10">{d.count.toLocaleString()}</span>
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      {pageCount > 1 && (
        <div className="flex items-center justify-between px-5 py-3 border-t border-slate-100 bg-slate-50">
          <span className="text-[11px] text-slate-500 tabular-nums">
            Page {page + 1} of {pageCount} · Showing {rows.length} of {destinations.length}
          </span>
          <div className="flex items-center gap-2">
            <button
              onClick={() => setPage((p) => Math.max(0, p - 1))}
              disabled={page === 0}
              className="px-3 py-1 text-[11px] font-semibold text-slate-600 border border-slate-200 rounded-lg hover:bg-white disabled:opacity-40 disabled:cursor-not-allowed"
            >
              Previous
            </button>
            <button
              onClick={() => setPage((p) => Math.min(pageCount - 1, p + 1))}
              disabled={page >= pageCount - 1}
              className="px-3 py-1 text-[11px] font-semibold text-slate-600 border border-slate-200 rounded-lg hover:bg-white disabled:opacity-40 disabled:cursor-not-allowed"
            >
              Next
            </button>
          </div>
        </div>
      )}
    </CardShell>
  );
}

function formatRelative(iso: string): string {
  const diff = Date.now() - new Date(iso).getTime();
  const secs = Math.floor(diff / 1000);
  if (secs < 5) return "Just now";
  if (secs < 60) return `${secs}s ago`;
  const mins = Math.floor(secs / 60);
  if (mins < 60) return `${mins}m ago`;
  const hours = Math.floor(mins / 60);
  if (hours < 24) return `${hours}h ago`;
  const days = Math.floor(hours / 24);
  return `${days}d ago`;
}
