<?php
/**
 * TrackSharp WooCommerce Integration
 * 
 * Handles WooCommerce hooks, order stitching, and purchase event sending.
 *
 * @package TrackSharp
 */

if (!defined('ABSPATH')) {
    exit;
}

class TrackSharp_WooCommerce
{
    /** @var TrackSharp_Consent */
    private $consent;

    /** @var TrackSharp_Hasher */
    private $hasher;

    /** @var TrackSharp_GA4 */
    private $ga4;

    /** @var TrackSharp_Meta */
    private $meta;

    /** @var TrackSharp_Events_Log */
    private $events_log;

    private function get_order_value($order): float
    {
        $total = (float) $order->get_total();
        if (!TrackSharp_Features::is_revenue_excl_tax()) {
            return $total;
        }

        $tax = (float) $order->get_total_tax();
        $value = $total - $tax;
        return $value > 0 ? $value : 0.0;
    }

    /**
     * Constructor.
     *
     * @param TrackSharp_Consent         $consent    Consent handler.
     * @param TrackSharp_Hasher|null     $hasher     Hasher utility (Pro only).
     * @param TrackSharp_GA4             $ga4        GA4 client.
     * @param TrackSharp_Meta|null       $meta       Meta client (Pro only).
     * @param TrackSharp_Events_Log      $events_log Events logger.
     */
    public function __construct(
        TrackSharp_Consent $consent,
        ?object $hasher,
        TrackSharp_GA4 $ga4,
        ?object $meta,
        TrackSharp_Events_Log $events_log
    ) {
        $this->consent = $consent;
        $this->hasher = $hasher;
        $this->ga4 = $ga4;
        $this->meta = $meta;
        $this->events_log = $events_log;
    }

    /**
     * Register WooCommerce hooks.
     *
     * @return void
     */
    public function register_hooks()
    {
        add_action('init', [$this, 'ensure_session_cookie']);
        add_action('woocommerce_checkout_create_order', [$this, 'attach_session_to_order'], 10, 2);

        add_action('woocommerce_payment_complete', [$this, 'send_purchase_event'], 10, 1);
        add_action('woocommerce_order_status_processing', [$this, 'send_purchase_event'], 10, 1);
        add_action('woocommerce_order_status_completed', [$this, 'send_purchase_event'], 10, 1);

        add_action('woocommerce_thankyou', [$this, 'backfill_order_consent'], 5, 1);
        add_action('woocommerce_order_status_changed', [$this, 'backfill_order_consent'], 5, 1);

        add_action('woocommerce_add_to_cart', [$this, 'flag_atc_cookie'], 10, 6);
        add_action('wp_enqueue_scripts', [$this, 'enqueue_order_received_purchase_breadcrumb'], 25);
        add_action('woocommerce_checkout_order_processed', [$this, 'capture_order_consent'], 10, 1);
    }

    /**
     * Ensure session cookie is set.
     *
     * @return void
     */
    public function ensure_session_cookie()
    {
        if (headers_sent()) {
            return;
        }

        $required_cat = apply_filters('tracksharp_session_cookie_category', 'functional');
        if (!$this->consent->wp_consent_has($required_cat)) {
            return;
        }

        $cookie = isset($_COOKIE['ws_session_id']) ? sanitize_text_field(wp_unslash($_COOKIE['ws_session_id'])) : '';
        if (!$cookie) {
            $cookie = wp_generate_uuid4();
            setcookie('ws_session_id', $cookie, time() + 60 * 60 * 24 * 365, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
            $_COOKIE['ws_session_id'] = $cookie;
        }
    }

    /**
     * Attach session data to order.
     *
     * @param \WC_Order $order Order object.
     * @param array     $data  Order data.
     * @return void
     */
    public function attach_session_to_order($order, $data)
    {
        $sid = isset($_COOKIE['ws_session_id']) ? sanitize_text_field(wp_unslash($_COOKIE['ws_session_id'])) : '';
        if ($sid) {
            $order->update_meta_data('ws_session_id', $sid);
        }

        // Capture first-party cookies
        if (!empty($_COOKIE['_fbp'])) {
            $order->update_meta_data('_ws_fbp', sanitize_text_field(wp_unslash($_COOKIE['_fbp'])));
        }
        if (!empty($_COOKIE['_fbc'])) {
            $order->update_meta_data('_ws_fbc', sanitize_text_field(wp_unslash($_COOKIE['_fbc'])));
        }
        if (!empty($_COOKIE['ws_gclid'])) {
            $order->update_meta_data('_ws_gclid', sanitize_text_field(wp_unslash($_COOKIE['ws_gclid'])));
        }
        if (!empty($_COOKIE['ws_gbraid'])) {
            $order->update_meta_data('_ws_gbraid', sanitize_text_field(wp_unslash($_COOKIE['ws_gbraid'])));
        }
        if (!empty($_COOKIE['ws_wbraid'])) {
            $order->update_meta_data('_ws_wbraid', sanitize_text_field(wp_unslash($_COOKIE['ws_wbraid'])));
        }

        // Capture attribution hints for server-side fallback
        $order->update_meta_data('_ws_user_agent', isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '');
        $order->update_meta_data('_ws_page_url', (is_ssl() ? 'https://' : 'http://') . sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'] ?? '')) . sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'] ?? '')));

        // Save raw GA cookies as a Cookie header string for sGTM-style header forwarding.
        // Forwarding these as HTTP headers when calling GA4's MP endpoint mirrors how sGTM
        // proxies browser hits, which is what enables proper session-based ads attribution.
        $raw_cookie_parts = [];
        foreach ($_COOKIE as $cookie_key => $cookie_val) {
            $k = (string) $cookie_key;
            if (($k === '_ga' || strpos($k, '_ga_') === 0 || strpos($k, '_gcl_') === 0) && !empty($cookie_val)) {
                $raw_cookie_parts[] = $k . '=' . sanitize_text_field(wp_unslash($cookie_val));
            }
        }
        if ($raw_cookie_parts) {
            $order->update_meta_data('_ws_ga_cookie_header', implode('; ', $raw_cookie_parts));
        }

        // Save browser IP at checkout time (not the server IP used during async hooks/cron).
        $checkout_ip = $this->get_client_ip();
        if ($checkout_ip) {
            $order->update_meta_data('_ws_client_ip', $checkout_ip);
        }

        $initial_ref = isset($_COOKIE['ws_initial_referrer']) ? sanitize_url(wp_unslash($_COOKIE['ws_initial_referrer'])) : '';
        if (!$initial_ref && !empty($_SERVER['HTTP_REFERER'])) {
            $initial_ref = sanitize_url(wp_unslash($_SERVER['HTTP_REFERER']));
        }
        $order->update_meta_data('_ws_page_referrer', $initial_ref);

        // --- GA4: Prefer real GA cookies over TrackSharp fallbacks ---
        // This improves session stitching + GA4 dedup robustness (browser + server purchase must share IDs).
        $ga_cid = '';
        if (!empty($_COOKIE['_ga'])) {
            $ga_raw = sanitize_text_field(wp_unslash($_COOKIE['_ga']));
            $parts = explode('.', $ga_raw);
            if (count($parts) >= 4 && preg_match('/^GA\\d+$/', (string) $parts[0])) {
                $p1 = (string) $parts[2];
                $p2 = (string) $parts[3];
                if ($p1 !== '' && $p2 !== '') {
                    $ga_cid = $p1 . '.' . $p2;
                }
            }
        }
        if (!$ga_cid && !empty($_COOKIE['ws_ga_cid'])) {
            $ga_cid = sanitize_text_field(wp_unslash($_COOKIE['ws_ga_cid']));
        }
        if ($ga_cid) {
            $order->update_meta_data('_ws_ga_cid', $ga_cid);
        }

        $ga_sid = 0;
        $ga_sno = 0;
        $mid = trim((string) get_option('tracksharp_ga4_measurement_id', ''));
        $cookie_name = '';
        if ($mid) {
            $cookie_name = '_ga_' . preg_replace('/^G-/', '', $mid);
        }

        $sess_cookie = '';
        if ($cookie_name && !empty($_COOKIE[$cookie_name])) {
            $sess_cookie = sanitize_text_field(wp_unslash($_COOKIE[$cookie_name]));
        } else {
            foreach (array_keys($_COOKIE) as $k) {
                if (strpos((string) $k, '_ga_') === 0 && !empty($_COOKIE[$k])) {
                    $sess_cookie = sanitize_text_field(wp_unslash($_COOKIE[$k]));
                    break;
                }
            }
        }

        if ($sess_cookie) {
            // GS1.1.<sid>.<sno>....
            if (strpos($sess_cookie, 'GS1.1.') === 0) {
                $p = explode('.', $sess_cookie);
                if (count($p) >= 4) {
                    $ga_sid = (int) $p[2];
                    $ga_sno = (int) $p[3];
                }
            } elseif (strpos($sess_cookie, 'GS2.1') === 0) {
                // GS2.1.s<sid>$...$j<sno>$...
                if (preg_match('/(?:^|[\\.$])s(\\d{6,})\\b/', $sess_cookie, $m)) {
                    $ga_sid = (int) $m[1];
                }
                if (preg_match('/(?:^|[\\.$])j(\\d{1,})\\b/', $sess_cookie, $m2)) {
                    $ga_sno = (int) $m2[1];
                }
            }
        }

        if (!$ga_sid && !empty($_COOKIE['ws_ga_sid'])) {
            $ga_sid = (int) $_COOKIE['ws_ga_sid'];
        }
        if (!$ga_sno && !empty($_COOKIE['ws_ga_sno'])) {
            $ga_sno = (int) $_COOKIE['ws_ga_sno'];
        }
        if ($ga_sid) {
            $order->update_meta_data('_ws_ga_sid', (int) $ga_sid);
        }
        if ($ga_sno) {
            $order->update_meta_data('_ws_ga_sno', (int) $ga_sno);
        }
    }

    /**
     * Capture order consent state.
     *
     * @param int $order_id Order ID.
     * @return void
     */
    public function capture_order_consent($order_id)
    {
        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        // If WP Consent API is available, use actual consent values
        // Otherwise, fallback to the configured setting (grant_immediately by default)
        if (function_exists('wp_has_consent')) {
            $marketing_consent = (bool) wp_has_consent('marketing');
            $stats_consent = (bool) wp_has_consent('statistics') || (bool) wp_has_consent('statistics-anonymous');
        } else {
            // No CMP installed - use fallback setting
            $fallback = get_option('tracksharp_consent_fallback', 'grant_immediately');
            $marketing_consent = ($fallback === 'grant_immediately');
            $stats_consent = ($fallback === 'grant_immediately');
        }

        $order->update_meta_data('_ws_marketing_consent', $marketing_consent ? 1 : 0);
        $order->update_meta_data('_ws_stats_consent', $stats_consent ? 1 : 0);
        $order->save();
    }

    /**
     * Backfill order consent if missing.
     *
     * @param int $order_id Order ID.
     * @return void
     */
    public function backfill_order_consent($order_id)
    {
        if (!$order_id) {
            return;
        }

        $o = wc_get_order($order_id);
        if (!$o) {
            return;
        }

        if ('' === $o->get_meta('_ws_stats_consent', true)) {
            $o->update_meta_data('_ws_stats_consent', $this->consent->server_consent_granted_for('ga4') ? 1 : 0);
            $o->update_meta_data('_ws_marketing_consent', $this->consent->server_consent_granted_for('meta') ? 1 : 0);
            $o->save();
        }
    }

    /**
     * Send server-side purchase event.
     *
     * @param int $order_id Order ID.
     * @return void
     */
    public function send_purchase_event($order_id)
    {
        if (!$order_id || !function_exists('wc_get_order')) {
            return;
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        // Get consent state
        $has_marketing_meta = ('' !== $order->get_meta('_ws_marketing_consent', true));
        $has_stats_meta = ('' !== $order->get_meta('_ws_stats_consent', true));

        $order_marketing_ok = (bool) $order->get_meta('_ws_marketing_consent', true);
        $order_stats_ok = (bool) $order->get_meta('_ws_stats_consent', true);

        $marketing_ok = $order_marketing_ok;
        $stats_ok = $order_stats_ok;

        // Fallback to server state if snapshot missing
        if (!$has_marketing_meta) {
            $marketing_ok = $this->consent->server_consent_granted_for('meta');
        }
        if (!$has_stats_meta) {
            $stats_ok = $this->consent->server_consent_granted_for('ga4');
        }

        if (!$marketing_ok && !$stats_ok) {
            $event_id = 'purchase_' . $order_id;

            $this->events_log->log_event([
                'channel'     => 'ga4',
                'source'      => 'server',
                'event_name'  => TrackSharp_Features::ga4_purchase_event_name(),
                'event_id'    => $event_id,
                'txid'        => (string) $order_id,
                'ok'          => 0,
                'http_status' => null,
                'consent'     => 'denied',
            ]);

            if ($this->meta) {
                $this->events_log->log_event([
                    'channel'     => 'meta',
                    'source'      => 'server',
                    'event_name'  => 'Purchase',
                    'event_id'    => $event_id,
                    'txid'        => (string) $order_id,
                    'ok'          => 0,
                    'http_status' => null,
                    'consent'     => 'denied',
                ]);
            }

            return;
        }

        $ctx = $this->collect_purchase_context($order, $order_id);

        // Send GA4
        if ($stats_ok) {
            $mode = TrackSharp_Features::ga4_attribution_mode();

            // In reporting-first mode, the browser purchase is canonical for GA4 session attribution.
            // We schedule a server-side fallback to avoid losing purchases when the browser can't send.
            if ($mode === 'reporting_first') {
                if (get_post_meta($order_id, '_ws_ga4_purchase_fallback_scheduled', true) !== '1') {
                    $locked = add_post_meta($order_id, '_ws_ga4_purchase_fallback_scheduled', '1', true);
                    if ($locked && function_exists('wp_schedule_single_event')) {
                        wp_schedule_single_event(time() + 60, 'tracksharp_ga4_purchase_fallback', [$order_id]);
                    }
                }
            } else {
                // Server-only (default): send immediately.
                $this->send_ga4_purchase_fallback($order_id);
            }
        }

        // Send Meta
        if ($marketing_ok) {
            $this->send_meta_purchase(
                $order_id,
                $ctx['items'],
                $ctx['currency'],
                $ctx['value'],
                $ctx['event_id'],
                $ctx['em_h'],
                $ctx['ph_h'],
                $ctx['fn_h'],
                $ctx['ln_h'],
                $ctx['ct_h'],
                $ctx['st_h'],
                $ctx['zp_h'],
                $ctx['country_h'],
                $ctx['external_id_h'],
                $ctx['fbp'],
                $ctx['fbc'],
                $ctx['client_ip'],
                $ctx['client_ua']
            );
        }
    }

    /**
     * Send the GA4 purchase server-side after a delay.
     *
     * The delay allows the browser session (page_view/session_start) to establish
     * acquisition context before the server-side purchase arrives.
     *
     * @param int $order_id Order ID.
     * @return void
     */
    public function send_ga4_purchase_fallback($order_id)
    {
        if (!$order_id || !function_exists('wc_get_order')) {
            return;
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        // Best-effort: skip the server fallback if we observed a TrackSharp browser-side
        // GA4 purchase send for this order. This protects against double counting even if
        // WP-Cron runs late or settings were changed after scheduling.
        $browser_seen = (string) get_post_meta((int) $order_id, '_ws_ga4_browser_purchase_sent', true);
        if ($browser_seen === '1') {
            delete_post_meta((int) $order_id, '_ws_ga4_purchase_fallback_scheduled');
            return;
        }

        // Consent: use order snapshot when available; otherwise fall back to server state.
        $has_stats_meta = ('' !== $order->get_meta('_ws_stats_consent', true));
        $order_stats_ok = (bool) $order->get_meta('_ws_stats_consent', true);
        $stats_ok = $order_stats_ok;
        if (!$has_stats_meta) {
            $stats_ok = $this->consent->server_consent_granted_for('ga4');
        }
        if (!$stats_ok) {
            return;
        }

        $ctx = $this->collect_purchase_context($order, (int) $order_id);
        $this->send_ga4_purchase(
            $order,
            (int) $order_id,
            $ctx['items'],
            $ctx['currency'],
            $ctx['value'],
            $ctx['event_id'],
            $ctx['ga_cid'],
            $ctx['ga_sid'],
            $ctx['ga_sno'],
            $ctx['gclid'],
            $ctx['gbraid'],
            $ctx['wbraid'],
            $ctx['user_id'],
            $ctx['em_h'],
            $ctx['ph_h'],
            $ctx['fn_h'],
            $ctx['ln_h'],
            $ctx['street_h'],
            $ctx['billing_city'],
            $ctx['billing_st'],
            $ctx['billing_zp'],
            $ctx['billing_ctry'],
            $order_stats_ok,
            $ctx['page_url'],
            $ctx['page_referrer'],
            $ctx['user_agent'],
            $ctx['raw_cookie_header'],
            $ctx['checkout_ip']
        );
    }

    /**
     * Collect purchase context for GA4/Meta payloads.
     *
     * @param WC_Order $order    WooCommerce order.
     * @param int      $order_id Order ID.
     * @return array<string,mixed>
     */
    private function collect_purchase_context($order, $order_id): array
    {
        $items = [];
        $include_tax_in_items = !TrackSharp_Features::is_revenue_excl_tax();
        foreach ($order->get_items() as $item) {
            $product = $item->get_product();
            $items[] = [
                'item_id' => $product ? $product->get_id() : $item->get_product_id(),
                'item_name' => $item->get_name(),
                'price' => (float) $order->get_item_total($item, $include_tax_in_items),
                'quantity' => (int) $item->get_quantity(),
            ];
        }

        $currency = $order->get_currency() ?: (function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'EUR');
        $value = $this->get_order_value($order);
        $event_id = 'purchase_' . $order_id;

        $sid = (string) $order->get_meta('ws_session_id');
        $fbp = (string) $order->get_meta('_ws_fbp');
        $fbc = (string) $order->get_meta('_ws_fbc');
        $gclid = (string) $order->get_meta('_ws_gclid');
        $gbraid = (string) $order->get_meta('_ws_gbraid');
        $wbraid = (string) $order->get_meta('_ws_wbraid');
        $ga_cid = (string) $order->get_meta('_ws_ga_cid');
        $ga_sid = (int) $order->get_meta('_ws_ga_sid');
        $ga_sno = (int) $order->get_meta('_ws_ga_sno');

        $billing_city = null;
        $billing_st = null;
        $billing_zp = null;
        $billing_ctry = strtolower(trim((string) $order->get_billing_country()));

        $em_h = null;
        $ph_h = null;
        $fn_h = null;
        $ln_h = null;
        $ct_h = null;
        $st_h = null;
        $zp_h = null;
        $country_h = null;
        $external_id_h = null;

        if ($this->hasher) {
            $billing_email = $this->hasher->norm_email($order->get_billing_email());
            $billing_phone = $this->hasher->norm_phone($order->get_billing_phone());
            $billing_fn = $this->hasher->norm_name($order->get_billing_first_name());
            $billing_ln = $this->hasher->norm_name($order->get_billing_last_name());
            $billing_city = $this->hasher->norm_str($order->get_billing_city());
            $billing_st = $this->hasher->norm_str($order->get_billing_state());
            $billing_zp = $this->hasher->norm_zip($order->get_billing_postcode());
            $billing_street = $this->hasher->norm_street($order->get_billing_address_1() . ' ' . $order->get_billing_address_2());

            $em_h = $this->hasher->h256_or_null($billing_email);
            $ph_h = $this->hasher->h256_or_null($billing_phone);
            $fn_h = $this->hasher->h256_or_null($billing_fn);
            $ln_h = $this->hasher->h256_or_null($billing_ln);
            $ct_h = $this->hasher->h256_or_null($billing_city);
            $st_h = $this->hasher->h256_or_null($billing_st);
            $zp_h = $this->hasher->h256_or_null($billing_zp);
            $street_h = $this->hasher->h256_or_null($billing_street);
            $country_h = $this->hasher->h256_or_null($billing_ctry);
            $external_id_h = $sid ? $this->hasher->h256_or_null($sid) : null;
        }

        $client_ip = $this->get_client_ip();
        $client_ua = isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '';

        // Retrieve the GA cookie header saved at checkout for sGTM-style forwarding.
        $raw_cookie_header = (string) $order->get_meta('_ws_ga_cookie_header');
        // Prefer browser IP saved at checkout; fall back to current request IP.
        $checkout_ip = (string) $order->get_meta('_ws_client_ip') ?: $client_ip;

        $user_id = '';
        if (function_exists('is_user_logged_in') && is_user_logged_in()) {
            $uid = get_current_user_id();
            if ($uid) {
                $user_id = 'uid_' . intval($uid);
            }
        }
        if (!$user_id && !empty($sid)) {
            $user_id = 'sid_' . substr($sid, 0, 36);
        }

        return [
            'items' => $items,
            'currency' => $currency,
            'value' => $value,
            'event_id' => $event_id,
            'sid' => $sid,
            'fbp' => $fbp,
            'fbc' => $fbc,
            'gclid' => $gclid,
            'gbraid' => $gbraid,
            'wbraid' => $wbraid,
            'ga_cid' => $ga_cid,
            'ga_sid' => $ga_sid,
            'ga_sno' => $ga_sno,
            'billing_city' => $billing_city,
            'billing_st' => $billing_st,
            'billing_zp' => $billing_zp,
            'billing_ctry' => $billing_ctry,
            'em_h' => $em_h,
            'ph_h' => $ph_h,
            'fn_h' => $fn_h,
            'ln_h' => $ln_h,
            'ct_h' => $ct_h,
            'st_h' => $st_h,
            'zp_h' => $zp_h,
            'street_h' => $street_h ?? null,
            'country_h' => $country_h,
            'external_id_h' => $external_id_h,
            'client_ip' => $client_ip,
            'client_ua' => $client_ua,
            'user_id' => $user_id,
            'page_url' => (string) $order->get_meta('_ws_page_url'),
            'page_referrer' => (string) $order->get_meta('_ws_page_referrer'),
            'user_agent' => (string) $order->get_meta('_ws_user_agent') ?: $client_ua,
            'raw_cookie_header' => $raw_cookie_header,
            'checkout_ip' => $checkout_ip,
        ];
    }

    /**
     * Send GA4 purchase event.
     */
    private function send_ga4_purchase($order, $order_id, $items, $currency, $value, $event_id, $ga_cid, $ga_sid, $ga_sno, $gclid, $gbraid, $wbraid, $user_id, $em_h, $ph_h, $fn_h, $ln_h, $street_h, $billing_city, $billing_st, $billing_zp, $billing_ctry, $order_stats_ok, $page_url = '', $page_referrer = '', $user_agent = '', $raw_cookie_header = '', $checkout_ip = '')
    {
        $event_name = TrackSharp_Features::ga4_purchase_event_name();

        // Prevent duplicate GA4 sends (including cron fallback runs).
        if (get_post_meta($order_id, '_ws_ga4_purchase_sent', true) === '1' || get_post_meta($order_id, '_ws_purchase_sst_sent', true) === '1') {
            return;
        }
        $locked = add_post_meta($order_id, '_ws_ga4_purchase_sent', '1', true);
        if (!$locked) {
            return;
        }

        // Pro feature gate: Enhanced Conversions requires Pro
        $ga_user_data = [];
        if (TrackSharp_Features::is_enhanced_conversions_available()) {
            $ga_user_address = array_filter([
                'sha256_first_name' => $fn_h ?: null,
                'sha256_last_name' => $ln_h ?: null,
                'sha256_street' => $street_h ?: null,
                'city' => $billing_city ?: null,
                'region' => $billing_st ?: null,
                'postal_code' => $billing_zp ?: null,
                'country' => strtoupper($billing_ctry) ?: null,
            ]);

            $ga_user_data = array_filter([
                'sha256_email_address' => $em_h ?: null,
                'sha256_phone_number' => $ph_h ?: null,
                'address' => $ga_user_address ?: null,
            ]);
        }

        $ga4_payload = [
            'client_id' => $ga_cid ?: (time() . '.' . wp_rand(1000000000, 2147483647)),
            'user_id' => $ga_user_data ? $user_id : null,
            'user_data' => $ga_user_data ?: null,
            'non_personalized_ads' => false,
            'events' => [
                [
                    'name' => $event_name,
                    'timestamp_micros' => (int) (microtime(true) * 1e6),
                    'params' => [
                        'transaction_id' => (string) $order_id,
                        'currency' => $currency,
                        'value' => $value,
                        'items' => array_map(function ($i) {
                            return [
                                'item_id' => (string) $i['item_id'],
                                'item_name' => $i['item_name'],
                                'price' => (float) $i['price'],
                                'quantity' => (int) $i['quantity'],
                            ];
                        }, $items),
                        'coupon' => (string) ($order->get_coupon_codes() ? implode(',', $order->get_coupon_codes()) : ''),
                        'shipping' => (float) $order->get_shipping_total(),
                        'tax' => (float) $order->get_total_tax(),
                        'event_id' => $event_id,
                        'engagement_time_msec' => 1,
                        'session_id' => $ga_sid ?: null,
                        'session_number' => $ga_sno ?: null,
                        'page_location' => $page_url ?: null,
                        'page_referrer' => $page_referrer ?: null,
                        'user_agent' => $user_agent ?: null,
                        'ua' => $user_agent ?: null,
                    ] + (TrackSharp_Features::is_google_ads_available() ? array_filter([
                            'gclid' => $gclid ?: null,
                            'gbraid' => $gbraid ?: null,
                            'wbraid' => $wbraid ?: null,
                        ]) : [])
                ]
            ]
        ];

        // Attach sGTM-style HTTP context: forward the original browser cookies, IP and UA as
        // HTTP headers on the MP call. GA4 treats these headers as authoritative session proof,
        // which is the mechanism sGTM tools use to achieve proper ads attribution.
        $http_context = array_filter([
            'cookie'     => $raw_cookie_header ?: null,
            'client_ip'  => $checkout_ip ?: null,
            'user_agent' => $user_agent ?: null,
        ]);
        if ($http_context) {
            $ga4_payload['_http_context'] = $http_context;
        }

        $res = $this->ga4->send($ga4_payload);

        // Read marketing consent from order meta (captured during checkout when browser session was active)
        // Orders with marketing=denied are "rescued" - GTM would have missed them
        $marketing_granted = (bool) $order->get_meta('_ws_marketing_consent', true);

        $this->events_log->log_event([
            'channel' => 'ga4',
            'source' => 'server',
            'event_name' => $event_name,
            'event_id' => $event_id,
            'txid' => (string) $order_id,
            'ok' => !empty($res['ok']),
            'http_status' => isset($res['status']) ? (int) $res['status'] : null,
            'consent' => $marketing_granted ? 'granted' : 'denied',
        ]);
    }

    /**
     * Send Meta purchase event.
     */
    private function send_meta_purchase($order_id, $items, $currency, $value, $event_id, $em_h, $ph_h, $fn_h, $ln_h, $ct_h, $st_h, $zp_h, $country_h, $external_id_h, $fbp, $fbc, $client_ip, $client_ua)
    {
        // Guard: Meta requires Pro (hasher and meta classes)
        if (!$this->meta || !$this->hasher) {
            return;
        }

        if (get_post_meta($order_id, '_ws_meta_purchase_sent', true) === '1') {
            return;
        }
        $locked = add_post_meta($order_id, '_ws_meta_purchase_sent', '1', true);
        if (!$locked) {
            return;
        }

        $ud = array_filter([
            'em' => $em_h,
            'ph' => $ph_h,
            'fn' => $fn_h,
            'ln' => $ln_h,
            'ct' => $ct_h,
            'st' => $st_h,
            'zp' => $zp_h,
            'country' => $country_h,
            'external_id' => $external_id_h,
            'fbp' => $fbp ?: null,
            'fbc' => $fbc ?: null,
            'client_user_agent' => $client_ua,
            'client_ip_address' => $client_ip,
        ]);

        $ud = $this->hasher->validate_user_data_for_meta($ud)['clean'];

        $meta_res = $this->meta->send([
            'event_name' => 'Purchase',
            'event_id' => $event_id,
            'event_time' => time(),
            'action_source' => 'website',
            'user_data' => $ud,
            'custom_data' => [
                'currency' => $currency,
                'value' => $value,
                'contents' => array_map(function ($i) {
                    return [
                        'id' => sanitize_text_field($i['item_id']),
                        'quantity' => (int) $i['quantity'],
                        'item_price' => (float) $i['price'],
                    ];
                }, $items),
                'content_type' => 'product',
                'order_id' => (string) $order_id,
            ],
        ]);

        $this->events_log->log_event([
            'channel' => 'meta',
            'source' => 'server',
            'event_name' => 'Purchase',
            'event_id' => $event_id,
            'txid' => (string) $order_id,
            'ok' => !empty($meta_res['ok']),
            'http_status' => isset($meta_res['status']) ? (int) $meta_res['status'] : null,
            'consent' => $this->consent->server_consent_granted_for('meta') ? 'granted' : 'denied',
        ]);
    }

    /**
     * Enqueue browser-side purchase breadcrumb on the order received page.
     *
     * This is used for audit/deduplication visibility and should be added via
     * `wp_add_inline_script()` (not printed with raw script tags).
     *
     * @return void
     */
    public function enqueue_order_received_purchase_breadcrumb()
    {
        if (is_admin() || !function_exists('is_order_received_page') || !is_order_received_page()) {
            return;
        }

        if (!function_exists('wc_get_order')) {
            return;
        }

        $order_id = 0;
        if (function_exists('get_query_var')) {
            $order_id = (int) get_query_var('order-received');
        }
        if (!$order_id) {
            global $wp;
            if (isset($wp) && is_object($wp) && !empty($wp->query_vars['order-received'])) {
                $order_id = (int) $wp->query_vars['order-received'];
            }
        }
        if (!$order_id) {
            return;
        }

        if (!wp_script_is('tracksharp-tracker', 'registered')) {
            return;
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return;
        }

        $items = [];
        $include_tax_in_items = !TrackSharp_Features::is_revenue_excl_tax();
        foreach ($order->get_items() as $item) {
            $product = $item->get_product();
            $items[] = [
                'item_id' => $product ? $product->get_id() : $item->get_product_id(),
                'item_name' => $item->get_name(),
                'price' => (float) $order->get_item_total($item, $include_tax_in_items),
                'quantity' => (int) $item->get_quantity(),
            ];
        }

        $currency = $order->get_currency() ?: (function_exists('get_woocommerce_currency') ? get_woocommerce_currency() : 'EUR');
        $value = $this->get_order_value($order);
        $event_id = 'purchase_' . $order_id;
        $txn_id = (string) $order_id;

        $purchase_data = [
            'order_id' => $order_id,
            'event_id' => $event_id,
            'currency' => $currency,
            'value' => $value,
            'transaction_id' => $txn_id,
            'items' => $items,
        ];

        $inline_script = sprintf(
            '(function(){var d=%s,k="tracksharp_purchase_"+d.order_id;function store(){try{return localStorage}catch(_){try{return sessionStorage}catch(__){return null}}}var st=store();try{if(st&&st.getItem(k))return}catch(_){}function s(){if(window.TrackSharpTracker&&window.TrackSharpTracker.postEvent){try{window.TrackSharpTracker.postEvent("purchase",{__event_id:d.event_id,__skip_server:true,ecom:{currency:d.currency,value:d.value,transaction_id:d.transaction_id,items:d.items}});if(st)st.setItem(k,String(Date.now()))}catch(_){}return true}return false}if(s())return;var n=0,t=setInterval(function(){if(s()||++n>20)clearInterval(t)},100)})();',
            wp_json_encode($purchase_data)
        );

        wp_enqueue_script('tracksharp-tracker');
        wp_add_inline_script('tracksharp-tracker', $inline_script, 'after');
    }

    /**
     * Flag add-to-cart cookie.
     *
     * @param string $cart_item_key Cart item key.
     * @param int    $product_id    Product ID.
     * @param int    $quantity      Quantity.
     * @param int    $variation_id  Variation ID.
     * @param array  $variation     Variation data.
     * @param array  $cart_item_data Cart item data.
     * @return void
     */
    public function flag_atc_cookie($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data)
    {
        if ((function_exists('wp_doing_ajax') && wp_doing_ajax()) || (defined('REST_REQUEST') && REST_REQUEST)) {
            return;
        }

        $pid = $variation_id ? (int) $variation_id : (int) $product_id;
        $this->set_ws_atc_cookie($pid, (int) $quantity);
    }

    /**
     * Set add-to-cart cookie.
     *
     * @param int $pid Product ID.
     * @param int $qty Quantity.
     * @return void
     */
    private function set_ws_atc_cookie($pid, $qty)
    {
        if (!$pid || headers_sent()) {
            return;
        }

        if (!$this->consent->wp_consent_has(apply_filters('tracksharp_atc_cookie_category', 'marketing'))) {
            return;
        }

        $payload = wp_json_encode(['pid' => $pid, 'qty' => max(1, $qty), 'ts' => time()]);
        $opts = [
            'expires' => time() + 120,
            'path' => defined('COOKIEPATH') ? COOKIEPATH : '/',
            'domain' => defined('COOKIE_DOMAIN') ? COOKIE_DOMAIN : '',
            'secure' => is_ssl(),
            'httponly' => false,
            'samesite' => 'Lax',
        ];
        setcookie('ws_atc', $payload, $opts);
    }

    /**
     * Get client IP address.
     *
     * @return string Client IP.
     */
    private function get_client_ip()
    {
        foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'] as $k) {
            if (!empty($_SERVER[$k])) {
                $iplist = explode(',', sanitize_text_field(wp_unslash($_SERVER[$k])));
                return trim($iplist[0]);
            }
        }
        return '';
    }
}
