/**
 * meSEO Site Tag — Form + Phone conversion tracking
 * --------------------------------------------------
 * Captures conversions the site owner never had to instrument:
 * native submits, fetch/XHR POSTs, iFrame providers via postMessage and
 * URL-pattern detection, mutation-observer success-text fallback, and
 * phone clicks on tel: links + non-linked phone-number patterns.
 *
 * Privacy: field NAMES only, never values. Depends on meSEOAttribution
 * for visitor/session/touchpoint context.
 */
(function () {
  "use strict";

  var PHONE_RE = /(\+?\d[\d\s().-]{8,}\d)/;
  var SUCCESS_RE = /\b(thank you|thanks for|successfully submitted|message sent|we(‘|’)ll be in touch|submission received|form submitted)\b/i;
  var seenForms = {};

  // Registry of cross-origin iframe form/booking providers.
  // Each entry describes how to identify the provider from the iframe src
  // URL and how to recognise its postMessage submission event.
  // conversionType "booking_confirmed" skips the form_submit cooldown and
  // emits the raw booking event instead.
  var IFRAME_PROVIDER_REGISTRY = [
    {
      key: "hubspot",
      urlPattern: /hubspot\.com|hs-scripts\.com|hsforms\.com/i,
      isSubmit: function (d) { return /hsFormCallback|onFormSubmitted/i.test(String(d.type || d.eventName || "")); },
      conversionType: "form_submit",
      verified: false,
    },
    {
      key: "typeform",
      urlPattern: /typeform\.com/i,
      isSubmit: function (d) { return /form[-_]submit/i.test(String(d.type || d.eventName || "")); },
      conversionType: "form_submit",
      verified: false,
    },
    {
      key: "calendly",
      urlPattern: /calendly\.com/i,
      isSubmit: function (d) { return /calendly\.event_scheduled/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
      verified: false,
    },
    {
      key: "jotform",
      urlPattern: /jotform\.com/i,
      isSubmit: function (d) { return /JotFormSubmission|submission[-_]completed/i.test(String(d.type || d.action || "")); },
      conversionType: "form_submit",
      verified: false,
    },
    {
      key: "acuity",
      urlPattern: /acuityscheduling\.com/i,
      isSubmit: function (d) { return /acuitySchedul|scheduled|appointment.*confirm/i.test(String(d.type || d.action || "")); },
      conversionType: "booking_confirmed",
      verified: false,
    },
    {
      key: "formstack",
      urlPattern: /formstack\.com/i,
      isSubmit: function (d) { return /FormstackSubmit|submission/i.test(String(d.type || d.action || "")); },
      conversionType: "form_submit",
    },
    {
      key: "wufoo",
      urlPattern: /wufoo\.com/i,
      isSubmit: function (d) { return /wufooForm|submit/i.test(String(d.type || "")); },
      conversionType: "form_submit",
    },
    {
      key: "gohighlevel",
      urlPattern: /leadconnectorhq\.com|msgsndr\.com|ghl[-_]cdn\.com/i,
      isSubmit: function (d) { return /form[-_]submitted|survey[-_]submitted/i.test(String(d.type || d.event || "")); },
      conversionType: "form_submit",
    },
    {
      key: "nexhealth",
      urlPattern: /nexhealth\.com/i,
      isSubmit: function (d) { return /booking.*confirm|appointment.*booked|nexhealth.*submit/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "patientpop",
      urlPattern: /patientpop\.com/i,
      isSubmit: function (d) { return /appointment.*request|form.*submit/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "doctible",
      urlPattern: /doctible\.com/i,
      isSubmit: function (d) { return /appointment|submit|confirm/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "podium",
      urlPattern: /podium\.com/i,
      isSubmit: function (d) { return /message[-_]sent|form[-_]submit/i.test(String(d.type || d.event || "")); },
      conversionType: "form_submit",
    },
    {
      key: "squareup",
      urlPattern: /squareup\.com|square[-_]cdn\.com/i,
      isSubmit: function (d) { return /appointment.*created|booking.*confirm|payment.*success/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "simplepractice",
      urlPattern: /simplepractice\.com/i,
      isSubmit: function (d) { return /appointment.*request|booking.*submit/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "tebra",
      urlPattern: /tebra\.com|kareo\.com/i,
      isSubmit: function (d) { return /appointment|submit|confirm/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "athenahealth",
      urlPattern: /athenahealth\.com/i,
      isSubmit: function (d) { return /appointment|submit|confirm/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "mychart",
      urlPattern: /mychart\.com|epichosted\.com/i,
      isSubmit: function (d) { return /appointment|submit|confirm/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },
    {
      key: "cerner",
      urlPattern: /cerner\.com|healow\.com/i,
      isSubmit: function (d) { return /appointment|submit|confirm/i.test(String(d.type || d.event || "")); },
      conversionType: "booking_confirmed",
    },

    // ── SMS / Text Marketing providers ──────────────────────────────────
    // conversionType 'sms_optin' for standard marketing opt-ins;
    // 'sms_community_join' for conversational-SMS community platforms.
    {
      key: "attentive",
      urlPattern: /attentivemobile\.com|attn\.tv|attentive\.com/i,
      isSubmit: function (d) { return /attentive\.(subscribe|optin_complete|creative_close)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "postscript",
      urlPattern: /postscript\.io|postscript-pixel\.com/i,
      isSubmit: function (d) { return /postscript\.(subscribe|optin_complete)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "klaviyo_sms",
      urlPattern: /klaviyo\.com|a\.klaviyo\.com/i,
      isSubmit: function (d) { return /klaviyo\.(form_submit|sms_subscribe)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "smsbump",
      urlPattern: /smsbump\.com|yotpo\.com/i,
      isSubmit: function (d) { return /smsbump\.subscribe|yotpo\.sms\.optin/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "recart",
      urlPattern: /recart\.com/i,
      isSubmit: function (d) { return /recart\.(subscribe|sms_optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "voyage",
      urlPattern: /voyagetext\.com|voyage\.com/i,
      isSubmit: function (d) { return /voyage\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "eztexting",
      urlPattern: /eztexting\.com/i,
      isSubmit: function (d) { return /eztexting\.subscribe/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "simpletexting",
      urlPattern: /simpletexting\.com/i,
      isSubmit: function (d) { return /simpletexting\.optin/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "twilio",
      urlPattern: /twilio\.com|studio\.twilio\.com/i,
      isSubmit: function (d) { return /twilio\.(subscribe|optin|sms.*complete)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "textmagic",
      urlPattern: /textmagic\.com/i,
      isSubmit: function (d) { return /textmagic\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "textedly",
      urlPattern: /textedly\.com/i,
      isSubmit: function (d) { return /textedly\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "slicktext",
      urlPattern: /slicktext\.com/i,
      isSubmit: function (d) { return /slicktext\.subscribe/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "tatango",
      urlPattern: /tatango\.com/i,
      isSubmit: function (d) { return /tatango\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "trumpia",
      urlPattern: /trumpia\.com/i,
      isSubmit: function (d) { return /trumpia\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "salesmsg",
      urlPattern: /salesmsg\.com/i,
      isSubmit: function (d) { return /salesmsg\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "heymarket",
      urlPattern: /heymarket\.com/i,
      isSubmit: function (d) { return /heymarket\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "community",
      urlPattern: /community\.com/i,
      isSubmit: function (d) { return /community\.subscribe/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_community_join",
    },
    {
      key: "subtext",
      urlPattern: /subtext\.us|joinsubtext\.com/i,
      isSubmit: function (d) { return /subtext\.(subscribe|join)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_community_join",
    },
    {
      key: "textrequest",
      urlPattern: /textrequest\.com/i,
      isSubmit: function (d) { return /textrequest\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "sakari",
      urlPattern: /sakari\.io/i,
      isSubmit: function (d) { return /sakari\.(subscribe|optin)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "podium_sms",
      urlPattern: /podium\.com|podium\.co/i,
      isSubmit: function (d) { return /podium\.(sms.*optin|webchat.*text)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
    {
      key: "birdeye_sms",
      urlPattern: /birdeye\.com/i,
      isSubmit: function (d) { return /birdeye\.(sms.*optin|messaging.*subscribe)/i.test(String(d.type || d.event || "")); },
      conversionType: "sms_optin",
    },
  ];

  // Per-form fill tracking: first focus timestamp + which fields got input
  var formFillState = {};

  function formKey(form, idx) {
    return form.id || form.action || form.name || ("form_" + idx);
  }

  function ensureFormState(form, idx) {
    var key = formKey(form, idx);
    if (!formFillState[key]) {
      formFillState[key] = { firstFocusAt: 0, filledFields: {}, key: key };
    }
    return formFillState[key];
  }

  function init(cfg) {
    if (!cfg || !cfg.endpoint) return;
    var endpoint = cfg.endpoint;

    function ctx() { try { return (window.meSEOAttribution && window.meSEOAttribution.getContext()) || {}; } catch (e) { return {}; } }

    function send(extra) {
      var c = ctx();
      var first = (c.touchpoints && c.touchpoints[0]) || null;
      var base = {
        siteId: cfg.siteId,
        visitorId: c.visitorId || null,
        sessionId: c.sessionId || null,
        pageUrl: window.location.href,
        firstTouchChannel: first ? first.channel : null,
        firstTouchSource: first ? first.source : null,
        lastTouchChannel: c.currentTouch ? c.currentTouch.channel : null,
        lastTouchSource: c.currentTouch ? c.currentTouch.source : null,
        touchpoints: c.touchpoints || [],
        timestamp: new Date().toISOString(),
      };
      for (var k in extra) if (Object.prototype.hasOwnProperty.call(extra, k)) base[k] = extra[k];
      var body = JSON.stringify(base);
      try { if (navigator.sendBeacon) { navigator.sendBeacon(endpoint, new Blob([body], { type: "application/json" })); return; } } catch (e) {}
      try { fetch(endpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: body, keepalive: true, credentials: "omit" }).catch(function () {}); } catch (e) {}
    }

    // ── Deduplication layer ────────────────────────────────────────────
    // Multiple detection paths (native submit, fetch intercept, XHR
    // intercept, MutationObserver) can all fire for the same user action.
    // The cooldown collapses them: once ANY form_submit is recorded, all
    // other form_submit attempts within the window are suppressed.
    var lastFormSubmitAt = 0;
    var FORM_SUBMIT_COOLDOWN = 3000; // 3 seconds

    // URLs that are DEFINITELY NOT user-initiated form submissions.
    // Matches analytics beacons, ad pixels, chat widgets, error trackers,
    // and our own meSEO endpoints. Without this, every third-party POST
    // on the page (Google Analytics, HotJar, Drift, cookie consent, A/B
    // tests…) would be counted as a form_submit conversion.
    var NON_FORM_RE = /\/(collect|analytics|pixel|beacon|track|event|log|rum|heatmap|conversion|api\/v\d|__)\b|google-analytics|googletagmanager|facebook\.net|doubleclick|hotjar|clarity\.ms|intercom|drift|tawk|sentry|meseo|segment\.io|mixpanel|amplitude|\.gif(\?|$)|\.png(\?|$)/i;

    /** Send a form_submit conversion, gated by the cooldown. Returns true
     *  if the event was actually sent, false if suppressed by dedup. */
    function sendFormSubmit(extra) {
      var now = Date.now();
      if (now - lastFormSubmitAt < FORM_SUBMIT_COOLDOWN) return false;
      lastFormSubmitAt = now;
      extra.conversionType = "form_submit";
      send(extra);
      return true;
    }

    function fieldNames(form) {
      if (!form || !form.elements) return [];
      var out = [];
      for (var i = 0; i < form.elements.length && out.length < 50; i++) {
        var el = form.elements[i];
        if (!el.name || el.type === "submit" || el.type === "button" || el.type === "hidden") continue;
        out.push({ name: el.name, type: el.type || el.tagName.toLowerCase() });
      }
      return out;
    }

    function countTrackableFields(form) {
      if (!form || !form.elements) return 0;
      var n = 0;
      for (var i = 0; i < form.elements.length; i++) {
        var el = form.elements[i];
        if (!el.name || el.type === "submit" || el.type === "button" || el.type === "hidden") continue;
        n++;
      }
      return n;
    }

    function reportForm(form, method) {
      if (!form) return;
      var key = (form.id || form.action || form.name || Array.prototype.indexOf.call(document.forms, form)) + "|" + method;
      if (seenForms[key]) return;
      seenForms[key] = 1;
      var sent = sendFormSubmit({ formId: form.id || null, formProvider: "native", formFields: fieldNames(form), detectionMethod: method });
      if (!sent) return; // Cooldown suppressed — skip the form_filled too

      // Also emit a form_filled event with completion stats. The stats
      // (fieldsTotal/Filled, duration, completionRate) ride along inside
      // the formFields JSON column on SiteConversion since the model has
      // no dedicated columns yet.
      var idx = Array.prototype.indexOf.call(document.forms, form);
      var state = ensureFormState(form, idx);
      var totalFields = countTrackableFields(form);
      var filledCount = Object.keys(state.filledFields).length;
      var fillDurationMs = state.firstFocusAt > 0 ? Date.now() - state.firstFocusAt : 0;
      send({
        conversionType: "form_filled",
        formId: form.id || null,
        formProvider: "native",
        detectionMethod: method,
        formFields: {
          fields: fieldNames(form),
          stats: {
            fieldsTotal: totalFields,
            fieldsFilled: filledCount,
            fillDurationSeconds: Math.round(fillDurationMs / 1000),
            completionRate: totalFields > 0 ? Math.round((filledCount / totalFields) * 100) : 0,
          },
        },
      });
    }

    // 0. Track first focus + which fields the user actually fills.
    // Used to compute fill duration and field-count stats on submit.
    document.addEventListener("focusin", function (e) {
      var el = e.target;
      if (!el || !el.form) return;
      var form = el.form;
      var idx = Array.prototype.indexOf.call(document.forms, form);
      var state = ensureFormState(form, idx);
      if (state.firstFocusAt === 0) state.firstFocusAt = Date.now();
    }, true);

    document.addEventListener("input", function (e) {
      var el = e.target;
      if (!el || !el.form || !el.name) return;
      if (el.type === "submit" || el.type === "button" || el.type === "hidden") return;
      var form = el.form;
      var idx = Array.prototype.indexOf.call(document.forms, form);
      var state = ensureFormState(form, idx);
      if (state.firstFocusAt === 0) state.firstFocusAt = Date.now();
      // Mark this field as filled when it has any non-empty value
      var v = (el.value || "").toString();
      if (v.length > 0) state.filledFields[el.name] = 1;
      else delete state.filledFields[el.name];
    }, true);

    // 1. Native submits (capture phase)
    // Skip when t.source.js is present (window.meSEO set at its init).
    // t.source.js attaches its own submit listener, carries a submissionId
    // through resolveConversion(), and writes under a different sessionId
    // (meseo_sid vs _meseo_sid). Running both produces a duplicate row that
    // neither the submissionId dedup nor the sessionId-keyed fallback dedup
    // can collapse, inflating form_submitted counts ~2x on pages with both
    // scripts. When t.source.js is absent (standalone form-phone.js deploy)
    // this handler remains the only submit detector.
    document.addEventListener("submit", function (e) {
      if (window.meSEO) return;
      if (e.target && e.target.tagName === "FORM") reportForm(e.target, "submit_event");
    }, true);

    // 2–3. fetch() / XHR POST intercepts — REMOVED.
    //
    // These were meant to catch AJAX-only form submissions but in practice
    // fire for every same-origin POST on the page: HubSpot chat messages,
    // cookie consent saves, CMS lazy-loading, A/B test beacons, etc.
    // On a healthcare site this produced 1,700+ false form_submit records
    // per month from blog/content pages that don't even have forms.
    //
    // Real form submissions are already covered by:
    //   1. Native submit handler (submit_event) — captures <form> submits
    //   4. postMessage listener — captures HubSpot, Typeform, Calendly
    //   5. MutationObserver — captures "thank you" success text
    //
    // The only gap is a pure-JS form that uses fetch() to POST data without
    // a <form> element and without any success-text UI. This is rare enough
    // that the tradeoff (missing ~1% of conversions vs 1,700% false
    // positives) is overwhelmingly in favour of removing the intercepts.

    // 4. iFrame provider detection: scan existing iframes, then observe
    //    for late-loaded ones. Emits form_iframe_present once per provider
    //    per page load so the backend knows which embed providers are
    //    active on the site without waiting for a postMessage submission.
    var detectedIframeProviders = {};
    // Engagement tracker keyed by origin (scheme+host). Tracks load count,
    // focus state, and dwell time for cross-origin iframe engagement signals.
    // NEVER accesses contentDocument or contentWindow.
    var iframeOrigins = {};
    // Dedup for iframe signal emissions: visitorId+"|"+origin+"|"+60s-bucket.
    var iframeSignalFired = {};

    function normalizeOrigin(url) {
      try { var u = new URL(url); return u.protocol + "//" + u.hostname; } catch (e) { return ""; }
    }
    function iframeSignalKey(vid, origin) {
      return (vid || "anon") + "|" + origin + "|" + Math.floor(Date.now() / 60000);
    }
    // Emits an iframe engagement or inferred-submit signal.
    // asCompleted=true: promotes to form_submit (completed) only when
    // cfg.countInferredIframeSubmits is enabled for the brand; otherwise
    // always emits form_iframe_present (signal).
    function sendIframeSignal(origin, detectionMethod, asCompleted) {
      var c = ctx();
      var vid = c.visitorId || "anon";
      var k = iframeSignalKey(vid, origin);
      if (iframeSignalFired[k]) return;
      iframeSignalFired[k] = true;
      send({
        conversionType: (asCompleted && cfg.countInferredIframeSubmits) ? "form_submit" : "form_iframe_present",
        detectionMethod: detectionMethod,
        formProvider: "iframe_heuristic",
        formFields: { origin: origin },
      });
    }
    // Registers an iframe for load/engagement tracking. Called from
    // checkIframe() so all iframe elements — registry-matched or not —
    // are tracked. Safe against cross-origin: only attaches a load
    // listener, never reads contentDocument or contentWindow.
    function registerIframe(iframe) {
      var src = (iframe.src || "");
      if (!src || src === "about:blank") return;
      var origin = normalizeOrigin(src);
      if (!origin) return;
      if (!iframeOrigins[origin]) {
        iframeOrigins[origin] = { loadCount: 0, engaged: false, engageTs: 0, dwellMs: 0, lastHeight: 0 };
      }
      var state = iframeOrigins[origin];
      iframe.addEventListener("load", function () {
        state.loadCount++;
        if (state.loadCount > 1 && state.engaged) {
          sendIframeSignal(origin, "iframe_load_after_engage", true);
        }
      }, false);
    }
    function checkIframe(iframe) {
      var src = (iframe.src || "");
      if (!src || src === "about:blank") return;
      for (var pi = 0; pi < IFRAME_PROVIDER_REGISTRY.length; pi++) {
        var p = IFRAME_PROVIDER_REGISTRY[pi];
        if (p.urlPattern.test(src) && !detectedIframeProviders[p.key]) {
          detectedIframeProviders[p.key] = true;
          send({ conversionType: "form_iframe_present", formProvider: p.key, detectionMethod: "iframe_scan" });
        }
      }
      registerIframe(iframe);
    }
    try {
      var allIframes = document.querySelectorAll("iframe");
      for (var ii = 0; ii < allIframes.length; ii++) checkIframe(allIframes[ii]);
      var iframeMo = new MutationObserver(function (muts) {
        for (var mi = 0; mi < muts.length; mi++) {
          var nodes = muts[mi].addedNodes;
          for (var ni = 0; ni < nodes.length; ni++) {
            var node = nodes[ni];
            if (node.tagName === "IFRAME") { checkIframe(node); }
            if (node.querySelectorAll) {
              var nested = node.querySelectorAll("iframe");
              for (var ki = 0; ki < nested.length; ki++) checkIframe(nested[ki]);
            }
          }
        }
      });
      iframeMo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // 4b. window blur/focus for iframe focus-dwell signals.
    // blur + setTimeout(0): by the time the microtask runs, document.activeElement
    // reflects the iframe that captured focus. Sets engaged=true on that origin and
    // emits a signal-tier form_iframe_present (detectionMethod "iframe_focus").
    // focus: accumulates dwell time so downstream analysis can see how long the
    // visitor spent inside each embedded form.
    window.addEventListener("blur", function () {
      setTimeout(function () {
        try {
          var ae = document.activeElement;
          if (!ae || ae.tagName !== "IFRAME") return;
          var origin = normalizeOrigin(ae.src || "");
          if (!origin) return;
          var state = iframeOrigins[origin];
          if (!state) return;
          if (!state.engaged) {
            state.engaged = true;
            state.engageTs = Date.now();
            sendIframeSignal(origin, "iframe_focus", false);
          } else if (!state.engageTs) {
            state.engageTs = Date.now();
          }
        } catch (e) {}
      }, 0);
    }, false);
    window.addEventListener("focus", function () {
      try {
        for (var fo in iframeOrigins) {
          if (!Object.prototype.hasOwnProperty.call(iframeOrigins, fo)) continue;
          var fst = iframeOrigins[fo];
          if (fst.engageTs > 0) {
            fst.dwellMs += Date.now() - fst.engageTs;
            fst.engageTs = 0;
          }
        }
      } catch (e) {}
    }, false);

    // 4c. iFrame providers via postMessage — registry-driven.
    // First matching provider wins; booking providers bypass the
    // form_submit cooldown and emit a booking_confirmed event instead.
    // Also handles iframe-resizer height messages: a significant height
    // change (>50px) on an engaged iframe origin emits a signal-tier
    // iframe_resize_after_engage. Never produces a completed conversion.
    window.addEventListener("message", function (e) {
      if (!e || !e.data) return;
      var d = e.data;
      if (typeof d === "string") {
        try { d = JSON.parse(d); } catch (x) { d = { type: d }; }
      }
      // Height message handling (iframe-resizer and similar patterns).
      var msgOrigin = "";
      try { msgOrigin = e.origin ? normalizeOrigin(e.origin) : ""; } catch (x) {}
      if (msgOrigin && iframeOrigins[msgOrigin]) {
        var hst = iframeOrigins[msgOrigin];
        var heightVal = null;
        if (d && typeof d === "object") {
          if (typeof d.height === "number") heightVal = d.height;
          else if (d.type === "height" && typeof d.value === "number") heightVal = d.value;
        }
        if (heightVal !== null && Math.abs(heightVal - hst.lastHeight) > 50) {
          hst.lastHeight = heightVal;
          if (hst.engaged) sendIframeSignal(msgOrigin, "iframe_resize_after_engage", false);
        }
      }
      for (var ri = 0; ri < IFRAME_PROVIDER_REGISTRY.length; ri++) {
        var reg = IFRAME_PROVIDER_REGISTRY[ri];
        if (!reg.isSubmit(d)) continue;
        if (reg.conversionType === "booking_confirmed") {
          send({ conversionType: "booking_confirmed", formProvider: reg.key, detectionMethod: "iframe_postmessage", formFields: [] });
        } else {
          sendFormSubmit({ formProvider: reg.key, detectionMethod: "iframe_postmessage", formFields: [] });
        }
        break;
      }
    }, false);

    // 5. Framework-specific success events + fallback MO.
    //
    // Per-source dedup: successFired[key] prevents a single source from
    // firing more than once per page load. The FORM_SUBMIT_COOLDOWN still
    // collapses events within 3 s (server resolver handles the rest).
    var successFired = {};
    function sendSuccessSubmit(formProvider, detectionMethod) {
      var key = formProvider + "|" + detectionMethod;
      if (successFired[key]) return;
      successFired[key] = 1;
      sendFormSubmit({ formProvider: formProvider, detectionMethod: detectionMethod, successConfirmed: true, formFields: [] });
    }

    // 5a. Contact Form 7
    try {
      document.addEventListener("wpcf7mailsent", function () {
        sendSuccessSubmit("contactform7", "success:cf7");
      }, false);
    } catch (e) {}

    // 5b. Gravity Forms — jQuery event (also works without jQuery via CustomEvent)
    try {
      document.addEventListener("gform_confirmation_loaded", function () {
        sendSuccessSubmit("gravityforms", "success:gravity");
      }, false);
    } catch (e) {}

    // 5c. WPForms — wait for confirmation container to appear
    try {
      var wpfMo = new MutationObserver(function () {
        if (document.querySelector(".wpforms-confirmation-container-full,.wpforms-confirmation-container")) {
          wpfMo.disconnect();
          sendSuccessSubmit("wpforms", "success:wpforms");
        }
      });
      wpfMo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // 5d. Elementor forms
    try {
      document.addEventListener("submit_success", function () {
        sendSuccessSubmit("elementor", "success:elementor");
      }, false);
      var elMo = new MutationObserver(function () {
        if (document.querySelector(".elementor-message-success")) {
          elMo.disconnect();
          sendSuccessSubmit("elementor", "success:elementor");
        }
      });
      elMo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // 5e. Ninja Forms
    try {
      document.addEventListener("nfFormSubmitResponse", function () {
        sendSuccessSubmit("ninjaforms", "success:ninja");
      }, false);
    } catch (e) {}

    // 5f. Fluent Forms — jQuery event + DOM MutationObserver
    try {
      if (window.jQuery) {
        window.jQuery(document).on("fluentform_submission_success fluentform_submission_completed", function () {
          sendSuccessSubmit("fluentforms", "success:fluentforms");
        });
      }
      // jQuery may load after init on some themes; also listen via native CustomEvent.
      document.addEventListener("fluentform_submission_success", function () {
        sendSuccessSubmit("fluentforms", "success:fluentforms");
      }, false);
      document.addEventListener("fluentform_submission_completed", function () {
        sendSuccessSubmit("fluentforms", "success:fluentforms");
      }, false);
      var ffMo = new MutationObserver(function () {
        if (document.querySelector(".ff-message-success,.fluentform-success,.ff_submit_success,.ff_submit_success_message,[data-ff_success]")) {
          ffMo.disconnect();
          sendSuccessSubmit("fluentforms", "success:fluentforms");
        }
      });
      ffMo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // 5g. Generic success containers (Formidable, custom themes)
    try {
      var GENERIC_SUCCESS_SEL = [
        ".frm_message",
        ".wpforms-confirmation", ".gf_confirmation_message",
        "[class*='confirmation'][class*='success']", "[class*='success'][class*='message']",
      ].join(",");
      var genericMo = new MutationObserver(function () {
        if (document.querySelector(GENERIC_SUCCESS_SEL)) {
          genericMo.disconnect();
          sendSuccessSubmit("custom", "success:container");
        }
      });
      genericMo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // 5h. Thank-you navigation — detect a URL change to a path matching
    //     common thank-you patterns after a prior form interaction on this page.
    //     Uses the same-origin path normalizer so /thank-you, /thank_you,
    //     /thanks, /confirmation, /success, /booking-confirmed all match.
    //     Also checks query params: ?fluentform_success and ?submitted cover
    //     Fluent Forms and WordPress themes that append a flag instead of
    //     navigating to a separate path. Gate (hadFormInteraction) prevents
    //     firing on direct/landing visits to thank-you URLs.
    var THANKYOU_RE = /\/(thank[_-]?you|thanks|confirmation|success|booking[_-]confirmed|submitted|received)(\/|$|\?|#)/i;
    var THANKYOU_SEARCH_RE = /[?&](submitted|fluentform_success)(?:=|&|$)/i;
    var hadFormInteraction = false;
    document.addEventListener("submit", function () { hadFormInteraction = true; }, true);
    try {
      var _histPush = history.pushState;
      var _histReplace = history.replaceState;
      function checkThankyouNav(url) {
        try {
          var u = new URL(url, location.href);
          if (hadFormInteraction && (THANKYOU_RE.test(u.pathname) || THANKYOU_SEARCH_RE.test(u.search))) {
            sendSuccessSubmit("custom", "success:thankyou_nav");
          }
        } catch (e) {}
      }
      history.pushState = function () { _histPush.apply(this, arguments); checkThankyouNav(arguments[2] || ""); };
      history.replaceState = function () { _histReplace.apply(this, arguments); checkThankyouNav(arguments[2] || ""); };
      window.addEventListener("popstate", function () { checkThankyouNav(location.href); }, false);
    } catch (e) {}

    // 5i. Success-text MutationObserver fallback (runs only when no
    //     framework-specific signal has fired yet for "custom").
    try {
      var mo = new MutationObserver(function (muts) {
        if (successFired["custom|mutation_observer"]) return;
        for (var i = 0; i < muts.length; i++) {
          var ns = muts[i].addedNodes;
          for (var j = 0; j < ns.length; j++) {
            var text = (ns[j].innerText || ns[j].textContent || "").slice(0, 500);
            if (text && SUCCESS_RE.test(text)) {
              sendSuccessSubmit("custom", "mutation_observer");
              return;
            }
          }
        }
      });
      mo.observe(document.body || document.documentElement, { childList: true, subtree: true });
    } catch (e) {}

    // ── Success-probe telemetry (temporary) ────────────────────────────
    // Gate is enforced server-side by brandId; client fires for all sites.
    // Emits a SiteEvent (NOT a conversion) ONLY when no framework success
    // detector fires within 5 s of a submit, capturing what DID change so
    // we can reverse-engineer the site's real success mechanism.
    try {
      var SUCCESS_VOCAB_RE_PROBE = /thank|success|receiv|submit|confirm|message sent|get back|in touch/i;
      var probeEventEndpoint = cfg.endpoint.replace(/\/conversion$/, "/event");
      var probeInFlight = false;

      document.addEventListener("submit", function (e) {
        if (probeInFlight) return;
        if (window.meSEO) return;
        probeInFlight = true;

        var form = (e && e.target && e.target.tagName === "FORM") ? e.target : null;
        var container = form;
        if (form) {
          var pp = form.parentElement;
          while (pp && pp !== document.body && pp !== document.documentElement) {
            container = pp;
            pp = pp.parentElement;
          }
        }

        var startUrl = { path: location.pathname, search: location.search, hash: location.hash };
        var addedEls = [];
        var matchedText = null;
        var eventsHeard = {};

        var probeMo = new MutationObserver(function (muts) {
          for (var mi = 0; mi < muts.length && addedEls.length < 20; mi++) {
            var ns = muts[mi].addedNodes;
            for (var ni = 0; ni < ns.length && addedEls.length < 20; ni++) {
              var n = ns[ni];
              if (!n || n.nodeType !== 1) continue;
              addedEls.push({
                tag: (n.tagName || "").toLowerCase(),
                cls: (n.className && typeof n.className === "string") ? n.className.slice(0, 120) : ""
              });
              if (!matchedText) {
                var txt = (n.innerText || n.textContent || "").slice(0, 300);
                if (SUCCESS_VOCAB_RE_PROBE.test(txt)) matchedText = txt.slice(0, 120);
              }
            }
          }
        });
        probeMo.observe(container || document.body || document.documentElement, { childList: true, subtree: true });

        var knownEvents = ["wpcf7mailsent", "gform_confirmation_loaded", "submit_success",
          "nfFormSubmitResponse", "fluentform_submission_success", "fluentform_submission_completed"];
        knownEvents.forEach(function (ev) {
          document.addEventListener(ev, function () { eventsHeard[ev] = 1; }, { once: true, capture: false });
        });

        setTimeout(function () {
          probeMo.disconnect();
          if (Object.keys(successFired).length > 0) return;

          var endUrl = { path: location.pathname, search: location.search, hash: location.hash };
          var urlDelta = (endUrl.path !== startUrl.path || endUrl.search !== startUrl.search || endUrl.hash !== startUrl.hash)
            ? { from: startUrl, to: endUrl } : null;

          var c2 = (window.meSEOAttribution && window.meSEOAttribution.getContext) ? window.meSEOAttribution.getContext() : {};
          var probePayload = JSON.stringify({
            siteId: cfg.siteId,
            visitorId: c2.visitorId || null,
            sessionId: c2.sessionId || null,
            pageUrl: window.location.href,
            eventType: "form_success_probe",
            eventData: {
              addedEls: addedEls,
              matchedText: matchedText,
              urlDelta: urlDelta,
              eventsHeard: Object.keys(eventsHeard),
              formId: form ? (form.id || form.getAttribute("data-formid") || null) : null,
              formAction: form ? (form.action || null) : null,
            }
          });
          try { fetch(probeEventEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: probePayload, keepalive: true, credentials: "omit" }).catch(function () {}); } catch (e2) {}
        }, 5000);
      }, true);
    } catch (e) {}

    // 6. Phone tracking — intent-based signals only
    //
    // Three legitimate phone conversion signals:
    //   tel_click    — user clicked a tel: link (HIGHEST confidence)
    //   phone_input  — user typed a phone number into a form field
    //   phone_copy   — user selected and copied a phone number
    //
    // The previous phone_pattern visibility-based detection (which
    // fired on any click near a phone number in the DOM) has been
    // REMOVED — it produced 865 false conversions on a single day
    // for one brand because every page view with a header phone
    // number triggered a conversion.

    function locationOf(el) {
      var p = el;
      while (p && p !== document.body) {
        var tag = (p.tagName || "").toLowerCase();
        if (tag === "header") return "header";
        if (tag === "footer") return "footer";
        if (tag === "nav") return "nav";
        if (tag === "aside") return "sidebar";
        if (tag === "main") return "main_content";
        p = p.parentElement;
      }
      return "main_content";
    }

    // Client-side dedup: one conversion per type + phone + 5min bucket
    var phoneFired = {};
    function phoneKey(type, digits) {
      var bucket = Math.floor(Date.now() / 300000); // 5-minute bucket
      return type + ":" + digits + ":" + bucket;
    }
    function shouldFire(type, digits) {
      if (window.__meseoOptOut) return false;
      var k = phoneKey(type, digits);
      if (phoneFired[k]) return false;
      phoneFired[k] = true;
      return true;
    }

    // 6a. tel_click — user clicks a tel: link
    document.addEventListener("click", function (e) {
      var el = e.target, a = el;
      while (a && a.tagName !== "A") a = a.parentElement;
      if (a && a.href && a.href.toLowerCase().indexOf("tel:") === 0) {
        var digits = a.href.replace(/^tel:/i, "").replace(/\D/g, "");
        if (!shouldFire("tel_click", digits)) return;
        send({
          conversionType: "phone_call",
          phoneNumber: a.href.replace(/^tel:/i, ""),
          elementLocation: locationOf(a),
          detectionMethod: "tel_click",
          formFields: [{ name: "_text", type: (a.innerText || "").trim().slice(0, 80) }],
        });
      }
    }, true);

    // 6b. phone_input — user types a phone number into a form field
    document.addEventListener("blur", function (e) {
      var el = e.target;
      if (!el || !el.tagName) return;
      var tag = el.tagName.toLowerCase();
      if (tag !== "input") return;
      var type = (el.type || "").toLowerCase();
      var name = (el.name || "").toLowerCase();
      var id = (el.id || "").toLowerCase();
      if (type !== "tel" && !name.includes("phone") && !id.includes("phone")) return;
      var val = (el.value || "").replace(/\D/g, "");
      if (val.length < 10) return; // not a phone number
      if (!shouldFire("phone_input", val)) return;
      send({
        conversionType: "phone_input",
        phoneNumber: el.value,
        elementLocation: locationOf(el),
        detectionMethod: "form_input",
      });
    }, true);

    // 6c. phone_copy — user selects and copies a phone number
    document.addEventListener("copy", function () {
      try {
        var sel = (window.getSelection() || "").toString().trim();
        if (!sel || sel.length > 30) return; // too long to be just a phone number
        if (!PHONE_RE.test(sel)) return;
        var digits = sel.replace(/\D/g, "");
        if (digits.length < 10) return;
        if (!shouldFire("phone_copy", digits)) return;
        send({
          conversionType: "phone_copy",
          phoneNumber: sel,
          detectionMethod: "clipboard",
        });
      } catch (e) {}
    }, false);
  }

  window.meSEOFormPhone = { init: init };
})();
