/**
 * Meta (Facebook) Pixel integration for the stand-alone contact page.
 *
 * Rather than injecting the customer-supplied `<script>` snippet verbatim
 * (which hard-codes a single pixel ID and is prone to copy/paste artifacts
 * such as smart quotes), we run the standard `fbq` bootstrap programmatically
 * with a per-customer pixel ID supplied via config or URL.
 *
 * @see https://developers.facebook.com/docs/meta-pixel/get-started
 */

const FBEVENTS_SRC = "https://connect.facebook.net/en_US/fbevents.js";

/**
 * Minimal typing for the `fbq` function the Meta Pixel installs on `window`.
 */
type Fbq = ((...args: unknown[]) => void) & {
    callMethod?: (...args: unknown[]) => void;
    queue?: unknown[];
    loaded?: boolean;
    version?: string;
    push?: unknown;
};

declare global {
    interface Window {
        fbq?: Fbq;
        _fbq?: Fbq;
    }
}

/**
 * Validates that a value looks like a Meta Pixel ID.
 *
 * Pixel IDs are numeric strings (Meta's are currently 15-16 digits). We accept
 * 14-20 to leave margin for future ID lengths while still rejecting typos.
 */
export function isValidMetaPixelId(pixelId: unknown): pixelId is string {
    return typeof pixelId === "string" && /^[0-9]{14,20}$/.test(pixelId.trim());
}

/**
 * Loads the Meta Pixel base code (idempotent) and installs the `fbq` stub.
 *
 * Mirrors the official snippet: creates the `fbq` command queue, then injects
 * the `fbevents.js` loader script. Safe to call multiple times — subsequent
 * calls are no-ops once `window.fbq` exists.
 *
 * Limited Data Use is enabled here (not in `initMetaPixel`) because
 * `dataProcessingOptions` is a *global* fbq command with no pixel scope —
 * applying it only when we install our own stub avoids overriding the LDU
 * settings of a host page that already loaded its own `fbq`.
 */
function loadFbqBase(): void {
    if (window.fbq) {
        return;
    }

    const fbq: Fbq = function (...args: unknown[]) {
        if (fbq.callMethod) {
            fbq.callMethod(...args);
        } else {
            (fbq.queue as unknown[]).push(args);
        }
    };

    if (!window._fbq) {
        window._fbq = fbq;
    }
    fbq.push = fbq;
    fbq.loaded = true;
    fbq.version = "2.0";
    fbq.queue = [];
    window.fbq = fbq;

    // Enable Limited Data Use for California visitors. The `0, 0` args let Meta
    // geolocate from the IP server-side and apply LDU only to CA users; everyone
    // else is unaffected. Must be set before any `init`.
    window.fbq("dataProcessingOptions", ["LDU"], 0, 0);

    // Inject the loader script if it isn't already on the page.
    // CSP: the host's Content-Security-Policy must allow this in script-src
    // (`script-src https://connect.facebook.net`). If the deployment ever moves
    // to a nonce-based CSP, a nonce must be threaded onto this element or the
    // injection is silently blocked (fbevents.js never loads, no events fire).
    const existing = document.querySelector(`script[src="${FBEVENTS_SRC}"]`);
    if (!existing) {
        const script = document.createElement("script");
        script.async = true;
        script.src = FBEVENTS_SRC;
        const firstScript = document.getElementsByTagName("script")[0];
        if (firstScript && firstScript.parentNode) {
            firstScript.parentNode.insertBefore(script, firstScript);
        } else {
            (document.head || document.documentElement).appendChild(script);
        }
    }
}

// Tracks which pixel IDs have been initialized so we don't double-init (which
// would cause Meta to log duplicate-pixel warnings).
const initializedPixels = new Set<string>();

/**
 * Returns true if the visitor has opted out of sale/sharing via the Global
 * Privacy Control browser signal.
 *
 * CCPA/CPRA require honoring GPC as a valid opt-out of sale/sharing. GPC is a
 * location-agnostic browser signal, so we honor it for every visitor rather
 * than trying to geolocate.
 */
export function hasGlobalPrivacyControlOptOut(): boolean {
    if (typeof navigator === "undefined") {
        return false;
    }
    return (
        (navigator as Navigator & { globalPrivacyControl?: boolean }).globalPrivacyControl === true
    );
}

/**
 * Initializes the Meta Pixel for the given customer pixel ID and tracks the
 * standard `PageView` event.
 *
 * Privacy behavior (CCPA/CPRA):
 * - If the visitor sends a Global Privacy Control opt-out, the pixel is not
 *   loaded at all — no fbevents.js, no PageView, no subsequent Lead.
 * - Otherwise Meta Limited Data Use is enabled so California visitors' data is
 *   processed in restricted mode (Meta geolocates from IP and applies it only
 *   to CA users).
 *
 * Scope: this is a California (CCPA/CPRA) opt-out model. EU/GDPR is intentionally
 * NOT covered — there is no prior-consent gate, so do not enable this for EU
 * audiences without first adding a consent (CMP) gate ahead of the pixel load.
 *
 * Returns `true` if the pixel was initialized (or was already initialized for
 * this ID), `false` if the ID was missing/invalid or the visitor opted out.
 */
export function initMetaPixel(pixelId: string | undefined): boolean {
    if (!isValidMetaPixelId(pixelId)) {
        if (pixelId) {
            console.warn(`[ContactApp] Ignoring invalid Meta Pixel ID: "${pixelId}"`);
        }
        return false;
    }

    // GPC is checked here at call time (and again in trackMetaPixelEvent). Known
    // window: a signal that appears between the config fetch and this call won't
    // be seen until the next event — inherent to any async init pattern.
    if (hasGlobalPrivacyControlOptOut()) {
        return false;
    }

    const id = pixelId.trim();

    loadFbqBase();

    if (initializedPixels.has(id)) {
        return true;
    }

    // LDU is configured inside loadFbqBase (it's a global fbq command).
    // If the host page already loaded its own pixel, `init` registers ours
    // alongside it under Meta's Multi-Pixel model — events we send target our
    // pixel ID specifically, so co-existing with a foreign pixel is safe.
    window.fbq?.("init", id);
    window.fbq?.("track", "PageView");
    initializedPixels.add(id);

    return true;
}

/**
 * Tracks a standard Meta Pixel event (e.g. "Lead", "Schedule").
 *
 * No-op if the pixel has not been initialized or the visitor has opted out.
 *
 * @param event - Standard Meta event name
 * @param data - Optional event parameters (e.g. value, currency)
 * @param eventId - Optional shared event id. When set, it is passed as fbq's
 *   `eventID` so Meta deduplicates this browser event against the server-side
 *   Conversions API event carrying the same id.
 */
export function trackMetaPixelEvent(
    event: string,
    data?: Record<string, unknown>,
    eventId?: string
): void {
    // Guard on initializedPixels too: window.fbq may already exist because the
    // host page loaded its own pixel. Without an init of our own, a track call
    // would be credited to that foreign pixel. A positive size means our pixel
    // is active (the contact app only ever initializes a single pixel).
    //
    // Re-check GPC in case the opt-out signal appeared after the pixel was
    // initialized (e.g. an extension set it post-load).
    if (!window.fbq || initializedPixels.size === 0 || hasGlobalPrivacyControlOptOut()) {
        return;
    }

    if (eventId) {
        // The 4th arg's eventID is what Meta dedupes on (event_name + event_id).
        window.fbq("track", event, data || {}, { eventID: eventId });
    } else if (data) {
        window.fbq("track", event, data);
    } else {
        window.fbq("track", event);
    }
}

/**
 * Resets the in-memory init state. Intended for tests only — no-ops in
 * production. The NODE_ENV check is inside the body (not baked in at module
 * load) so the guard reflects the runtime env, not the bundling env.
 */
export function __resetMetaPixelForTests(): void {
    if (process.env.NODE_ENV === "production") {
        return;
    }
    initializedPixels.clear();
    delete window.fbq;
    delete window._fbq;
}
