/**
 * Conversion Taxonomy — single source of truth
 * ─────────────────────────────────────────────
 * Defines every conversion type the platform recognises, splits them
 * into Tier 1 (hard conversions — drive the headline KPI) and Tier 2
 * (engagement / intent — shown separately), and maps every raw
 * source name (gtag event, plugin event, native event) to its
 * canonical conversionType.
 *
 * Why this exists
 * ───────────────
 * Before this module the platform tracked a half-dozen overlapping
 * names (form_submit / form_submission, click_to_call / phone_call,
 * booking_submit / booking_attempt / booking_confirmed) across
 * TrackedEvent.eventType, SiteConversion.conversionType, and the
 * dashboard rollup queries — each surface had its own ad-hoc list,
 * each list drifted, and the same real-world action was counted
 * differently depending on which page you were looking at.
 *
 * Now: every surface that needs to know "is this a conversion?" or
 * "what tier?" or "is this gtag-routed or native?" reads from this
 * module. Add a new source key here and the dashboard, the
 * reconciliation endpoint, the analytics service, and the resolver
 * all pick it up automatically.
 *
 * Backwards compatibility
 * ───────────────────────
 * The codebase ships with rows already written under legacy types
 * (form_submission, phone_call, booking_attempt, booking_confirmed,
 * purchase). Don't migrate those rows — `canonicalConversionType`
 * maps them to the new canonical names at read time so historical
 * data still rolls up correctly.
 */

import type { LucideIcon } from "lucide-react";
import {
  CalendarCheck, FileCheck, Phone, CreditCard,
  CalendarPlus, FileEdit, MousePointerClick, MessageCircle,
  Mail, MailPlus, ShoppingCart, ClipboardCheck, MessageSquare,
  Download, Clock, Smartphone, Signal,
} from "lucide-react";

// ─── Canonical conversion type IDs ───────────────────────────────
//
// These are the keys the platform uses going forward. Every new
// SiteConversion write should use one of these. Legacy rows (written
// before this taxonomy landed) are mapped at read time via
// canonicalConversionType().

export type CanonicalConversionType =
  // Tier 1 — hard conversions (revenue-generating actions)
  | "appointment_booked"
  | "form_submitted"
  | "phone_call"
  | "email_contact"
  | "sms_contact"
  | "purchase"
  // SMS opt-in conversions (Tier 1 — confirmed subscriber)
  | "sms_optin"
  | "sms_community_join"
  // Tier 2 — soft conversions (high-intent, not yet completed)
  | "appointment_attempted"
  | "form_started"
  | "appointment_intent"
  | "chat_initiated"
  | "newsletter_signup"
  | "add_to_cart"
  | "checkout_started"
  | "file_download"
  | "waitlist_signup"
  // SMS Tier 2 — intent signals and partial opt-in steps
  | "sms_keyword_displayed"
  | "sms_outbound_click"
  | "phone_field_completed"
  | "sms_optin_followup";

export interface ConversionTypeDef {
  /** 1 = hard conversion (drives the headline KPI), 2 = engagement / intent. */
  tier: 1 | 2;
  /** Human-friendly name used in dashboards. */
  label: string;
  /** One-line description shown in tooltips and the reconciliation report. */
  description: string;
  /** All raw source identifiers that resolve to this canonical type.
   *  Format: "<source>:<eventName>" or "<eventName>" when the source
   *  is implicit (e.g. native form_submit). The resolver below uses
   *  this list to canonicalise. */
  sources: string[];
  /** Lucide icon component — used by every dashboard surface. */
  icon: LucideIcon;
  /** Tailwind colour family used in pills + badges. */
  color: "emerald" | "blue" | "violet" | "amber" | "teal" | "slate" | "orange" | "cyan";
}

export const CONVERSION_TYPES: Record<CanonicalConversionType, ConversionTypeDef> = {
  // ── TIER 1 — hard conversions ───────────────────────────────────
  appointment_booked: {
    tier: 1,
    label: "Appointment Booked",
    description: "Patient/customer confirmed a booking",
    sources: [
      "gtag:make_an_appointment",
      "gtag:book_appointment2025",
      "gtag:appointment_scheduled",
      "gtag:appointment_confirmed",
      "gtag:booking_confirmed",
      "booking_confirmed",          // t.js native + plugin path
      "amelia:booking_confirmed",
      "calendly:event_scheduled",
      "nexhealth:appointment_booked",
      "acuity:scheduled",
    ],
    icon: CalendarCheck,
    color: "emerald",
  },
  form_submitted: {
    tier: 1,
    label: "Form Submitted",
    description: "Contact form, intake form, or lead form completed",
    sources: [
      "gtag:contact_us",
      "gtag:contact_form_submit",
      "gtag:form_submit",
      "gtag:form_submission",
      "gtag:generate_lead",
      "gtag:sign_up",
      "gtag:subscribe",
      "gtag:newsletter_signup",
      "form_submit:native",         // t.js focus+submit listener
      "form_submit",                // generic form_submit (pre-source-tagging)
      "form_submission",            // CONVERSION_EVENT_MAP legacy value
      "form_submitted",             // canonical name as source (self-referencing)
      "cf7:wpcf7mailsent",
      "wpforms:submit",
      "gravityforms:submit",
      "hubspot:onFormSubmitted",
      "jotform:submission-completed",
      "typeform:form-submit",
    ],
    icon: FileCheck,
    color: "blue",
  },
  phone_call: {
    tier: 1,
    label: "Phone Call",
    description: "Click-to-call on a phone number link",
    // Every path below represents the SAME real-world action (a user
    // clicked a phone number). We intentionally collapse the t.js
    // tel:-click listener, form-phone.js's tel-link + text-pattern
    // detectors, and the gtag variants into a single canonical type
    // — so "59 phone calls" on the dashboard means 59 unique user
    // actions, not 59 rows across five overlapping detection paths.
    sources: [
      // Canonical + legacy SiteConversion.conversionType values
      "phone_call",                 // form-phone.js canonical write
      "phone_number_click",         // form-phone.js text-pattern match
      "click_to_call",              // t.js tel: click listener + legacy
      // gtag-intercepted variants
      "gtag:phone_call",
      "gtag:click_to_call",
      "gtag:call_click",
      "gtag:call_now",
      // form-phone.js detectionMethod values
      "tel_link",                   // tel: href match
      "phone_pattern",              // phone-number text-pattern match
    ],
    icon: Phone,
    color: "violet",
  },
  purchase: {
    tier: 1,
    label: "Purchase",
    description: "E-commerce transaction completed",
    sources: [
      "gtag:purchase",
      "gtag:transaction",
      "gtag:order_complete",
      "purchase",
      // URL-based confirmation-page detection (/thank-you, /receipt,
      // /order-confirmation). Fires alongside the gtag path on
      // platforms that emit both — the 30-second resolver dedup
      // collapses them into one SiteConversion row.
      "purchase_completed",
    ],
    icon: CreditCard,
    color: "amber",
  },
  email_contact: {
    tier: 1,
    label: "Email Contact",
    description: "Clicked a mailto: link to open an email composer",
    // Mirrors the phone_call pattern: a click on a mailto: link is a
    // high-intent contact action at parity with click-to-call, so it
    // earns Tier 1. The raw address is intentionally never on the
    // wire — t.js only forwards the email_domain metadata field.
    sources: [
      "email_click",            // t.js mailto: click listener
      "email_contact",          // legacy SiteConversion.conversionType
    ],
    icon: Mail,
    color: "blue",
  },
  sms_contact: {
    tier: 1,
    label: "SMS Contact",
    description: "Clicked an sms: link to open the messaging app",
    // Parallel to email_contact. sms_submit is the pre-existing
    // tracking-only event (carries the full phone number for session
    // timelines); sms_click is the privacy-preserving variant
    // (last-4 only) that drives this canonical. Both fire on the
    // same click — the resolver's 30s dedup collapses downstream
    // writes.
    sources: [
      "sms_click",              // t.js sms: click (privacy-preserving)
      "sms_submit",             // legacy tracking-only variant
      "sms_contact",
    ],
    icon: MessageSquare,
    color: "violet",
  },

  // ── TIER 2 — soft conversions (engagement / intent) ─────────────
  appointment_attempted: {
    tier: 2,
    label: "Appointment Started",
    description: "User began booking flow but may not have completed",
    sources: [
      "booking_submit",             // t.js booking-plugin AJAX path
      "gtag:book_appointment",
      "gtag:schedule_appointment",
      "gtag:appointment_request",
      "gtag:request_appointment",
      "amelia:booking_submit",
      "booking_attempt",            // legacy SiteConversion.conversionType
    ],
    icon: CalendarPlus,
    color: "teal",
  },
  form_started: {
    tier: 2,
    label: "Form Started",
    description: "User focused on a form field",
    sources: [
      "form_start",                 // t.js focusin listener
    ],
    icon: FileEdit,
    color: "slate",
  },
  appointment_intent: {
    tier: 2,
    label: "Appointment Intent",
    description: "Clicked Schedule/Book button before any field interaction",
    sources: [
      "appointment_intent",         // t.js intent-CTA listener
      "form_intent",                // t.js broader intent-CTA listener
      "booking_widget_opened",      // booking-plugin widget open
    ],
    icon: MousePointerClick,
    color: "orange",
  },
  chat_initiated: {
    tier: 2,
    label: "Chat Started",
    description: "Opened live chat widget",
    sources: [
      "chat_opened",
    ],
    icon: MessageCircle,
    color: "cyan",
  },
  newsletter_signup: {
    tier: 2,
    label: "Newsletter Signup",
    description: "Submitted a newsletter / subscribe / mailing-list form",
    // Distinct from form_submitted so dashboards can separate lead-
    // generation forms from mailing-list opt-ins (same action, very
    // different value per conversion). Historical gtag-sourced
    // newsletter signups stay under form_submitted — no retroactive
    // reclassification — but new native-detected signups land here.
    sources: [
      "newsletter_signup",      // t.js newsletter-form + ESP detection
      "mailchimp:submit",
      "constant_contact:submit",
      "convertkit:submit",
      "activecampaign:submit",
      "klaviyo:submit",
    ],
    icon: MailPlus,
    color: "cyan",
  },
  add_to_cart: {
    tier: 2,
    label: "Add to Cart",
    description: "Added a product to the cart (intent to purchase, not yet purchased)",
    // gtag variants cover GA4-instrumented stores (Shopify default,
    // BigCommerce default, most WooCommerce themes). Native detection
    // fires on button clicks matching /add to cart|add to bag|buy now/i
    // or the standard selectors each platform ships.
    sources: [
      "gtag:add_to_cart",
      "gtag:addtocart",
      "add_to_cart",
    ],
    icon: ShoppingCart,
    color: "orange",
  },
  checkout_started: {
    tier: 2,
    label: "Checkout Started",
    description: "Entered the checkout flow (headed for purchase)",
    // checkout_step events roll up under this canonical so the Tier 2
    // funnel stays single-bucket; the raw step identifier is carried
    // in metadata for anyone who needs per-step drop-off detail.
    sources: [
      "gtag:begin_checkout",
      "gtag:checkout_started",
      "checkout_started",
      "checkout_step",
    ],
    icon: ClipboardCheck,
    color: "teal",
  },
  file_download: {
    tier: 2,
    label: "File Downloaded",
    description: "Downloaded a PDF / DOCX / ZIP / media asset",
    // Category metadata (brochure / whitepaper / case_study /
    // pricing / form / menu / manual / other) is stamped into
    // SiteConversion.formFields by writeConversionSideEffect — it's
    // not part of the canonical source list here.
    sources: [
      "file_download",
      "gtag:file_download",
    ],
    icon: Download,
    color: "slate",
  },
  waitlist_signup: {
    tier: 2,
    label: "Waitlist Signup",
    description: "Submitted a waitlist / notify-me form (capacity-constrained intent)",
    // Distinct from form_submitted so dashboards can separate
    // "capacity-constrained" intent (user WANTS the service but
    // can't book it today) from a completed booking. Useful signal
    // for healthcare practices that close panels and need to
    // quantify deferred demand.
    sources: [
      "waitlist_signup",
      "gtag:waitlist_signup",
      "gtag:notify_me",
    ],
    icon: Clock,
    color: "amber",
  },

  // ── SMS Tier 1 — confirmed opt-ins ──────────────────────────────
  sms_optin: {
    tier: 1,
    label: "SMS Opt-in",
    description: "Visitor subscribed to receive marketing or transactional text messages",
    sources: [
      "sms_optin",
      "attentive:subscribe_success",
      "postscript:subscribe",
      "klaviyo_sms:sms_subscribe",
      "smsbump:subscribe",
      "recart:sms_optin",
      "eztexting:subscribe",
      "simpletexting:optin",
      "slicktext:subscribe",
      "community:subscribe",
      "explicit_checkbox",
    ],
    icon: Smartphone,
    color: "teal",
  },
  sms_community_join: {
    tier: 1,
    label: "SMS Community Join",
    description: "Visitor joined a conversational SMS community (e.g. creator or public figure direct SMS)",
    sources: [
      "sms_community_join",
      "community:join",
      "subtext:subscribe",
    ],
    icon: Smartphone,
    color: "teal",
  },

  // ── SMS Tier 2 — intent and partial opt-in signals ──────────────
  sms_keyword_displayed: {
    tier: 2,
    label: "SMS Keyword CTA Seen",
    description: "Page displayed an SMS keyword opt-in call to action (e.g. 'Text JOIN to 55555')",
    sources: [
      "sms_keyword_displayed",
    ],
    icon: Signal,
    color: "cyan",
  },
  sms_outbound_click: {
    tier: 2,
    label: "SMS Link Click",
    description: "Visitor clicked an sms: protocol link to initiate a text to the business",
    sources: [
      "sms_outbound_click",
      "sms_link",
    ],
    icon: Smartphone,
    color: "cyan",
  },
  phone_field_completed: {
    tier: 2,
    label: "Phone Field Completed",
    description: "Visitor filled a phone/tel field in a form (value not captured — privacy-safe)",
    sources: [
      "phone_field_completed",
      "form_field",
    ],
    icon: Phone,
    color: "slate",
  },
  sms_optin_followup: {
    tier: 1,
    label: "SMS Opt-in (Two-Step)",
    description: "Second step of a two-step email-then-SMS opt-in sequence completed",
    sources: [
      "sms_optin_followup",
      "two_step_form",
      "two_step_sequence",
    ],
    icon: Smartphone,
    color: "teal",
  },
};

// ─── Tier helpers ────────────────────────────────────────────────

export const HARD_CONVERSION_TYPES: CanonicalConversionType[] =
  (Object.entries(CONVERSION_TYPES) as Array<[CanonicalConversionType, ConversionTypeDef]>)
    .filter(([, v]) => v.tier === 1)
    .map(([k]) => k);

export const SOFT_CONVERSION_TYPES: CanonicalConversionType[] =
  (Object.entries(CONVERSION_TYPES) as Array<[CanonicalConversionType, ConversionTypeDef]>)
    .filter(([, v]) => v.tier === 2)
    .map(([k]) => k);

/** Returns 1 (hard), 2 (soft), or null (not a recognised conversion). */
export function getConversionTier(type: string): 1 | 2 | null {
  const canon = canonicalConversionType(type);
  if (!canon) return null;
  return CONVERSION_TYPES[canon].tier;
}

// ─── Source → canonical resolver ────────────────────────────────
//
// Builds a reverse-index of source string → canonical type on first
// call. The same source string is intentionally NOT allowed to
// resolve to two different canonical types (that would be
// ambiguous) — if a future edit causes a collision the build-time
// assertion in __selfCheck() throws so we catch it before deploy.

let _sourceIndex: Map<string, CanonicalConversionType> | null = null;

function buildSourceIndex(): Map<string, CanonicalConversionType> {
  if (_sourceIndex) return _sourceIndex;
  const idx = new Map<string, CanonicalConversionType>();
  for (const [key, def] of Object.entries(CONVERSION_TYPES) as Array<[CanonicalConversionType, ConversionTypeDef]>) {
    for (const src of def.sources) {
      const norm = src.toLowerCase();
      if (idx.has(norm) && idx.get(norm) !== key) {
        // Collision — same source mapped to two canonical types. Never
        // happens at runtime if the table above is well-formed; this
        // catches a typo at first import.
        throw new Error(
          `[conversion-taxonomy] source "${src}" resolves to both "${idx.get(norm)}" and "${key}"`
        );
      }
      idx.set(norm, key);
    }
  }
  _sourceIndex = idx;
  return idx;
}

/**
 * Resolve a raw source identifier OR a stored conversionType to its
 * canonical conversion type. Accepts:
 *   - "appointment_booked" / "form_submitted" / etc. (already canonical)
 *   - "form_submission" / "booking_attempt" / "phone_call" (legacy
 *     SiteConversion.conversionType values — mapped via the sources list)
 *   - "gtag:contact_us" / "amelia:booking_confirmed" / etc. (raw source
 *     strings written by the t.js gtag interceptor + plugin registry)
 *   - "click_to_call" / "form_submit" / etc. (raw t.js eventType names)
 *
 * Returns null when the input doesn't resolve to any known type
 * (e.g. an unmapped custom event the operator hasn't categorised).
 */
export function canonicalConversionType(raw: string | null | undefined): CanonicalConversionType | null {
  if (!raw) return null;
  const norm = raw.toLowerCase();
  // Direct hit on a canonical key.
  if (CONVERSION_TYPES[norm as CanonicalConversionType]) {
    return norm as CanonicalConversionType;
  }
  // Source-table lookup.
  const idx = buildSourceIndex();
  return idx.get(norm) ?? null;
}

/** Self-check — runs once on first canonicalConversionType() call.
 *  Surfaces table errors at boot rather than waiting for a query to
 *  hit a colliding source. */
buildSourceIndex();
