// public/visitor-tracking.jsx
// window.visitorTracker — initializes PostHog, manages the engagement gate,
// and debounces sync PATCHes to /api/sync-visitor.
//
// Load order: must be loaded BEFORE email-gate.jsx and app.jsx in index.html.

(function () {
  // === Config ===
  // PostHog key + host are injected into the page via Vite's HTML env
  // substitution. index.html sets window.POSTHOG_KEY = "%VITE_POSTHOG_KEY%"
  // and window.POSTHOG_HOST = "%VITE_POSTHOG_HOST%" before this script
  // loads — the values come from .env at build time. Falls back to the
  // public host directly if unset (e.g. local dev without env).
  var POSTHOG_KEY = window.POSTHOG_KEY || "";
  var POSTHOG_HOST = window.POSTHOG_HOST || "https://us.i.posthog.com";
  var SYNC_ENDPOINT = "/api/sync-visitor";
  var SYNC_DEBOUNCE_MS = 2000;
  var SCHEMA_VERSION = 1;

  // Non-prod hosts (localhost, *.pages.dev preview deploys, reurbano.dev)
  // still load PostHog so window.posthog is inspectable, but events get
  // flagged via setInternalOrTestUser() and the Attio sync is skipped
  // outright. PostHog dashboards filter $is_internal_user via project-level
  // Test Account Filters; Attio has no equivalent filter, so the cleanest
  // option is just not to write.
  var isProdHost = /(^|\.)reurbano\.mx$/i.test(location.hostname);

  // Refuse to init without a real key — avoids accidentally sending events to
  // an empty/test project if .env is missing.
  if (!POSTHOG_KEY || POSTHOG_KEY.indexOf("phc_") !== 0) {
    console.warn("visitor-tracker: POSTHOG_KEY not set or invalid; tracker disabled");
    window.visitorTracker = {
      // Positive, observable marker that this is the intentional no-op stub
      // (not a real tracker, not a broken page). Smoke tests key off this.
      disabled: true,
      init: function () {},
      onEmailSubmit: function () {},
      onProfileChange: function () {},
      onProductOpen: function () {},
      onProductClose: function () {},
      onDeckOpen: function () {}, onDeckSlideView: function () {},
      onDeckComplete: function () {}, onDeckDismiss: function () {},
      onPanelSectionView: function () {}, onPanelMediaView: function () {},
    };
    return;
  }

  // === Feature flag (rollback path) ===
  var enabled = typeof window.VISITOR_TRACKING_ENABLED === "undefined" || window.VISITOR_TRACKING_ENABLED === true;
  if (!enabled) {
    window.visitorTracker = {
      // Same intentional-no-op marker as the missing-key branch above.
      disabled: true,
      init: function () {},
      onEmailSubmit: function () {},
      onProfileChange: function () {},
      onProductOpen: function () {},
      onProductClose: function () {},
      onDeckOpen: function () {}, onDeckSlideView: function () {},
      onDeckComplete: function () {}, onDeckDismiss: function () {},
      onPanelSectionView: function () {}, onPanelMediaView: function () {},
    };
    return;
  }

  // === PostHog SDK loader (official snippet, 2026-01-30 defaults) ===
  !(function (t, e) {
    var o, n, p, r;
    e.__SV ||
      (window.posthog && window.posthog.__loaded) ||
      ((window.posthog = e),
      (e._i = []),
      (e.init = function (i, s, a) {
        function g(t, e) {
          var o = e.split(".");
          2 == o.length && ((t = t[o[0]]), (e = o[1])),
            (t[e] = function () {
              t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
            });
        }
        ((p = t.createElement("script")).type = "text/javascript"),
          (p.crossOrigin = "anonymous"),
          (p.async = !0),
          (p.src = s.api_host.replace(".i.posthog.com", "-assets.i.posthog.com") + "/static/array.js"),
          (r = t.getElementsByTagName("script")[0]).parentNode.insertBefore(p, r);
        var u = e;
        for (
          void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
            u.people = u.people || [],
            u.toString = function (t) {
              var e = "posthog";
              return "posthog" !== a && (e += "." + a), t || (e += " (stub)"), e;
            },
            u.people.toString = function () {
              return u.toString(1) + ".people (stub)";
            },
            o =
              "Mi Ri init Vi Gi Rr Wi Ji Bi capture calculateEventProperties tn register register_once register_for_session unregister unregister_for_session an getFeatureFlag getFeatureFlagPayload getFeatureFlagResult isFeatureEnabled reloadFeatureFlags updateFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSurveysLoaded onSessionId getSurveys getActiveMatchingSurveys renderSurvey displaySurvey cancelPendingSurvey canRenderSurvey canRenderSurveyAsync un identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset setIdentity clearIdentity get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException addExceptionStep captureLog startExceptionAutocapture stopExceptionAutocapture loadToolbar get_property getSessionProperty nn Xi createPersonProfile setInternalOrTestUser sn Hi cn opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing get_explicit_consent_status is_capturing clear_opt_in_out_capturing Ki debug Lr rn getPageViewId captureTraceFeedback captureTraceMetric Di".split(
                " "
              ),
            n = 0;
          n < o.length;
          n++
        )
          g(u, o[n]);
        e._i.push([i, s, a]);
      }),
      (e.__SV = 1));
  })(document, window.posthog || []);

  posthog.init(POSTHOG_KEY, {
    api_host: POSTHOG_HOST,
    ui_host: "https://us.posthog.com",
    defaults: "2026-01-30",
    person_profiles: "identified_only",
    persistence: "localStorage+cookie",
    cross_subdomain_cookie: true,
    respect_dnt: true,
    autocapture: true,
    capture_pageview: true,
    debug: !isProdHost,
    loaded: function (ph) {
      if (/bot|crawler|spider|preview|fetch|headless/i.test(navigator.userAgent)) {
        ph.opt_out_capturing();
      }
      if (!isProdHost) {
        try { ph.setInternalOrTestUser(); } catch (_) {}
        console.info("visitor-tracker: non-prod host (" + location.hostname + ") — events flagged as $is_internal_user, sync-visitor disabled");
      }
      try {
        var qs = new URLSearchParams(window.location.search);
        var gclid = qs.get("gclid");
        if (gclid) ph.register({ gclid: gclid });
      } catch (_) {}
      // Resolve ?inv=<token> after PostHog is loaded so identify/register
      // fire against the real SDK, not the queueing stub.
      tryResolveInvToken();
    },
  });

  // === Inv-token resolver (per-investor PDF link identification) ===
  // Decodes ?inv=<token> via /api/resolve-inv, identifies the viewer in
  // PostHog, suppresses the email gate, and strips ?inv= from the URL.
  // Token is minted by ~/Reurbano/investor-tracking CLI.
  function tryResolveInvToken() {
    try {
      var qs = new URLSearchParams(window.location.search);
      var inv = qs.get("inv");
      if (!inv) return;
      // Identity guard: if this browser is already identified to an email, do
      // not let a (possibly forwarded) inv token override it. Still strip inv.
      var alreadyIdentified = false;
      try {
        var gs = JSON.parse(localStorage.getItem("r-gate-state") || "null");
        alreadyIdentified = !!(gs && gs.status === "submitted" && gs.email);
      } catch (_) {}
      if (alreadyIdentified) {
        try { posthog.capture("inv_token_ignored", { reason: "already_identified" }); } catch (_) {}
        try {
          qs.delete("inv");
          var ns = qs.toString();
          window.history.replaceState({}, "", window.location.pathname + (ns ? "?" + ns : "") + window.location.hash);
        } catch (_) {}
        return;
      }
      fetch("/api/resolve-inv?t=" + encodeURIComponent(inv), { method: "GET", credentials: "omit" })
        .then(function (res) {
          if (!res.ok) {
            return res.text().then(function (body) {
              console.warn("visitor-tracker: inv resolve " + res.status + " " + body);
              return null;
            });
          }
          return res.json();
        })
        .then(function (data) {
          if (!data || !data.ok || !data.email) return;
          try {
            localStorage.setItem("r-gate-state", JSON.stringify({
              status: "submitted",
              email: data.email,
              ts: Date.now(),
              source: "inv_token",
            }));
          } catch (_) {}
          // Identified by the link: later engagement in this page load syncs with this email.
          snapshot.email = String(data.email).trim().toLowerCase();
          try {
            posthog.identify(data.email, { email: data.email, send_id: data.sendId, identified_via: "inv_token" });
            posthog.register({ send_id: data.sendId });
            posthog.capture("inv_token_resolved_client", { send_id: data.sendId });
          } catch (_) {}
          try {
            qs.delete("inv");
            var newSearch = qs.toString();
            var newUrl = window.location.pathname + (newSearch ? "?" + newSearch : "") + window.location.hash;
            window.history.replaceState({}, "", newUrl);
          } catch (_) {}
        })
        .catch(function (err) {
          console.warn("visitor-tracker: inv resolve error", err);
        });
    } catch (_) {}
  }

  // === Engagement gate (per-tab sessionStorage) ===
  var GATE_KEY = "r-vt-engaged";
  function gateActive() {
    try { return sessionStorage.getItem(GATE_KEY) === "1"; } catch (_) { return false; }
  }
  function tripGate() {
    try { sessionStorage.setItem(GATE_KEY, "1"); } catch (_) {}
  }

  // === Snapshot state ===
  // engaged seeds from the sessionStorage gate so it survives in-tab reloads.
  // Without this, post-reload product opens leave engaged=false and the sync
  // endpoint short-circuits, silently dropping Attio writes.
  var initTime = Date.now();
  var snapshot = {
    email: null,
    engaged: gateActive(),
    profile: {},
    productsViewed: [],
    lastProductViewed: null,
    firstReferrer: null,
    firstUtm: null,
    firstDeviceType: null,
    firstGclid: null,
    posthogDistinctId: null,
    isNewSession: true,
    // Engagement-depth signals consumed by sync-visitor (Attio rollup + Slack alert).
    deckCompleted: false,
    slidesViewed: 0,
    deckSlideCount: 0,
    sectionDwellMs: {},
  };
  // Story slides viewed this page load, by number. slidesViewed is how many distinct slides that is,
  // so a jump that sweeps past slides, or rereading one, does not inflate it.
  var slidesSeen = {};
  // A visitor who gave their email on an earlier visit (or was identified by an ?inv= link) is still
  // that person. flushNow() needs snapshot.email, and only onEmailSubmit used to set it, so nothing
  // they did on a later visit reached /api/sync-visitor. The gate and the inv resolver both save the
  // email in r-gate-state with status "submitted".
  try {
    var savedGate = JSON.parse(localStorage.getItem("r-gate-state") || "null");
    if (savedGate && savedGate.status === "submitted" && typeof savedGate.email === "string" && savedGate.email.trim()) {
      snapshot.email = savedGate.email.trim().toLowerCase();
    }
  } catch (_) {}
  function readPosthogProps() {
    try {
      snapshot.posthogDistinctId = posthog.get_distinct_id();
      snapshot.firstReferrer = posthog.get_property("$initial_referrer") || null;
      snapshot.firstDeviceType = posthog.get_property("$device_type") || null;
      snapshot.firstGclid = posthog.get_property("gclid") || null;
      var utm = {
        source: posthog.get_property("$initial_utm_source") || undefined,
        medium: posthog.get_property("$initial_utm_medium") || undefined,
        campaign: posthog.get_property("$initial_utm_campaign") || undefined,
      };
      snapshot.firstUtm = utm.source || utm.medium || utm.campaign ? utm : null;
    } catch (_) {}
  }

  // === Debounced sync ===
  var syncTimer = null;
  // Top panel sections by accumulated dwell time (most-engaged first).
  function topSections(max) {
    var entries = Object.keys(snapshot.sectionDwellMs || {}).map(function (k) {
      return [k, snapshot.sectionDwellMs[k]];
    });
    entries.sort(function (a, b) { return b[1] - a[1]; });
    return entries.slice(0, max || 3).map(function (e) { return e[0]; });
  }
  function buildPayload() {
    return {
      version: SCHEMA_VERSION,
      email: snapshot.email,
      engaged: snapshot.engaged,
      profile: snapshot.profile,
      productsViewed: snapshot.productsViewed,
      lastProductViewed: snapshot.lastProductViewed,
      deckCompleted: snapshot.deckCompleted,
      slidesViewed: snapshot.slidesViewed,
      deckSlideCount: snapshot.deckSlideCount,
      topSections: topSections(3),
      posthogDistinctId: snapshot.posthogDistinctId,
      firstReferrer: snapshot.firstReferrer,
      firstUtm: snapshot.firstUtm,
      firstDeviceType: snapshot.firstDeviceType,
      firstGclid: snapshot.firstGclid,
      isNewSession: snapshot.isNewSession,
      ts: Date.now(),
    };
  }
  function flushNow(useBeacon) {
    if (!snapshot.email) return;
    readPosthogProps();
    var payload = buildPayload();
    if (!isProdHost) {
      console.info("visitor-tracker: skip sync-visitor (non-prod host)", payload);
      return;
    }
    var body = JSON.stringify(payload);
    if (useBeacon && navigator.sendBeacon) {
      try {
        navigator.sendBeacon(SYNC_ENDPOINT, new Blob([body], { type: "application/json" }));
        return;
      } catch (_) {}
    }
    fetch(SYNC_ENDPOINT, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: body,
      keepalive: true,
    })
      .then(function (res) {
        if (!res.ok) {
          res.text().then(function (t) {
            console.warn("visitor-tracker: sync-visitor " + res.status + " " + t);
          }).catch(function () {
            console.warn("visitor-tracker: sync-visitor " + res.status);
          });
        }
      })
      .catch(function (err) {
        console.warn("visitor-tracker: sync-visitor network error", err);
      });
  }
  function scheduleSync() {
    if (syncTimer) clearTimeout(syncTimer);
    syncTimer = setTimeout(function () { flushNow(false); }, SYNC_DEBOUNCE_MS);
  }

  document.addEventListener("visibilitychange", function () {
    if (document.visibilityState === "hidden") flushNow(true);
  });

  // === Public API ===
  window.visitorTracker = {
    // Real, active tracker — explicitly not the disabled no-op stub.
    disabled: false,
    init: function () {},
    onEmailSubmit: function (email, meta) {
      if (!email) return;
      snapshot.email = String(email).trim().toLowerCase();
      // Pass email as a Person property (second arg = $set payload). Storing
      // it both as distinct_id and as a filterable Person property means it
      // attaches to every future event from this person.
      try { posthog.identify(snapshot.email, { email: snapshot.email }); } catch (_) {}
      posthog.capture("email_submitted", Object.assign({
        email: snapshot.email,
        products_viewed_before_submit: snapshot.productsViewed.length,
        time_on_site_ms: Date.now() - initTime,
        had_profile: Object.keys(snapshot.profile || {}).length > 0,
      }, meta || {}));
      scheduleSync();
    },
    onProfileChange: function (field, value, eligibleCount) {
      if (!snapshot.profile) snapshot.profile = {};
      snapshot.profile[field] = value;
      posthog.capture("profile_updated", { field: field, new_value: value, eligible_count: eligibleCount });
      if (snapshot.engaged) scheduleSync();
    },
    onProductOpen: function (productId, meta) {
      var firstTime = !gateActive();
      if (firstTime) {
        tripGate();
        snapshot.engaged = true;
        posthog.capture("engagement_gate_tripped", { product_id: productId });
      }
      snapshot.lastProductViewed = productId;
      if (snapshot.productsViewed.indexOf(productId) === -1) snapshot.productsViewed.push(productId);
      posthog.capture("product_opened", Object.assign({
        product_id: productId,
        is_first_product_opened: firstTime,
        time_since_pageview_ms: Date.now() - initTime,
      }, meta || {}));
      scheduleSync();
    },
    onProductClose: function (productId, timeOpenMs) {
      posthog.capture("product_closed", { product_id: productId, time_open_ms: timeOpenMs });
    },
    onDeckOpen: function (deckId, meta) {
      if (meta && meta.slide_count) snapshot.deckSlideCount = meta.slide_count;
      posthog.capture("deck_opened", Object.assign({ deck_id: deckId, time_since_pageview_ms: Date.now() - initTime }, meta || {}));
      if (snapshot.engaged) scheduleSync();
    },
    onDeckSlideView: function (deckId, slideNumber, slideCount, slideKey) {
      slidesSeen[slideNumber] = true;
      snapshot.slidesViewed = Object.keys(slidesSeen).length;
      if (slideCount) snapshot.deckSlideCount = slideCount;
      posthog.capture("deck_slide_viewed", { deck_id: deckId, slide_index: slideNumber, slide_count: slideCount, slide_key: slideKey });
    },
    onDeckComplete: function (deckId) {
      snapshot.deckCompleted = true;
      // Completing the whole narrative deck is a strong signal — count it as
      // engagement so the visitor syncs to Attio and the hot-investor alert can
      // evaluate even if they never opened a product cell.
      if (!gateActive()) { tripGate(); snapshot.engaged = true; }
      posthog.capture("deck_completed", { deck_id: deckId });
      scheduleSync();
    },
    onDeckDismiss: function (deckId, lastSlide) {
      posthog.capture("deck_dismissed", { deck_id: deckId, last_slide: lastSlide });
    },
    // Per-section dwell on the rich offering panel — fired from the scrollspy
    // IntersectionObserver when a section leaves view (dwellMs = time in view).
    onPanelSectionView: function (productId, section, dwellMs) {
      if (section && dwellMs > 0) {
        snapshot.sectionDwellMs[section] = (snapshot.sectionDwellMs[section] || 0) + dwellMs;
      }
      posthog.capture("panel_section_viewed", { product_id: productId, section: section, dwell_ms: dwellMs });
      if (snapshot.engaged) scheduleSync();
    },
    // Panel hero/render carousel media advance.
    onPanelMediaView: function (productId, imageIndex) {
      posthog.capture("panel_media_viewed", { product_id: productId, image_index: imageIndex });
    },
  };
})();
