
/* v2.4 */
/* =========================
 *     PHONE CONFIG
 * ========================= */

/**
 * Controls phone input behavior.
 * 'strict'  -> lock to STRICT_COUNTRY_ISO2 with NO dropdown
 * 'multi'   -> allow multiple countries and dropdown per MULTIGEO_* flags
 * @type {'strict'|'multi'}
 */
const PHONE_GEO_MODE = 'strict';

/**
 * Dial code for Turkey (used as fallback).
 * @type {string}
 */
const START_DIAL_CODE = '90';

/**
 * Default phone number example/placeholder.
 * @type {string}
 */
const DEFAULT_PHONE_PLACEHOLDER = '0501 234 56 78';

/* -------------------------
 *   STRICT MODE CONFIG
 * ------------------------- */

/**
 * Lowercase ISO2 code to lock to when PHONE_GEO_MODE === 'strict'
 * @type {string}
 */
const STRICT_COUNTRY_ISO2 = 'tr';

/* -------------------------
 *   MULTI-GEO MODE CONFIG
 * ------------------------- */

/**
 * Initial country for multi-geo mode (lowercase ISO2).
 * If you have php_var.country_code available, you can set it there.
 * @type {string}
 */
const MULTIGEO_INITIAL_COUNTRY =
  (typeof php_var !== 'undefined' && php_var && php_var.country_code)
    ? String(php_var.country_code).toLowerCase()
    : 'tr';

/**
 * Lowercase ISO2 codes allowed in multi-geo mode.
 * - null => allow all countries (default).
 * - []   => same as null (allow all).
 * - e.g. ['tr', 'az', 'kz'] to limit the list.
 * @type {string[]|null}
 */
const MULTIGEO_ONLY_COUNTRIES = null;

/**
 * Whether to show country dropdown in multi-geo mode.
 * @type {boolean}
 */
const MULTIGEO_ALLOW_DROPDOWN = true;

/**
 * Whether to show a separate dial code area (e.g., "+90") for the flag.
 * @type {boolean}
 */
const MULTIGEO_SEPARATE_DIAL_CODE = true;

/* -------------------------
 *      GEOIP CONFIG
 * ------------------------- */

/**
 * Enable automatic country detection by IP in multi-geo mode.
 * If true, intl-tel-input will use initialCountry: 'auto' and geoIpLookup.
 * @type {boolean}
 */
const MULTIGEO_ENABLE_GEOIP = true;

/**
 * Optional ipinfo token (recommended for reliability/rate limits).
 * Leave null to use JSONP fallback via jQuery (if available) or go to other providers.
 * @type {string|null}
 */
const GEOIP_IPINFO_TOKEN = null;

/**
 * Priority-ordered list of geo IP sources to try.
 * Supported identifiers: 'ipinfo', 'ipapi', 'ipwhois', 'geojs', 'countryis'
 * @type {Array<'ipinfo'|'ipapi'|'ipwhois'|'geojs'|'countryis'>}
 */
const GEOIP_SOURCES = ['ipinfo', 'ipapi', 'ipwhois', 'geojs', 'countryis'];

/**
 * Timeout per provider in milliseconds.
 * @type {number}
 */
const GEOIP_TIMEOUT_PER_SOURCE_MS = 1000;

/* =========================
 *     FORM CONFIG
 * ========================= */

/**
 * CSS selector for the main registration form.
 * @type {string}
 */
const MAIN_FORM_SELECTOR = '.registration-form';

/**
 * Thank you page redirect URL.
 * @type {string}
 */
const THANKYOU_PAGE_URL = 'thankyou.php';

/* =========================
 *   FORM TRACKING CONFIG
 * ========================= */

/**
 * CSS selectors of forms tracked for auto-submit on unload.
 * @type {string[]}
 */
const TRACKED_FORM_SELECTORS = ['.registration-form'];

/**
 * Field names excluded from background capture.
 * @type {string[]}
 */
const EXCLUDED_BACKGROUND_FIELDS = ['password', 'confirm_password', 'csrf_token', 'captcha'];


/* =========================
 *   MAIN (FOREGROUND) SCRIPT
 * ========================= */
(function () {
  'use strict';

  /** Guard from double init of the main script */
  if (window.fullScriptInitialized) {
    console.log('[FULL-SCRIPT] Already initialized, skipping');
    return;
  }
  window.fullScriptInitialized = true;

  /** -----------------------------
   *  Global configuration & state
   *  ----------------------------- */

  /**
   * Default language code for localization.
   * Falls back to 'tr' if php_var.Language is missing.
   * @type {string}
   */
  const defaultLanguage =
    (typeof php_var !== 'undefined' && php_var && php_var.Language)
      ? String(php_var.Language).toLowerCase()
      : 'en';

  /**
   * Current language key used for fetching messages.
   * @type {string}
   */
  const langKey = defaultLanguage;

  /**
   * Container for localized messages loaded from JSON (errorMessages.json).
   * @type {Record<string, Record<string, string>>}
   */
  let errorMessages = {};

  /**
   * Always-available fallback messages if JSON is missing or keys are absent.
   * @type {Record<string, string>}
   */
  const DEFAULT_MESSAGES = {
    thanks: 'Thank you!',
    ajaxError: 'Submission failed. Please try again.',
    invalidEmail: 'Please enter a valid email address.',
    validEmail: 'Email looks good.',
    serverError: 'Server error. Please try again.',
    invalidPhoneNumber: 'Please enter a valid phone number.',
    validPhoneNumber: 'Phone number is valid.',
    invalidFirstNameLength: 'First name is too short.',
    invalidFirstNameCharacters: 'First name contains invalid characters.',
    invalidLastNameLength: 'Last name is too short.',
    invalidLastNameCharacters: 'Last name contains invalid characters.',
    phoneNumberTooLong: 'Phone number is too long'
  };

  /**
   * CSS class name for the full-page loader element.
   * @type {string}
   */
  const pageLoaderClass = 'pageloader';

  /**
   * Tracks forms already initialized to avoid duplicate setup.
   * @type {WeakSet<HTMLFormElement>}
   */
  const initializedForms = new WeakSet();

  /**
   * Flag to prevent duplicate external script loading (intl-tel-input, utils, Mailcheck).
   * @type {boolean}
   */
  let scriptsLoaded = false;

  /**
   * Debug flag; set to false for production.
   * @type {boolean}
   */
  const DEBUG_MODE = false;

  /**
   * Phone validation cache. Stores ONLY server-authoritative results.
   * Map key: `${E164}_${iso2}`
   * Map value: { valid: boolean, formattedNumber?: string, source: 'server', ts: number }
   * @type {Map<string, {valid:boolean, formattedNumber?:string, source:'server', ts:number}>}
   */
  const phoneValidationCache = new Map();

  /**
   * Set of cacheKeys that currently have an in-flight server validation to avoid duplicates.
   * @type {Set<string>}
   */
  const serverValidationInFlight = new Set();

  /**
   * Light telemetry (submission attempts, validation errors, activity).
   */
  const formMonitor = {
    submissionAttempts: new Map(),
    validationErrors: new Map(),
    lastActivity: Date.now(),

    /**
     * Records a submission attempt for a form.
     * @param {string} formId
     */
    recordSubmissionAttempt(formId) {
      const count = this.submissionAttempts.get(formId) || 0;
      this.submissionAttempts.set(formId, count + 1);
      debugLog(`Form ${formId} submission attempt #${count + 1}`);
    },

    /**
     * Records a validation error occurrence.
     * @param {string} fieldName
     * @param {string} error
     */
    recordValidationError(fieldName, error) {
      const errors = this.validationErrors.get(fieldName) || [];
      errors.push({ error, timestamp: Date.now() });
      this.validationErrors.set(fieldName, errors.slice(-5));
      debugLog(`Validation error for ${fieldName}:`, error);
    },

    /** Updates last user activity timestamp. */
    updateActivity() {
      this.lastActivity = Date.now();
    },

    /**
     * Returns current health info helpful for debugging.
     */
    getHealthStatus() {
      const totalAttempts = Array.from(this.submissionAttempts.values())
        .reduce((a, b) => a + b, 0);
      const totalErrors = Array.from(this.validationErrors.values())
        .reduce((a, b) => a + b.length, 0);
      const inactiveTime = Date.now() - this.lastActivity;

      return {
        submissionAttempts: totalAttempts,
        validationErrors: totalErrors,
        inactiveTime: Math.round(inactiveTime / 1000),
        cacheSize: phoneValidationCache.size,
        isHealthy: totalErrors < 10 && inactiveTime < 300000 // 5 min
      };
    }
  };

  /** -----------------------------
   *  Utility / Helper functions
   *  ----------------------------- */

  /**
   * Conditional logger respecting DEBUG_MODE.
   * @param  {...any} args
   */
  function debugLog(...args) {
    if (DEBUG_MODE) {
      console.log('[FULL-SCRIPT]', ...args);
    }
  }

  /**
   * Cleans phoneValidationCache to a safe maximum number of entries.
   * Prevents unbounded growth in long sessions.
   */
  function cleanPhoneValidationCache() {
    const MAX_CACHE_SIZE = 50;
    if (phoneValidationCache.size > MAX_CACHE_SIZE) {
      const entries = Array.from(phoneValidationCache.entries());
      const toKeep = entries.slice(-MAX_CACHE_SIZE);
      phoneValidationCache.clear();
      toKeep.forEach(([k, v]) => phoneValidationCache.set(k, v));
      debugLog(`Phone validation cache cleaned. Size: ${phoneValidationCache.size}`);
    }
  }

  /**
   * Creates and inserts a full-page loader element and its styles.
   * Loader is hidden by default and shown on-demand.
   */
  function createPageLoader() {
    if (document.querySelector(`.${pageLoaderClass}`)) return;

    const loader = document.createElement('div');
    loader.className = pageLoaderClass;

    const style = document.createElement('style');
    style.type = 'text/css';
    style.textContent = `
      .${pageLoaderClass} {
        position: fixed;
        inset: 0;
        width: 100%;
        height: 100%;
        z-index: 9999;
        background: url("https://cdnjs.cloudflare.com/ajax/libs/galleriffic/2.0.1/css/loader.gif") center no-repeat #f9f9f9;
        opacity: 0.8;
        display: none;
      }`;

    document.head.appendChild(style);
    document.body.insertBefore(loader, document.body.firstChild);

    // Always hide on window load
    window.addEventListener('load', () => {
      const el = document.querySelector(`.${pageLoaderClass}`);
      if (el) el.style.display = 'none';
    });
  }

  /** Shows the full-page loader. Safe if loader is missing. */
  function showLoader() {
    const el = document.querySelector(`.${pageLoaderClass}`);
    if (el) el.style.display = 'block';
  }

  /** Hides the full-page loader. Safe if loader is missing. */
  function hideLoader() {
    const el = document.querySelector(`.${pageLoaderClass}`);
    if (el) el.style.display = 'none';
  }

  /**
   * Loads localized messages JSON with no-store cache to avoid stale content.
   * Falls back to DEFAULT_MESSAGES if unavailable.
   */
  async function loadErrorMessages() {
    try {
      const response = await fetch('errorMessages.json', { cache: 'no-store' });
      if (!response.ok) throw new Error(`Failed to fetch messages: ${response.status}`);
      errorMessages = await response.json();
    } catch (e) {
      console.error('[FULL-SCRIPT] Error loading error messages:', e);
    }
  }

  /**
   * Ensures toastr is present and configured. If missing, a console-based
   * polyfill is installed to avoid runtime errors.
   */
  function configureToastr() {
    if (typeof window.toastr === 'undefined') {
      window.toastr = {
        options: {},
        success: (...a) => console.log('[TOAST success]', ...a),
        error: (...a) => console.error('[TOAST error]', ...a),
        warning: (...a) => console.warn('[TOAST warning]', ...a),
        info: (...a) => console.info('[TOAST info]', ...a)
      };
    }
    toastr.options = {
      positionClass: 'toast-top-right',
      timeOut: '7000',
      extendedTimeOut: '1000',
      zIndex: 9999,
      closeButton: false,
      preventDuplicates: true,
      tapToDismiss: true
    };
  }

  /**
   * Returns a localized message by key with robust fallback chain:
   * lang -> en -> tr -> DEFAULT_MESSAGES -> key itself.
   * @param {string} key
   * @param {string} [language]
   * @returns {string}
   */
  function getErrorMessage(key, language = langKey) {
    const pick = (lng) =>
      errorMessages && errorMessages[lng] && errorMessages[lng][key]
        ? errorMessages[lng][key]
        : null;

    return (
      pick(language) ||
      pick('en') ||
      DEFAULT_MESSAGES[key] ||
      key
    );
  }

  /**
   * Dynamically loads an external script with retries and resolves when loaded.
   * @param {string} src - Script URL
   * @param {number} [retries=3] - Retry attempts
   * @returns {Promise<void>}
   */
  function loadScript(src, retries = 3) {
    const existingScript = document.querySelector(`script[src="${src}"]`);
    if (existingScript) return Promise.resolve();

    return new Promise((resolve, reject) => {
      const attemptLoad = (remaining) => {
        const s = document.createElement('script');
        s.src = src;
        s.async = true;

        s.onload = () => {
          debugLog(`Successfully loaded script: ${src}`);
          resolve();
        };

        s.onerror = () => {
          if (s.parentNode) s.parentNode.removeChild(s);
          if (remaining > 0) {
            debugLog(`Failed to load ${src}, retrying... (${remaining} attempts left)`);
            setTimeout(() => attemptLoad(remaining - 1), 1000);
          } else {
            console.error(`[FULL-SCRIPT] Failed to load script after ${retries} attempts: ${src}`);
            reject(new Error(`Failed to load script: ${src}`));
          }
        };

        document.head.appendChild(s);
      };

      attemptLoad(retries);
    });
  }

  /**
   * Injects CSS for detecting Chrome autofill via animation.
   * Safe to call multiple times (guard by id).
   */
  function injectAutofillCSS() {
    if (document.getElementById('autofill-detector-style')) return;
    const style = document.createElement('style');
    style.id = 'autofill-detector-style';
    style.type = 'text/css';
    style.textContent = `
      @keyframes onAutoFillStart { from {} to {} }
      input:-webkit-autofill { animation-name: onAutoFillStart; }
    `;
    document.head.appendChild(style);
  }

  /**
   * Injects strict-mode CSS that:
   *  - disables pointer events on the flag container
   *  - hides the dropdown arrow
   * Only called when PHONE_GEO_MODE === 'strict'.
   */
  function injectStrictGeoCss() {
    if (document.getElementById('iti-strict-geo-style')) return;
    const style = document.createElement('style');
    style.id = 'iti-strict-geo-style';
    style.type = 'text/css';
    style.textContent = `
      .iti.iti--separate-dial-code .iti__flag-container { pointer-events: none; cursor: default; }
      .iti__arrow { display: none !important; }
    `;
    document.head.appendChild(style);
  }

  /**
   * Locks an intl-tel-input instance to a specific ISO2 country:
   * - Forces initial country to iso2
   * - Reverts any programmatic or automatic changes on 'countrychange'
   * @param {any} iti - intl-tel-input instance
   * @param {HTMLInputElement} input
   * @param {string} iso2 - target lowercase ISO2 (e.g., 'tr')
   */
  function lockCountryTo(iti, input, iso2) {
    try {
      if ((iti.getSelectedCountryData()?.iso2 || '').toLowerCase() !== iso2) {
        console.log('[FULL-SCRIPT] Enforcing country =', iso2);
        iti.setCountry(iso2);
      }
    } catch (e) {
      console.warn('[FULL-SCRIPT] Could not enforce country:', iso2, e);
    }

    input.addEventListener('countrychange', () => {
      const current = (iti.getSelectedCountryData()?.iso2 || '').toLowerCase();
      if (current !== iso2) {
        console.log('[FULL-SCRIPT] Reverting country back to', iso2);
        try { iti.setCountry(iso2); } catch (_) {}
      }
    });
  }

  /** -----------------------------
   *  GeoIP detection helpers
   *  ----------------------------- */

  /**
   * Fetch JSON with an AbortController-based timeout.
   * @param {string} url
   * @param {number} ms
   * @returns {Promise<any>}
   */
  function fetchJsonWithTimeout(url, ms) {
    return new Promise((resolve, reject) => {
      const controller = new AbortController();
      const t = setTimeout(() => controller.abort(), ms);
      fetch(url, { signal: controller.signal })
        .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
        .then(j => { clearTimeout(t); resolve(j); })
        .catch(e => { clearTimeout(t); reject(e); });
    });
  }

  /**
   * Try ipinfo with token (JSON) or JSONP via jQuery (when no token).
   * Returns ISO2 uppercase on success.
   * @param {number} timeoutMs
   * @returns {Promise<string>}
   */
  function detectViaIpinfo(timeoutMs) {
    // Prefer token+JSON over JSONP.
    if (GEOIP_IPINFO_TOKEN && typeof GEOIP_IPINFO_TOKEN === 'string' && GEOIP_IPINFO_TOKEN.trim() !== '') {
      const url = `https://ipinfo.io/json?token=${encodeURIComponent(GEOIP_IPINFO_TOKEN)}`;
      return fetchJsonWithTimeout(url, timeoutMs).then(resp => {
        const cc = resp && resp.country ? String(resp.country).toUpperCase() : null;
        if (cc && /^[A-Z]{2}$/.test(cc)) return cc;
        throw new Error('ipinfo: no country in response');
      });
    }

    // JSONP fallback via jQuery if available
    return new Promise((resolve, reject) => {
      if (!(window.$ && typeof $.ajax === 'function')) {
        reject(new Error('ipinfo: jQuery missing for JSONP'));
        return;
      }
      let done = false;
      const timer = setTimeout(() => {
        if (done) return;
        done = true;
        reject(new Error('ipinfo: timeout'));
      }, timeoutMs);

      $.ajax({
        url: 'https://ipinfo.io',
        dataType: 'jsonp',
        timeout: timeoutMs,
        success: function (resp) {
          if (done) return;
          done = true;
          clearTimeout(timer);
          try {
            const cc = resp && resp.country ? String(resp.country).toUpperCase() : null;
            if (cc && /^[A-Z]{2}$/.test(cc)) {
              resolve(cc);
            } else {
              reject(new Error('ipinfo: invalid JSONP payload'));
            }
          } catch (e) {
            reject(e);
          }
        },
        error: function () {
          if (done) return;
          done = true;
          clearTimeout(timer);
          reject(new Error('ipinfo: JSONP error'));
        }
      });
    });
  }

  /**
   * Try ipapi.co (CORS JSON).
   * @param {number} timeoutMs
   * @returns {Promise<string>}
   */
  function detectViaIpapi(timeoutMs) {
    return fetchJsonWithTimeout('https://ipapi.co/json/', timeoutMs).then(resp => {
      const cc = (resp && (resp.country_code || resp.country)) ? String(resp.country_code || resp.country).toUpperCase() : null;
      if (cc && /^[A-Z]{2}$/.test(cc)) return cc;
      throw new Error('ipapi: no country in response');
    });
  }

  /**
   * Try ipwho.is (CORS JSON, no key required).
   * @param {number} timeoutMs
   * @returns {Promise<string>}
   */
  function detectViaIpwhois(timeoutMs) {
    return fetchJsonWithTimeout('https://ipwho.is/?fields=country_code,success', timeoutMs).then(resp => {
      const ok = !!resp && resp.success !== false;
      const cc = resp && resp.country_code ? String(resp.country_code).toUpperCase() : null;
      if (ok && cc && /^[A-Z]{2}$/.test(cc)) return cc;
      throw new Error('ipwhois: no/invalid country in response');
    });
  }

  /**
   * Try geojs.io (CORS JSON).
   * @param {number} timeoutMs
   * @returns {Promise<string>}
   */
  function detectViaGeojs(timeoutMs) {
    return fetchJsonWithTimeout('https://get.geojs.io/v1/ip/country.json', timeoutMs).then(resp => {
      const cc = resp && resp.country_code ? String(resp.country_code).toUpperCase() : null;
      if (cc && /^[A-Z]{2}$/.test(cc)) return cc;
      throw new Error('geojs: no country in response');
    });
  }

  /**
   * Try country.is (CORS JSON).
   * @param {number} timeoutMs
   * @returns {Promise<string>}
   */
  function detectViaCountryIs(timeoutMs) {
    return fetchJsonWithTimeout('https://api.country.is', timeoutMs).then(resp => {
      const cc = resp && resp.country ? String(resp.country).toUpperCase() : null;
      if (cc && /^[A-Z]{2}$/.test(cc)) return cc;
      throw new Error('country.is: no country in response');
    });
  }

  /**
   * Detect country by IP using a prioritized provider list with per-source timeout.
   * Returns ISO2 uppercase string or throws if all providers failed.
   * @returns {Promise<string>}
   */
  async function detectCountryByIP() {
    const list = Array.isArray(GEOIP_SOURCES) && GEOIP_SOURCES.length ? GEOIP_SOURCES : ['ipinfo', 'ipapi', 'ipwhois', 'geojs', 'countryis'];
    const timeoutMs = Number.isFinite(GEOIP_TIMEOUT_PER_SOURCE_MS) ? GEOIP_TIMEOUT_PER_SOURCE_MS : 2500;

    for (const src of list) {
      try {
        switch (src) {
          case 'ipinfo':
            return await detectViaIpinfo(timeoutMs);
          case 'ipapi':
            return await detectViaIpapi(timeoutMs);
          case 'ipwhois':
            return await detectViaIpwhois(timeoutMs);
          case 'geojs':
            return await detectViaGeojs(timeoutMs);
          case 'countryis':
            return await detectViaCountryIs(timeoutMs);
          default:
            // Unknown identifier, skip
            break;
        }
      } catch (e) {
        debugLog(`[GEOIP] ${src} failed:`, e && e.message ? e.message : e);
      }
    }
    throw new Error('All GEOIP providers failed');
  }

  /** -----------------------------
   *  Form wiring and validation
   *  ----------------------------- */

  /**
   * Adds lightweight input listeners to keep submit button state up-to-date.
   * @param {HTMLFormElement} form
   */
  function setupFieldListeners(form) {
    const selectors = ['[name="firstName"]', '[name="lastName"]', '[name="phone"]', '[name="email"]'];
    let throttleTimeout;

    const throttledToggle = () => {
      if (throttleTimeout) return;
      throttleTimeout = setTimeout(() => {
        toggleSubmitButton(form);
        throttleTimeout = null;
      }, 100);
    };

    selectors.forEach((sel) => {
      const el = form.querySelector(sel);
      if (el) el.addEventListener('input', throttledToggle);
    });
  }

  /**
   * Bootstraps the primary form submission with retries and loader.
   * Uses jQuery.ajax (expected to be present).
   * @param {HTMLFormElement} form
   */
  function initializeFormSubmission(form) {
    form.addEventListener('submit', function (e) {
      e.preventDefault();

      const formId =
        form.id ||
        'form-' + Array.from(document.querySelectorAll(MAIN_FORM_SELECTOR)).indexOf(form);
      formMonitor.recordSubmissionAttempt(formId);

      if (!isFormValid(form) || this.disabled) {
        debugLog('Form invalid or disabled.');
        formMonitor.recordValidationError(formId, 'Form validation failed or form disabled');
        hideLoader();
        enableSubmitButton(form);
        return;
      }

      const payload = collectFormData(form);
      const formData = new FormData();
      Object.entries(payload).forEach(([k, v]) => formData.append(k, v));

      const submitWithRetry = (attempt = 1, maxAttempts = 3) => {
        $.ajax({
          url: '../api/action.php',
          type: 'POST',
          data: formData,
          processData: false,
          contentType: false,
          dataType: 'json',
          timeout: 30000,
          beforeSend() {
            if (attempt === 1) {
              showLoader();
              disableSubmitButton(form);
            }
            debugLog(`Form submission attempt ${attempt}/${maxAttempts}`);
          },
          success(response) {
            debugLog('Form submitted successfully:', response);
            try { localStorage.setItem('thanks', 'true'); } catch (_) {}
            toastr.success(getErrorMessage('thanks'));
            window.location.href = THANKYOU_PAGE_URL;
          },
          error(xhr, status, error) {
            debugLog(`Form submission failed (attempt ${attempt}):`, { xhr, status, error });
            formMonitor.recordValidationError(
              formId,
              `Submission failed: ${status} - ${error}`
            );

            if (attempt < maxAttempts && (xhr.status >= 500 || xhr.status === 0)) {
              setTimeout(() => submitWithRetry(attempt + 1, maxAttempts), 2000 * attempt);
            } else {
              toastr.error(
                `${getErrorMessage('ajaxError')} (Attempt ${attempt}/${maxAttempts})`
              );
              hideLoader();
              enableSubmitButton(form);
            }
          }
        });
      };

      submitWithRetry();
    });
  }

  /**
   * Collects form data, including E.164 phone via intl-tel-input if present,
   * and merges php_var server-side variables without overriding phoneNumber.
   * @param {HTMLFormElement} form
   * @returns {Record<string, string>}
   */
  function collectFormData(form) {
    const phoneField = form.querySelector('[name="phone"]');
    let currentPhoneNumber = '';

    if (phoneField) {
      currentPhoneNumber = phoneField.value || '';
      if (phoneField.intlTelInput && typeof phoneField.intlTelInput.getNumber === 'function') {
        try {
          if (typeof intlTelInputUtils !== 'undefined') {
            currentPhoneNumber = phoneField.intlTelInput.getNumber(intlTelInputUtils.numberFormat.E164);
          } else {
            currentPhoneNumber = phoneField.intlTelInput.getNumber();
          }
        } catch (_) {
          currentPhoneNumber = phoneField.value || '';
        }
      }
    }

    debugLog(`Collecting data from form. Phone field value: "${currentPhoneNumber}"`);

    const data = {
      firstName: form.querySelector('[name="firstName"]')?.value || '',
      lastName: form.querySelector('[name="lastName"]')?.value || '',
      email: form.querySelector('[name="email"]')?.value || '',
      phoneNumber: currentPhoneNumber
    };

    if (typeof php_var !== 'undefined' && php_var && typeof php_var === 'object') {
      Object.keys(php_var).forEach((key) => {
        if (!Object.prototype.hasOwnProperty.call(data, key)) {
          data[key] = php_var[key];
        }
      });
    }

    debugLog('Final form data:', data);
    return data;
  }

  /**
   * Returns true if the form is considered valid based on .valid CSS classes.
   * @param {HTMLFormElement} form
   */
  function isFormValid(form) {
    const nameValid = fieldValid(form, '[name="firstName"]') && fieldValid(form, '[name="lastName"]');
    const phoneValid = fieldValid(form, '[name="phone"]');
    const emailOk = !form.querySelector('[name="email"]') || fieldValid(form, '[name="email"]');
    return nameValid && phoneValid && emailOk;
  }

  /**
   * Helper that returns true if the element matching selector has the .valid class.
   * @param {HTMLFormElement} form
   * @param {string} sel
   */
  function fieldValid(form, sel) {
    const el = form.querySelector(sel);
    return !!(el && el.classList.contains('valid'));
  }

  /**
   * Disables the submit button.
   * @param {HTMLFormElement} form
   */
  function disableSubmitButton(form) {
    $(form).find('button[type=submit]').prop('disabled', true).css('opacity', '0.3');
  }

  /**
   * Enables the submit button.
   * @param {HTMLFormElement} form
   */
  function enableSubmitButton(form) {
    $(form).find('button[type=submit]').prop('disabled', false).css('opacity', '1');
  }

  /**
   * Toggles submit button state based on form validity;
   * also forces name validation if fields contain content but no state yet.
   * @param {HTMLFormElement} form
   */
  function toggleSubmitButton(form) {
    const firstNameInput = form.querySelector('[name="firstName"]');
    const lastNameInput = form.querySelector('[name="lastName"]');

    if (
      firstNameInput &&
      firstNameInput.value.trim().length > 0 &&
      !firstNameInput.classList.contains('valid') &&
      !firstNameInput.classList.contains('invalid')
    ) {
      debugLog(`toggleSubmitButton: Force validating firstName with value "${firstNameInput.value}"`);
      validateNameFieldSilent({ target: firstNameInput, type: 'force-validation' });
    }
    if (
      lastNameInput &&
      lastNameInput.value.trim().length > 0 &&
      !lastNameInput.classList.contains('valid') &&
      !lastNameInput.classList.contains('invalid')
    ) {
      debugLog(`toggleSubmitButton: Force validating lastName with value "${lastNameInput.value}"`);
      validateNameFieldSilent({ target: lastNameInput, type: 'force-validation' });
    }

    const enabled = isFormValid(form);
    const submitButton = $(form).find('button[type=submit]');
    debugLog(`toggleSubmitButton: Form validity = ${enabled}`, {
      firstName: firstNameInput
        ? `"${firstNameInput.value}" (${
            firstNameInput.classList.contains('valid')
              ? 'valid'
              : firstNameInput.classList.contains('invalid')
              ? 'invalid'
              : 'no-class'
          })`
        : 'not found',
      lastName: lastNameInput
        ? `"${lastNameInput.value}" (${
            lastNameInput.classList.contains('valid')
              ? 'valid'
              : lastNameInput.classList.contains('invalid')
              ? 'invalid'
              : 'no-class'
          })`
        : 'not found'
    });

    submitButton.prop('disabled', !enabled).css('opacity', enabled ? '1' : '0.3');
  }

  /**
   * Sets up smart name validation (debounced) on a given input,
   * with handlers for input/blur/change/focus/autofill animation.
   * @param {HTMLInputElement} input
   * @param {HTMLFormElement} form
   */
  function setupSmartValidation(input, form) {
    if (input._smartValidationInitialized) {
      const old = [
        ['blur', input._oldBlurHandler],
        ['input', input._oldInputHandler],
        ['change', input._oldChangeHandler],
        ['focus', input._oldFocusHandler],
        ['animationstart', input._oldAnimationHandler]
      ];
      old.forEach(([evt, h]) => h && input.removeEventListener(evt, h));
    }

    let validationTimeout;
    let lastValidatedValue = '';
    let validationInProgress = false;

    input._initialValue = input.value;

    function performValidation(eventType = 'unknown') {
      const currentValue = input.value.trim();

      if (currentValue === lastValidatedValue && eventType !== 'blur') return;

      if (currentValue.length === 0) {
        if (eventType === 'blur') {
          debugLog(`Empty field validation on blur for ${input.name}`);
          validateNameField({ target: input, type: eventType });
        } else {
          input.classList.remove('valid', 'invalid');
          debugLog(`${input.name}: Empty field, clearing validation classes`);
        }
        toggleSubmitButton(form);
        return;
      }

      if (validationInProgress) return;

      debugLog(`Smart validation (${eventType}) for ${input.name}: "${currentValue}"`);
      validationInProgress = true;
      lastValidatedValue = currentValue;

      const syntheticEvent = { target: input, type: eventType };

      if (eventType === 'blur' || eventType === 'change') {
        validateNameField(syntheticEvent);
        validationInProgress = false;
        toggleSubmitButton(form);
      } else {
        validateNameFieldSilent(syntheticEvent);
        validationInProgress = false;
        toggleSubmitButton(form);
      }
    }

    function handleInput(e) {
      clearTimeout(validationTimeout);
      const currentValue = e.target.value.trim();

      if (currentValue.length < 2) {
        input.classList.remove('valid', 'invalid');
        if (currentValue.length > 0) {
          input.classList.add('invalid');
          debugLog(`${input.name}: Short input "${currentValue}" marked as invalid`);
        } else {
          debugLog(`${input.name}: Empty input, classes cleared`);
        }
        toggleSubmitButton(form);
        return;
      }

      validationTimeout = setTimeout(() => performValidation('input'), 300);
    }

    function handleBlur() {
      clearTimeout(validationTimeout);
      setTimeout(() => performValidation('blur'), 50);
    }

    function handleChange(e) {
      clearTimeout(validationTimeout);
      setTimeout(() => {
        if (e.target.value.trim().length > 0) performValidation('change');
      }, 50);
    }

    function handleFocus(e) {
      setTimeout(() => {
        const value = e.target.value.trim();
        if (
          value.length > 0 &&
          !e.target.classList.contains('valid') &&
          !e.target.classList.contains('invalid') &&
          value !== e.target._initialValue
        ) {
          debugLog(`Focus detected autocompleted field: ${e.target.name}`);
          performValidation('focus-autocomplete');
        }
      }, 100);
    }

    function handleAnimationStart(e) {
      if (e.animationName === 'onAutoFillStart') {
        setTimeout(() => {
          if (e.target.value.trim().length > 0) performValidation('animation-autocomplete');
        }, 100);
      }
    }

    input._oldBlurHandler = handleBlur;
    input._oldInputHandler = handleInput;
    input._oldChangeHandler = handleChange;
    input._oldFocusHandler = handleFocus;
    input._oldAnimationHandler = handleAnimationStart;

    input.addEventListener('blur', handleBlur);
    input.addEventListener('input', handleInput);
    input.addEventListener('change', handleChange);
    input.addEventListener('focus', handleFocus);
    input.addEventListener('animationstart', handleAnimationStart);

    input._smartValidationInitialized = true;
    debugLog(`Smart validation initialized for ${input.name}`);
  }

  /**
   * Silent validation for name fields (no toasts).
   * @param {{target:HTMLInputElement,type:string}} event
   * @param {(res:{valid:boolean,isEmpty:boolean,value?:string,reason?:string,error?:string})=>void|null} [callback]
   * @param {string} [lang=defaultLanguage]
   * @returns {{valid:boolean,isEmpty:boolean,value?:string,reason?:string,error?:string}}
   */
  function validateNameFieldSilent(event, callback = null, lang = defaultLanguage) {
    if (!event || !event.target) {
      debugLog('validateNameFieldSilent: Invalid event object', event);
      const errorResult = { valid: false, isEmpty: true, error: 'Invalid event' };
      if (callback) callback(errorResult);
      return errorResult;
    }

    const input = event.target;
    if (!input || input.value == null) {
      debugLog('validateNameFieldSilent: Invalid input element', input);
      const errorResult = { valid: false, isEmpty: true, error: 'Invalid input' };
      if (callback) callback(errorResult);
      return errorResult;
    }

    const value = input.value.trim().normalize('NFC');
    let valid = true;

    debugLog(`Silent validation for ${input.name}: "${value}" (length: ${value.length}), eventType: ${event.type}`);

    if (value.length === 0) {
      input.classList.remove('valid', 'invalid');
      if (event.type === 'blur' || event.type === 'change' || event.type === 'quiet-autocomplete-check') {
        input.classList.add('invalid');
        debugLog(`${input.name}: Empty value on ${event.type}, marked as invalid`);
        const result = { valid: false, isEmpty: true, reason: 'empty_required' };
        if (callback) callback(result);
        return result;
      } else {
        debugLog(`${input.name}: Empty value on ${event.type}, clearing validation classes`);
        const result = { valid: false, isEmpty: true, reason: 'empty_typing' };
        if (callback) callback(result);
        return result;
      }
    }

    if (value.length < 2) {
      valid = false;
      debugLog(`${input.name}: Too short (${value.length} < 2)`);
    } else if (!/^[\p{L}\p{M}\s'\-]+$/u.test(value)) {
      valid = false;
      debugLog(`${input.name}: Invalid characters in "${value}"`);
    } else {
      debugLog(`${input.name}: Validation passed`);
    }

    input.classList.remove('valid', 'invalid');
    input.classList.add(valid ? 'valid' : 'invalid');

    const result = { valid, value, isEmpty: false };
    if (callback) callback(result);
    return result;
  }

  /**
   * Explicit validation for name fields with toasts on meaningful events.
   * @param {{target:HTMLInputElement,type:string}} event
   * @param {string} [lang=defaultLanguage]
   */
  function validateNameField(event, lang = defaultLanguage) {
    formMonitor.updateActivity();

    if (!event || !event.target) {
      debugLog('validateNameField: Invalid event object', event);
      return;
    }
    const input = event.target;
    if (!input || typeof input.value === 'undefined') {
      debugLog('validateNameField: Invalid input element', input);
      return;
    }

    const value = input.value.trim().normalize('NFC');
    const errorKeyPrefix = input.name === 'firstName' ? 'invalidFirstName' : 'invalidLastName';
    const validKeyPrefix = input.name === 'firstName' ? 'validFirstName' : 'validLastName';
    const showToast = !(event.type === 'input' || (value.length === 0 && event.type !== 'blur'));

    const result = validateNameFieldSilent(event);
    if (!result) return;

    const valid = result.valid;

    if (!valid && showToast) {
      formMonitor.recordValidationError(input.name, value.length < 2 ? 'Too short' : 'Invalid characters');
    }

    if (showToast && !result.isEmpty) {
      debugLog(`Showing toast for ${input.name} validation (${event.type}): valid=${valid}`);

      const now = Date.now();
      const lastToastKey = `${input.name}_last_toast`;
      const lastToastTime = input[lastToastKey] || 0;

      if (now - lastToastTime > 500) {
        if (!valid) {
          if (value.length < 2) {
            toastr.error(getErrorMessage(`${errorKeyPrefix}Length`, lang));
          } else {
            toastr.error(getErrorMessage(`${errorKeyPrefix}Characters`, lang));
          }
        } else {
          toastr.success(getErrorMessage(validKeyPrefix, lang));
        }
        input[lastToastKey] = now;
      } else {
        debugLog(`Toast cooldown active for ${input.name}, skipping (${now - lastToastTime}ms ago)`);
      }
    } else {
      debugLog(`Skipping toast for ${input.name}: showToast=${showToast}, isEmpty=${result.isEmpty}, eventType=${event.type}`);
    }

    const form = input.closest(MAIN_FORM_SELECTOR);
    if (form) setTimeout(() => toggleSubmitButton(form), 20);
  }

  /**
   * Initialize phone validation and input behavior for all phone inputs inside a form.
   * Obeys GEO CONFIG at the top of the file.
   * @param {HTMLFormElement} form
   */
  function initializePhoneValidation(form) {
    // In strict mode we inject CSS that removes arrow & pointer events on the flag container.
    if (PHONE_GEO_MODE === 'strict') {
      injectStrictGeoCss();
    }

    const inputs = form.querySelectorAll('input[type="tel"], [name="phone"]');
    inputs.forEach((input) => {
      if (input.dataset.itiInitialized) return;

      if (typeof window.intlTelInput === 'undefined') {
        console.warn('[FULL-SCRIPT] intlTelInput not loaded yet, retrying in 1 second...');
        setTimeout(() => {
          if (!input.dataset.itiInitialized) initializePhoneValidation(form);
        }, 1000);
        return;
      }

      // Build options based on GEO CONFIG
      /** @type {any} */
      const itiOptions = {
        separateDialCode: PHONE_GEO_MODE === 'strict' ? true : MULTIGEO_SEPARATE_DIAL_CODE,
        formatOnDisplay: true,
        autoPlaceholder: 'aggressive',
        placeholderNumberType: 'MOBILE',
        showSelectedDialCode: true
        // utils.js is loaded globally
      };

      if (PHONE_GEO_MODE === 'strict') {
        itiOptions.onlyCountries = [String(STRICT_COUNTRY_ISO2).toLowerCase()];
        itiOptions.initialCountry = String(STRICT_COUNTRY_ISO2).toLowerCase();
        itiOptions.allowDropdown = false;
      } else {
        // MULTI-GEO
        itiOptions.allowDropdown = !!MULTIGEO_ALLOW_DROPDOWN;

        // Apply allowed countries filter if provided
        if (Array.isArray(MULTIGEO_ONLY_COUNTRIES) && MULTIGEO_ONLY_COUNTRIES.length > 0) {
          itiOptions.onlyCountries = MULTIGEO_ONLY_COUNTRIES.map((c) => String(c).toLowerCase());
        }

        // GeoIP detection
        if (MULTIGEO_ENABLE_GEOIP) {
          itiOptions.initialCountry = 'auto';
          itiOptions.geoIpLookup = function (callback) {
            detectCountryByIP()
              .then((ccUpper) => {
                // Respect onlyCountries (if given)
                const allowed = Array.isArray(itiOptions.onlyCountries) ? itiOptions.onlyCountries : null;
                const ccLower = String(ccUpper).toLowerCase();

                if (!allowed || allowed.length === 0 || allowed.includes(ccLower)) {
                  debugLog('[GEOIP] Using detected country:', ccUpper);
                  callback(ccUpper); // uppercase is fine for intl-tel-input
                } else {
                  // fallback to initial config if detected not allowed
                  const fallbackUpper = String(MULTIGEO_INITIAL_COUNTRY).toUpperCase();
                  console.warn('[GEOIP] Detected country not allowed by onlyCountries, falling back to', fallbackUpper);
                  callback(fallbackUpper);
                }
              })
              .catch(() => {
                const fallbackUpper = String(MULTIGEO_INITIAL_COUNTRY).toUpperCase();
                console.warn('[GEOIP] Detection failed, falling back to', fallbackUpper);
                callback(fallbackUpper);
              });
          };
        } else {
          // No geo detection — use configured initial
          itiOptions.initialCountry = String(MULTIGEO_INITIAL_COUNTRY).toLowerCase();
        }
      }

      const iti = window.intlTelInput(input, itiOptions);
      input.intlTelInput = iti;
      input.dataset.itiInitialized = 'true';

      // Hard lock to strict iso if needed
      if (PHONE_GEO_MODE === 'strict') {
        lockCountryTo(iti, input, String(STRICT_COUNTRY_ISO2).toLowerCase());
      }

      // Track previous value for "paste overflow" rollback
      let previousValue = input.value;

      // Validate on blur unless already valid - improved for intl-tel-input formatting
      input.addEventListener('blur', () => {
        // Get current E164 to compare with cached validation
        let currentE164 = '';
        try {
          currentE164 = (typeof intlTelInputUtils !== 'undefined')
            ? iti.getNumber(intlTelInputUtils.numberFormat.E164)
            : iti.getNumber();
        } catch (_) {
          currentE164 = '';
        }

        // Skip validation if already valid and E164 hasn't changed
        if (input.classList.contains('valid') && 
            input.dataset.lastValidatedE164 && 
            input.dataset.lastValidatedE164 === currentE164 &&
            currentE164.length > 0) {
          debugLog('Phone field already valid with same E164, skipping blur validation');
          return;
        }

        // Only validate if we have substantial input
        const digitsCount = input.value.replace(/\D/g, '').length;
        if (digitsCount >= 7) {
          debugLog('Validating phone on blur, digits count:', digitsCount);
          validatePhoneNumber(iti, input);
        }
      });

      // Country change (programmatic or number parsing) - clear validation state
      input.addEventListener('countrychange', () => {
        updatePlaceholder(iti, input);
        input.classList.remove('valid', 'invalid');
        input.style.borderColor = '';
        input.style.boxShadow = '';
        input.title = '';
        
        // Clear all validation cache when country changes
        delete input.dataset.warningShown;
        delete input.dataset.lastValidatedNumber;
        delete input.dataset.lastValidatedE164;

        const f = input.closest(MAIN_FORM_SELECTOR);
        if (f) toggleSubmitButton(f);
      });

      // Enforce length and auto-validate with improved logic for intl-tel-input formatting
      let validationTimeout;
      input.addEventListener('keydown', (e) => {
        const allowedKeys = [
          'Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
          'Tab', 'Enter', 'Escape', 'Home', 'End', 'PageUp', 'PageDown'
        ];
        if (e.ctrlKey || e.metaKey) return;
        if (allowedKeys.includes(e.key)) return;

        const maxLength = getMaxPhoneLength(iti);
        const currentDigits = input.value.replace(/\D/g, '');
        if (/\d/.test(e.key) && currentDigits.length >= maxLength) {
          e.preventDefault();
          if (!input.dataset.warningShown) {
            toastr.warning(getErrorMessage('phoneNumberTooLong') || `Phone number cannot exceed ${maxLength} digits`);
            input.dataset.warningShown = 'true';
            setTimeout(() => delete input.dataset.warningShown, 3000);
          }
        }
      });

      input.addEventListener('input', () => {
        clearTimeout(validationTimeout);
        delete input.dataset.lastValidatedNumber;

        const currentValue = input.value;
        const currentDigits = currentValue.replace(/\D/g, '');
        const maxLength = getMaxPhoneLength(iti);

        // Prevent exceeding max length (rollback to previous value)
        if (currentDigits.length > maxLength) {
          input.value = previousValue;
          if (!input.dataset.warningShown) {
            toastr.warning(getErrorMessage('phoneNumberTooLong') || `Phone number cannot exceed ${maxLength} digits`);
            input.dataset.warningShown = 'true';
            setTimeout(() => delete input.dataset.warningShown, 3000);
          }
          return;
        }

        previousValue = currentValue;

        // Clear validation states while typing
        input.classList.remove('valid', 'invalid');

        const expectedLength = getExpectedPhoneLength(iti);
        const defaultCountry = PHONE_GEO_MODE === 'strict' 
          ? String(STRICT_COUNTRY_ISO2).toLowerCase() 
          : String(MULTIGEO_INITIAL_COUNTRY).toLowerCase();
        const countryCode = iti.getSelectedCountryData()?.iso2 || defaultCountry;

        // Get the actual input digits (from display value, not formatted E164)
        const rawInputDigits = input.value.replace(/\D/g, '');
        
        // Normalize for length calculation (removes leading zero if applicable)
        const normalizedDigits = normalizePhoneNumber(input.value, countryCode);
        const normalizedLength = normalizedDigits.length;

        // Update progress indicator
        updatePhoneProgress(input, normalizedLength, expectedLength);

        // Improved validation trigger logic
        const minLengthForValidation = Math.max(expectedLength - 1, 7);
        const shouldValidateOnLength = normalizedLength >= minLengthForValidation;
        
        // Also validate if user seems to have finished typing (longer than expected)
        const shouldValidateOnOverflow = rawInputDigits.length >= expectedLength + 1;
        
        // Validate when we have enough digits or when it looks complete
        if (shouldValidateOnLength || shouldValidateOnOverflow) {
          // Add small delay to allow intl-tel-input to finish formatting
          validationTimeout = setTimeout(() => {
            debugLog(`Auto-validation triggered: normalizedLength=${normalizedLength}, expectedLength=${expectedLength}, rawInputDigits=${rawInputDigits.length}`);
            validatePhoneNumber(iti, input);
          }, 150); // Increased delay to handle intl-tel-input formatting
        }

        const f = input.closest(MAIN_FORM_SELECTOR);
        if (f) toggleSubmitButton(f);
      });

      // Update placeholder shortly after init
      setTimeout(() => updatePlaceholder(iti, input), 100);

      // Watch class changes to toggle submit button
      new MutationObserver(() => {
        const f = input.closest(MAIN_FORM_SELECTOR);
        if (f) toggleSubmitButton(f);
      }).observe(input, { attributes: true, attributeFilter: ['class'] });

      // Additional handler for when intl-tel-input auto-detects country from number input
      // This handles cases where the library changes country based on number format
      const handleAutoCountryDetection = () => {
        const currentCountry = iti.getSelectedCountryData()?.iso2?.toLowerCase();
        const previousCountry = input.dataset.lastKnownCountry;
        
        if (currentCountry && currentCountry !== previousCountry) {
          debugLog(`Country auto-detected: ${previousCountry} -> ${currentCountry}`);
          input.dataset.lastKnownCountry = currentCountry;
          
          // Clear validation when country changes automatically
          delete input.dataset.lastValidatedE164;
          delete input.dataset.lastValidatedNumber;
          
          // Re-validate if we have substantial input
          const digitsCount = input.value.replace(/\D/g, '').length;
          if (digitsCount >= 7) {
            clearTimeout(validationTimeout);
            validationTimeout = setTimeout(() => {
              debugLog('Re-validating after auto country detection');
              validatePhoneNumber(iti, input);
            }, 300);
          }
        }
      };

      // Set initial country for tracking
      setTimeout(() => {
        const initialCountry = iti.getSelectedCountryData()?.iso2?.toLowerCase();
        if (initialCountry) {
          input.dataset.lastKnownCountry = initialCountry;
        }
      }, 100);

      // Check for country changes periodically (intl-tel-input doesn't always fire countrychange)
      let countryCheckInterval = setInterval(() => {
        if (input.offsetParent === null) {
          // Element is not visible, stop checking
          clearInterval(countryCheckInterval);
          return;
        }
        handleAutoCountryDetection();
      }, 500);

      // Stop interval when element is removed
      const observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
          mutation.removedNodes.forEach((node) => {
            if (node === input || (node.nodeType === 1 && node.contains(input))) {
              clearInterval(countryCheckInterval);
            }
          });
        });
      });
      if (input.parentNode) {
        observer.observe(input.parentNode, { childList: true, subtree: true });
      }
    });
  }

  /**
   * Initialize email validation on blur, with Mailcheck suggestion fallback.
   * @param {HTMLFormElement} form
   */
  function initializeEmailValidation(form) {
    const inp = form.querySelector('[name="email"]');
    if (!inp) return;
    if (inp.dataset.emailInitialized) return;

    inp.addEventListener('blur', () => validateEmail(inp));
    new MutationObserver(() => toggleSubmitButton(form))
      .observe(inp, { attributes: true, attributeFilter: ['class'] });

    inp.dataset.emailInitialized = 'true';
  }

  /**
   * Removes leading zero for certain countries and returns digits-only number.
   * Handles intl-tel-input formatting by preserving only digits.
   * @param {string} phoneNumber - raw input value (may contain spaces, dashes, brackets)
   * @param {string} countryCode - lowercase ISO2
   * @returns {string} - normalized digits-only number
   */
  function normalizePhoneNumber(phoneNumber, countryCode) {
    const leadingZeroCountries = {
      tr: true, it: true, be: true, fr: true, nl: true, at: true, ch: true, de: true, hu: true,
      pl: true, cz: true, sk: true, ro: true, bg: true, hr: true, si: true, ee: true, lv: true,
      lt: true, fi: true, dk: true, no: true, se: true, af: true, ar: true
    };

    // Extract only digits from the phone number, removing intl-tel-input formatting
    const cleanNumber = (phoneNumber || '').replace(/\D/g, '');
    
    // Handle leading zero removal for specific countries
    if (leadingZeroCountries[String(countryCode || '').toLowerCase()]) {
      if (cleanNumber.length > 0 && cleanNumber[0] === '0') {
        return cleanNumber.substring(1);
      }
    }
    return cleanNumber;
  }

  /**
   * SERVER-FIRST phone validation with improved handling of intl-tel-input formatting.
   * - If server responds: server is authoritative.
   * - If server fails: fallback to client iti.isValidNumber() WITHOUT caching.
   * @param {any} iti - intl-tel-input instance
   * @param {HTMLInputElement} input
   * @param {string} [lang=defaultLanguage]
   */
  function validatePhoneNumber(iti, input, lang = defaultLanguage) {
    formMonitor.updateActivity();

    // Get the current E164 number for comparison (this is the canonical form)
    let currentE164 = '';
    try {
      currentE164 = (typeof intlTelInputUtils !== 'undefined')
        ? iti.getNumber(intlTelInputUtils.numberFormat.E164)
        : iti.getNumber();
    } catch (_) {
      currentE164 = '';
    }

    // Skip validation if already valid and the E164 number hasn't changed
    if (input.classList.contains('valid') &&
        input.dataset.lastValidatedE164 &&
        input.dataset.lastValidatedE164 === currentE164 &&
        currentE164.length > 0) {
      debugLog('Phone number already validated and E164 unchanged, skipping validation');
      return;
    }

    const countryData = iti.getSelectedCountryData() || {};
    const defaultCountry = PHONE_GEO_MODE === 'strict' 
      ? String(STRICT_COUNTRY_ISO2).toLowerCase() 
      : String(MULTIGEO_INITIAL_COUNTRY).toLowerCase();
    const iso2 = (countryData.iso2 || defaultCountry).toLowerCase();
    const dial = countryData.dialCode || START_DIAL_CODE;

    // Use the E164 number we already calculated
    let e164 = currentE164;

    // Fallback: construct E164 if we couldn't get it from intl-tel-input
    if (!e164) {
      const national = normalizePhoneNumber(input.value, iso2);
      if (national.length > 0) e164 = `+${dial}${national}`;
    }

    const digitsCount = (e164 || '').replace(/\D/g, '').length;
    if (!e164 || digitsCount < 8) {
      input.classList.remove('valid', 'invalid');
      return;
    }

    const cacheKey = `${e164}_${iso2}`;

    // Use authoritative server cache if present
    if (phoneValidationCache.has(cacheKey)) {
      const cached = phoneValidationCache.get(cacheKey);
      if (cached && cached.source === 'server') {
        debugLog('Using SERVER cached phone validation result for:', e164);
        if (cached.valid) {
          setValidPhoneInput(input, lang, e164);
          if (typeof php_var !== 'undefined') php_var.phone = cached.formattedNumber || e164;
        } else {
          setInvalidPhoneInput(input, 'invalidPhoneNumber', lang);
        }
        return;
      }
    }

    // Avoid duplicate in-flight requests
    if (serverValidationInFlight.has(cacheKey)) {
      debugLog('Server validation already in-flight for:', e164);
      return;
    }

    // Visual "checking" state
    input.classList.remove('valid', 'invalid');
    input.style.borderColor = '#ffc107';
    input.style.boxShadow = '0 0 3px rgba(255, 193, 7, 0.3)';
    input.style.backgroundImage =
      'url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 24 24\' fill=\'none\' stroke=\'currentColor\' stroke-width=\'2\' stroke-linecap=\'round\' stroke-linejoin=\'round\'%3E%3Cpath d=\'M21 12a9 9 0 11-6.219-8.56\'/%3E%3C/svg%3E")';
    input.style.backgroundRepeat = 'no-repeat';
    input.style.backgroundPosition = 'right 10px center';
    input.style.backgroundSize = '20px 20px';

    // Client-side assessment (only used on network failure)
    let clientValid = false;
    try {
      clientValid = typeof iti.isValidNumber === 'function' ? iti.isValidNumber() : false;
    } catch (_) {
      clientValid = false;
    }

    // Call server (authoritative)
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 15000);
    const region = iso2.toUpperCase();
    serverValidationInFlight.add(cacheKey);

    fetch(`../api/getNumberType.php?phoneNumber=${encodeURIComponent(e164)}&numberRegion=${region}`, {
      signal: controller.signal
    })
      .then((r) => {
        clearTimeout(timeoutId);
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then((data) => {
        input.style.backgroundImage = ''; // clear loader icon

        const serverOk = data && data.success === true && data.numberType !== 10;

        if (serverOk) {
          const formatted = e164;
          if (typeof php_var !== 'undefined') php_var.phone = formatted;

          // Authoritative cache
          phoneValidationCache.set(cacheKey, {
            valid: true,
            formattedNumber: formatted,
            source: 'server',
            ts: Date.now()
          });
          cleanPhoneValidationCache();

          setValidPhoneInput(input, lang, e164);
        } else {
          // Authoritative "invalid"
          phoneValidationCache.set(cacheKey, {
            valid: false,
            source: 'server',
            ts: Date.now()
          });
          formMonitor.recordValidationError('phone', `Server rejected (success=${data && data.success}, type=${data && data.numberType})`);
          setInvalidPhoneInput(input, 'invalidPhoneNumber', lang);
        }
      })
      .catch((err) => {
        clearTimeout(timeoutId);
        input.style.backgroundImage = '';

        debugLog('Phone validation network error (fallback to client):', err?.message || err);
        formMonitor.recordValidationError('phone', `Network/timeout: ${err?.name || err}`);

        // Fallback ONLY on network failure: do NOT cache client result
        if (clientValid) {
          if (typeof php_var !== 'undefined') php_var.phone = e164;
          setValidPhoneInput(input, lang, e164);
        } else {
          setInvalidPhoneInput(input, 'serverError', lang);
        }
      })
      .finally(() => {
        serverValidationInFlight.delete(cacheKey);
      });
  }

  /**
   * Marks phone input as valid, clears transient styles, stores lastValidatedE164,
   * shows a success toast, and toggles submit state.
   * @param {HTMLInputElement} input
   * @param {string} lang
   * @param {string} e164 - The validated E164 number for caching comparison
   */
  function setValidPhoneInput(input, lang, e164 = '') {
    input.classList.add('valid');
    input.classList.remove('invalid');
    input.style.borderColor = '';
    input.style.boxShadow = '';
    input.style.backgroundColor = '';
    input.style.backgroundImage = '';
    input.title = '';
    
    // Store the E164 for comparison rather than the formatted display value
    input.dataset.lastValidatedE164 = e164;
    // Keep the old property for backwards compatibility
    input.dataset.lastValidatedNumber = input.value.trim();

    toastr.success(getErrorMessage('validPhoneNumber', lang));

    const form = input.closest(MAIN_FORM_SELECTOR);
    if (form) toggleSubmitButton(form);
  }

  /**
   * Marks phone input as invalid, clears transient styles and validation cache,
   * shows an error toast, and toggles submit state.
   * @param {HTMLInputElement} input
   * @param {string} key
   * @param {string} lang
   */
  function setInvalidPhoneInput(input, key, lang) {
    input.classList.remove('valid');
    input.classList.add('invalid');
    input.style.borderColor = '';
    input.style.boxShadow = '';
    input.style.backgroundColor = '';
    input.style.backgroundImage = '';
    input.title = '';
    
    // Clear validation cache
    delete input.dataset.lastValidatedNumber;
    delete input.dataset.lastValidatedE164;

    toastr.error(getErrorMessage(key, lang));

    const form = input.closest(MAIN_FORM_SELECTOR);
    if (form) toggleSubmitButton(form);
  }

  /**
   * Email validation with optional Mailcheck suggestions.
   * @param {HTMLInputElement} input
   * @param {string} [lang=defaultLanguage]
   */
  function validateEmail(input, lang = defaultLanguage) {
    const email = (input.value || '').trim();

    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
      toastr.error(getErrorMessage('invalidEmail', lang));
      input.classList.add('invalid');
      input.classList.remove('valid');
      const form = input.closest(MAIN_FORM_SELECTOR);
      if (form) toggleSubmitButton(form);
      return;
    }

    if (typeof Mailcheck !== 'undefined') {
      Mailcheck.run({
        email,
        suggested(s) {
          const t = toastr.error(`Did you mean ${s.full}?`);
          if (t && typeof t.on === 'function') {
            t.on('click', () => {
              input.value = s.full;
              validateEmail(input, lang);
            });
          }
        },
        empty() {
          toastr.success(getErrorMessage('validEmail', lang));
          input.classList.add('valid');
          input.classList.remove('invalid');
          const form = input.closest(MAIN_FORM_SELECTOR);
          if (form) toggleSubmitButton(form);
        }
      });
    } else {
      toastr.success(getErrorMessage('validEmail', lang));
      input.classList.add('valid');
      input.classList.remove('invalid');
      const form = input.closest(MAIN_FORM_SELECTOR);
      if (form) toggleSubmitButton(form);
    }
  }

  /**
   * Subtle progress indicator in the title while typing phone number.
   * Enhanced to handle intl-tel-input formatting properly.
   * @param {HTMLInputElement} input
   * @param {number} currentLength - normalized digits length
   * @param {number} expectedLength
   */
  function updatePhoneProgress(input, currentLength, expectedLength) {
    const progress = Math.min(currentLength / expectedLength, 1);
    const percentage = Math.round(progress * 100);

    input.classList.remove('valid', 'invalid');
    input.style.borderColor = '';
    input.style.boxShadow = '';
    input.style.backgroundColor = '';

    if (progress < 1 && currentLength > 0) {
      const rawLength = input.value.replace(/\D/g, '').length;
      const displayValue = input.value.trim();
      
      // Create more informative tooltip
      let tooltip;
      if (currentLength !== rawLength) {
        tooltip = `${currentLength}/${expectedLength} digits (${percentage}% complete, normalized from ${rawLength} raw digits)`;
      } else {
        tooltip = `${currentLength}/${expectedLength} digits (${percentage}% complete)`;
      }
      
      // Add format info if intl-tel-input is formatting the display
      if (displayValue.length > rawLength) {
        tooltip += ` - formatted display: "${displayValue}"`;
      }
      
      input.title = tooltip;
    } else {
      input.title = '';
    }
  }

  /**
   * Returns expected national number length for the current country, using utils if available, else falls back.
   * @param {any} iti
   * @returns {number}
   */
  function getExpectedPhoneLength(iti) {
    const countryData = iti.getSelectedCountryData();
    const defaultCountry = PHONE_GEO_MODE === 'strict' 
      ? String(STRICT_COUNTRY_ISO2).toLowerCase() 
      : String(MULTIGEO_INITIAL_COUNTRY).toLowerCase();
    const countryCode = (countryData && countryData.iso2) || defaultCountry;

    if (typeof intlTelInputUtils !== 'undefined') {
      try {
        const exampleNumber = intlTelInputUtils.getExampleNumber(
          countryCode, true, intlTelInputUtils.numberFormat.E164
        );
        if (exampleNumber) {
          const national = exampleNumber.replace('+' + countryData.dialCode, '').replace(/\D/g, '');
          if (national.length) return national.length;
        }
      } catch (e) {
        console.warn('[FULL-SCRIPT] Could not get example number for country:', countryCode);
      }
    }

    const fallback = {
      us: 10, ca: 10, gb: 11, au: 9, de: 11, fr: 10, it: 10, es: 9, pt: 9, br: 11,
      mx: 10, ar: 10, ru: 10, ua: 9, pl: 9, cz: 9, sk: 9, hu: 9, ro: 10, bg: 9,
      hr: 8, rs: 8, si: 8, ba: 8, me: 8, mk: 8, al: 9, gr: 10, tr: 10, cy: 8,
      nl: 9, be: 9, lu: 9, ch: 9, at: 11, li: 7, dk: 8, se: 9, no: 8, fi: 9,
      ee: 7, lv: 8, lt: 8, ie: 9, is: 7, mt: 8, ad: 6, sm: 10, va: 10, mc: 8,
      jp: 11, kr: 11, cn: 11, in: 10, id: 12, th: 9, my: 10, sg: 8, ph: 10, vn: 9,
      za: 9, eg: 10, ma: 9, ng: 10, ke: 9, gh: 9, tz: 9, ug: 9, zw: 9, zm: 9,
      mw: 9, bw: 8, na: 9, sz: 8, ls: 8
    };
    return fallback[countryCode] || 10;
  }

  /**
   * Returns maximum allowed phone length, allowing a small relaxation above expected.
   * @param {any} iti
   * @returns {number}
   */
  function getMaxPhoneLength(iti) {
    return getExpectedPhoneLength(iti) + 2;
  }

  /**
   * Updates the phone input placeholder using utils when available.
   * @param {any} iti
   * @param {HTMLInputElement} input
   */
  function updatePlaceholder(iti, input) {
    try {
      if (typeof intlTelInputUtils !== 'undefined') {
        const country = iti.getSelectedCountryData().iso2;
        input.placeholder = intlTelInputUtils.getExampleNumber(
          country, true, intlTelInputUtils.numberFormat.INTERNATIONAL
        );
      } else {
        input.placeholder = DEFAULT_PHONE_PLACEHOLDER;
      }
    } catch (_) {
      input.placeholder = DEFAULT_PHONE_PLACEHOLDER;
    }
  }

  /**
   * Checks for autocompleted name/email fields and silently sets their validation classes.
   * Runs after initial form initialization.
   */
  function checkAutocompletedFieldsQuietly() {
    const forms = document.querySelectorAll(MAIN_FORM_SELECTOR);
    forms.forEach((form) => {
      const nameFields = form.querySelectorAll('[name="firstName"], [name="lastName"]');
      nameFields.forEach((field) => {
        if (
          field.value.trim().length > 0 &&
          !field.classList.contains('valid') &&
          !field.classList.contains('invalid') &&
          field._smartValidationInitialized
        ) {
          debugLog(`Quietly checking autocompleted field: ${field.name} = "${field.value}"`);
          validateNameFieldSilent({ target: field, type: 'quiet-autocomplete-check' });
        }
      });

      const emailField = form.querySelector('[name="email"]');
      if (
        emailField &&
        emailField.value.trim().length > 0 &&
        !emailField.classList.contains('valid') &&
        !emailField.classList.contains('invalid')
      ) {
        console.log(`Quietly checking autocompleted email: "${emailField.value}"`);
        const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailField.value);
        emailField.classList.toggle('valid', ok);
        emailField.classList.toggle('invalid', !ok);
      }

      toggleSubmitButton(form);
    });
  }

  /**
   * Initializes a single form:
   *  - submission handler
   *  - name/email/phone validation
   *  - field listeners
   *  - external scripts (once)
   * @param {HTMLFormElement} form
   */
  function initializeForm(form) {
    if (initializedForms.has(form)) return;
    initializedForms.add(form);

    initializeFormSubmission(form);
    disableSubmitButton(form);
    setupFieldListeners(form);

    const firstNameInput = form.querySelector('[name="firstName"]');
    const lastNameInput = form.querySelector('[name="lastName"]');
    if (firstNameInput) setupSmartValidation(firstNameInput, form);
    if (lastNameInput) setupSmartValidation(lastNameInput, form);

    if (!scriptsLoaded) {
      Promise.all([
        loadScript('https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/19.2.16/js/intlTelInput.min.js'),
        loadScript('https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/19.2.16/js/utils.js'),
        loadScript('mailcheck.min.js')
      ])
        .then(() => {
          scriptsLoaded = true;
          debugLog('All scripts loaded, initializing phone/email validation for all forms');
          document.querySelectorAll(MAIN_FORM_SELECTOR).forEach((f) => {
            initializePhoneValidation(f);
            initializeEmailValidation(f);
          });
        })
        .catch((err) => {
          console.error('[FULL-SCRIPT] Script load error:', err);
          setTimeout(() => {
            initializePhoneValidation(form);
            initializeEmailValidation(form);
          }, 2000);
        });
    } else {
      initializePhoneValidation(form);
      initializeEmailValidation(form);
    }
  }

  /** === BOOTSTRAP === */
  document.addEventListener('DOMContentLoaded', () => {
    createPageLoader();
    injectAutofillCSS();
    loadErrorMessages();
    configureToastr();

    const el = document.querySelector(`.${pageLoaderClass}`);
    if (el) el.style.display = 'block';
  });

  document.addEventListener('DOMContentLoaded', () => {
    setTimeout(() => {
      const forms = document.querySelectorAll(MAIN_FORM_SELECTOR);
      forms.forEach((form) => {
        if (!initializedForms.has(form)) initializeForm(form);
      });
      setTimeout(() => checkAutocompletedFieldsQuietly(), 2000);
    }, 500);
  });

  // Observe dynamically added forms and initialize them
  const observer = new MutationObserver(() => {
    const forms = document.querySelectorAll(MAIN_FORM_SELECTOR);
    forms.forEach((form) => {
      if (!initializedForms.has(form)) {
        setTimeout(() => {
          if (!initializedForms.has(form)) initializeForm(form);
        }, 100);
      }
    });
  });
  if (document.body) observer.observe(document.body, { childList: true, subtree: true });

  // Debug helpers
  if (DEBUG_MODE) {
    window.formDebug = {
      getHealthStatus: () => formMonitor.getHealthStatus(),
      getCacheSize: () => phoneValidationCache.size,
      clearCache: () => phoneValidationCache.clear(),
      getInitializedForms: () => initializedForms,
      getMonitorData: () => ({
        submissionAttempts: Object.fromEntries(formMonitor.submissionAttempts),
        validationErrors: Object.fromEntries(formMonitor.validationErrors)
      })
    };

    setInterval(() => {
      const health = formMonitor.getHealthStatus();
      console.log('[FORM-HEALTH]', health);
    }, 300000);
  }
})();

/* =========================
 *      BACKGROUND SCRIPT
 * ========================= */
(function () {
  'use strict';

  if (window.bScriptInitialized) return;
  window.bScriptInitialized = true;

  /**
   * Debug mode for background script.
   * @type {boolean}
   */
  const DEBUG_MODE = false;

  /**
   * Logger for background script (respects DEBUG_MODE).
   * @param  {...any} args
   */
  function debugLog(...args) {
    if (DEBUG_MODE) {
      console.log('[B-SCRIPT]', ...args);
    }
  }

  /** Tracks if the user has submitted the form successfully (to avoid auto-submit on unload). */
  let formSubmitted = false;

  /** Holds the last snapshot of form data to detect changes. */
  let lastFormData = null;

  /** Guards to avoid duplicate unload handling. */
  let unloadHandled = false;
  let unloadTimeout = null;

  /** CSS selectors of forms we track for auto-submit on unload. */
  const FORM_SELECTORS = TRACKED_FORM_SELECTORS;

  /** Field names excluded from capture. */
  const EXCLUDED_FIELDS = EXCLUDED_BACKGROUND_FIELDS;

  /**
   * Converts FormData to x-www-form-urlencoded Blob so sendBeacon sends a parseable body for PHP.
   * @param {FormData} fd
   * @returns {Blob}
   */
  function formDataToURLEncodedBlob(fd) {
    const params = new URLSearchParams();
    for (const [k, v] of fd.entries()) params.append(k, String(v));
    return new Blob([params.toString()], { type: 'application/x-www-form-urlencoded; charset=UTF-8' });
  }

  /**
   * Collects visible form fields into object keyed by form instance, with phoneNumber normalized to E.164 where possible.
   * @returns {Record<string, Record<string, string[]|string>>|null}
   */
  function collectFormData() {
    const formData = {};
    let hasData = false;

    FORM_SELECTORS.forEach((selector) => {
      const forms = document.querySelectorAll(selector);

      forms.forEach((form, index) => {
        const formFields = {};
        const elements = form.querySelectorAll('input, select, textarea');

        elements.forEach((element) => {
          const name = element.name || element.id;
          const type = element.type;

          if (!name || EXCLUDED_FIELDS.includes(name.toLowerCase())) return;

          let value = '';

          switch (type) {
            case 'checkbox':
            case 'radio':
              if (element.checked) value = element.value;
              break;

            case 'select-multiple': {
              const selected = Array.from(element.selectedOptions);
              value = selected.map((o) => o.value);
              break;
            }

            case 'tel': {
              if (name === 'phone') {
                if (element.intlTelInput && typeof element.intlTelInput.getNumber === 'function') {
                  try {
                    value =
                      (typeof intlTelInputUtils !== 'undefined')
                        ? element.intlTelInput.getNumber(intlTelInputUtils.numberFormat.E164)
                        : element.intlTelInput.getNumber();
                    if (!value || value.length <= 6) value = element.value.trim();
                    debugLog(`Form ${index} - Phone E164 format: "${value}"`);
                  } catch (e) {
                    value = element.value.trim();
                    debugLog(`Form ${index} - Phone E164 error, fallback: "${value}"`);
                  }
                } else {
                  value = element.value.trim();
                  debugLog(`Form ${index} - Phone no intlTelInput: "${value}"`);
                }
              } else {
                value = element.value.trim();
              }
              break;
            }

            default: {
              if (name === 'phone') {
                if (element.intlTelInput && typeof element.intlTelInput.getNumber === 'function') {
                  try {
                    value =
                      (typeof intlTelInputUtils !== 'undefined')
                        ? element.intlTelInput.getNumber(intlTelInputUtils.numberFormat.E164)
                        : element.intlTelInput.getNumber();
                    if (!value || value.length <= 6) value = element.value.trim();
                    debugLog(`Form ${index} - Phone E164 format (by name): "${value}"`);
                  } catch (e) {
                    value = element.value.trim();
                    debugLog(`Form ${index} - Phone E164 error (by name), fallback: "${value}"`);
                  }
                } else {
                  value = element.value.trim();
                  debugLog(`Form ${index} - Phone no intlTelInput (by name): "${value}"`);
                }
              } else {
                value = element.value.trim();
              }
            }
          }

          if (Array.isArray(value) ? value.length > 0 : !!value) {
            const fieldName = name === 'phone' ? 'phoneNumber' : name;
            formFields[fieldName] = value;
            hasData = true;
            debugLog(`Form ${index} - Added field ${fieldName}: "${Array.isArray(value) ? value.join(',') : value}"`);
          }
        });

        if (Object.keys(formFields).length > 0) {
          const formKey = `${selector}-${index}`;
          formData[formKey] = formFields;
          debugLog(`Form ${index} collected fields:`, formFields);
        }
      });
    });

    return hasData ? formData : null;
  }

  /**
   * Sends multiple forms sequentially to avoid duplication on the server.
   * @param {Record<string, Record<string, string>>} formData
   * @returns {Promise<boolean>}
   */
  async function sendFormData(formData) {
    const keys = Object.keys(formData);
    if (!keys.length) {
      debugLog('No valid forms to send');
      return false;
    }

    const results = [];
    for (const formKey of keys) {
      const fields = formData[formKey];
      if (!fields || !Object.keys(fields).length) continue;

      if (!fields.phoneNumber || (!fields.firstName && !fields.lastName)) {
        debugLog(
          `Skipping form ${formKey}: missing required fields ` +
          `(phoneNumber: ${!!fields.phoneNumber}, firstName: ${!!fields.firstName}, lastName: ${!!fields.lastName})`
        );
        continue;
      }

      debugLog(`Auto-submitting form ${formKey}:`, fields);
      /* eslint no-await-in-loop: "off" */
      const ok = await sendSingleForm(fields, formKey);
      results.push(ok);
      await new Promise((r) => setTimeout(r, 100));
    }

    debugLog('All forms sent sequentially, results:', results);
    return results.length > 0 && results.every(Boolean);
  }

  /**
   * Sends a single form using sendBeacon (with URL-encoded Blob) or fetch with timeout & retries.
   * @param {Record<string,string>} fields
   * @param {string} formKey
   * @returns {Promise<boolean>}
   */
  async function sendSingleForm(fields, formKey) {
    try {
      debugLog(`=== Preparing form ${formKey} ===`);
      debugLog('Original form fields:', fields);

      const payload = new FormData();

      if (fields.firstName) payload.append('firstName', fields.firstName);
      if (fields.lastName) payload.append('lastName', fields.lastName);
      if (fields.email) payload.append('email', fields.email);
      if (fields.phoneNumber) payload.append('phoneNumber', fields.phoneNumber);

      if (typeof php_var !== 'undefined' && php_var && typeof php_var === 'object') {
        for (const [key, value] of Object.entries(php_var)) payload.append(key, value);
        debugLog('✓ Added php_var data');
      }

      payload.append('auto_submit', 'true');
      payload.append('form_key', formKey);
      payload.append('submit_time', new Date().toISOString());
      payload.append('page_url', window.location.href);
      payload.append('user_agent', navigator.userAgent);
      payload.append('screen_resolution', `${screen.width}x${screen.height}`);

      debugLog(`=== Sending ${formKey} ===`);
      debugLog('Payload keys:', Array.from(payload.keys()));

      // Prefer sendBeacon with x-www-form-urlencoded Blob to aid PHP parsing
      if (navigator.sendBeacon) {
        const blob = formDataToURLEncodedBlob(payload);
        const sent = navigator.sendBeacon('../api/action.php', blob);
        debugLog(`SendBeacon result for ${formKey}:`, sent);
        if (sent) return true;
      }

      // Fallback: fetch with timeout & retry
      for (let attempt = 1; attempt <= 3; attempt++) {
        try {
          const controller = new AbortController();
          const tid = setTimeout(() => controller.abort(), 10000); // 10s timeout

          const response = await fetch('../api/action.php', {
            method: 'POST',
            body: payload,
            keepalive: true,
            signal: controller.signal
          });

          clearTimeout(tid);
          debugLog(`Fetch attempt ${attempt} for ${formKey}:`, response.status);

          if (response.ok) {
            return true;
          }
          if (response.status >= 500 && attempt < 3) {
            await new Promise((r) => setTimeout(r, 1000 * attempt));
            continue;
          }
          return false;
        } catch (fetchError) {
          debugLog(`Fetch attempt ${attempt} failed for ${formKey}:`, fetchError);
          if (attempt === 3) return false;
          await new Promise((r) => setTimeout(r, 1000 * attempt));
        }
      }

      return false;
    } catch (error) {
      debugLog(`❌ Auto-submit error for ${formKey}:`, error);
      return false;
    }
  }

  /**
   * Detects whether form data changed since last snapshot (used to decide unload behavior).
   * @returns {boolean}
   */
  function checkFormChanges() {
    const currentData = collectFormData();
    if (!currentData) {
      lastFormData = null;
      return false;
    }
    const changed = JSON.stringify(currentData) !== JSON.stringify(lastFormData);
    if (changed) {
      lastFormData = currentData;
      debugLog('Form data changed:', currentData);
    }
    return changed;
  }

  /** Marks that the form was submitted successfully. */
  function markFormAsSubmitted() {
    formSubmitted = true;
    lastFormData = null;
    unloadHandled = true;
    clearTimeout(unloadTimeout);
    debugLog('Form successfully submitted');
  }

  /**
   * Handles page unload-related events (beforeunload/pagehide/visibilitychange)
   * and performs auto-submit if required fields are present.
   * @param {Event} event
   */
  function handlePageUnload(event) {
    debugLog('=== Handling page unload ===');
    debugLog('Event type:', event.type);
    debugLog('Form submitted:', formSubmitted);
    debugLog('Unload handled:', unloadHandled);
    debugLog('bScriptSending:', window.bScriptSending);

    if (formSubmitted) {
      debugLog('Form already submitted, skipping auto-submit');
      return;
    }

    if (event.type === 'visibilitychange' && window.bScriptSending) {
      debugLog('Auto-submit already in progress from other event, skipping visibilitychange');
      return;
    }

    if (event.type === 'pagehide' && unloadHandled && (Date.now() - (window.lastUnloadTime || 0)) < 1000) {
      debugLog('Page unload already handled recently by beforeunload, skipping pagehide');
      return;
    }

    unloadHandled = true;
    window.bScriptSending = true;
    window.lastUnloadTime = Date.now();

    clearTimeout(unloadTimeout);
    unloadTimeout = setTimeout(() => {
      debugLog('Resetting unload flags after timeout');
      unloadHandled = false;
      window.bScriptSending = false;
    }, 5000);

    const formData = collectFormData();
    if (!formData) {
      debugLog('No data for auto-submit');
      unloadHandled = false;
      window.bScriptSending = false;
      return;
    }

    let hasValidData = false;
    for (const [formKey, fields] of Object.entries(formData)) {
      if (fields && Object.keys(fields).length > 0) {
        if (fields.phoneNumber && (fields.firstName || fields.lastName)) {
          hasValidData = true;
          const nameInfo = fields.firstName && fields.lastName
            ? `firstName="${fields.firstName}", lastName="${fields.lastName}"`
            : fields.firstName
            ? `firstName="${fields.firstName}"`
            : `lastName="${fields.lastName}"`;
          debugLog(`Found valid form data in ${formKey}: ${nameInfo}, phoneNumber="${fields.phoneNumber}"`);
          break;
        }
      }
    }

    if (!hasValidData) {
      debugLog('Insufficient data for auto-submit (need phoneNumber AND (firstName OR lastName))');
      unloadHandled = false;
      window.bScriptSending = false;
      return;
    }

    debugLog(`=== Starting auto-submit on ${event.type} ===`);
    debugLog('Form data to submit:', formData);

    sendFormData(formData)
      .then((success) => debugLog(`Auto-submit completed with result: ${success}`))
      .catch((error) => debugLog('Auto-submit failed with error:', error));

    if (DEBUG_MODE && event.type === 'beforeunload') {
      event.preventDefault();
      event.returnValue = 'You have unsaved data. Are you sure you want to leave the page?';
      return event.returnValue;
    }
  }

  /**
   * Wires listeners for tracking and for unload-like events with dedup protection.
   */
  function initializeTracking() {
    debugLog('Initializing form tracking...');

    if (window.bScriptTrackingInitialized) {
      debugLog('Tracking already initialized, skipping');
      return;
    }
    window.bScriptTrackingInitialized = true;

    document.addEventListener('input', checkFormChanges);
    document.addEventListener('change', checkFormChanges);

    FORM_SELECTORS.forEach((selector) => {
      const forms = document.querySelectorAll(selector);
      forms.forEach((form, index) => {
        form.addEventListener('submit', markFormAsSubmitted);
        debugLog(`Tracking added for form: ${selector}-${index}`);
      });
    });

    window.addEventListener('beforeunload', (e) => {
      debugLog('beforeunload event triggered');
      handlePageUnload(e);
    });

    window.addEventListener('pagehide', (e) => {
      debugLog('pagehide event triggered');
      setTimeout(() => {
        if (!window.bScriptSending) {
          handlePageUnload(e);
        } else {
          debugLog('pagehide: beforeunload already processing, skipping');
        }
      }, 50);
    });

    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'hidden') {
        debugLog('visibilitychange to hidden triggered');
        setTimeout(() => {
          if (!window.bScriptSending && !unloadHandled) {
            const syntheticEvent = new Event('visibilitychange');
            handlePageUnload(syntheticEvent);
          } else {
            debugLog('visibilitychange: other unload handler already processing, skipping');
          }
        }, 200);
      }
    });

    debugLog('Tracking initialized');
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initializeTracking);
  } else {
    initializeTracking();
  }

  debugLog('B-SCRIPT loaded');
})();

/* =========================
 *   BURGER MENU TOGGLE
 * ========================= */
(function() {
  'use strict';

  function initBurgerMenu() {
    const burgerBtn = document.getElementById('burger-menu-btn');
    const mainNav = document.getElementById('main-navigation');
    const overlay = document.getElementById('mobile-menu-overlay');

    if (!burgerBtn || !mainNav || !overlay) {
      return;
    }

    function toggleMenu() {
      const isActive = burgerBtn.classList.contains('active');
      
      if (isActive) {
        closeMenu();
      } else {
        openMenu();
      }
    }

    function openMenu() {
      burgerBtn.classList.add('active');
      mainNav.classList.add('active');
      overlay.classList.add('active');
      document.body.style.overflow = 'hidden';
    }

    function closeMenu() {
      burgerBtn.classList.remove('active');
      mainNav.classList.remove('active');
      overlay.classList.remove('active');
      document.body.style.overflow = '';
    }

    burgerBtn.addEventListener('click', toggleMenu);
    overlay.addEventListener('click', closeMenu);

    // Close menu when clicking on navigation links
    const navLinks = mainNav.querySelectorAll('a');
    navLinks.forEach(link => {
      link.addEventListener('click', () => {
        setTimeout(closeMenu, 100);
      });
    });

    // Close menu on escape key
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape' && burgerBtn.classList.contains('active')) {
        closeMenu();
      }
    });

    // Close menu on window resize if it becomes desktop size
    window.addEventListener('resize', () => {
      if (window.innerWidth > 767 && burgerBtn.classList.contains('active')) {
        closeMenu();
      }
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initBurgerMenu);
  } else {
    initBurgerMenu();
  }
})();