<?php
/**
 * Event Builder — Dynamic CAPI + GA4 payload construction.
 *
 * Builds complete payloads for all event types:
 * PageView, ViewContent, Search, AddToCart, InitiateCheckout,
 * AddPaymentInfo, Purchase, Negative events.
 *
 * @package CapiFlow
 */

namespace CapiFlow\Core;

defined( 'ABSPATH' ) || exit;

class Event_Builder {

    // ═══════════════════════════════════════════════════════════════════
    // CENTRAL RESOLVERS — v1.1.0
    // Single source of truth for content_id, content_type, item_price.
    // All event methods MUST use these — never inline SKU/ID logic.
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Resolve content_id for a product based on the configured strategy.
     *
     * Strategy options (capiflow_content_id_strategy):
     *   'sku'        — variation/product SKU, fallback to product ID (default).
     *   'parent_sku' — parent product SKU for variations, fallback to product ID.
     *   'product_id' — numeric WooCommerce post ID always.
     *
     * Filterable via 'capiflow_content_id' for custom catalog integrations.
     *
     * @param \WC_Product $product WooCommerce product (simple, variable, or variation).
     * @return string Content ID string.
     */
    public static function resolve_content_id( \WC_Product $product ): string {
        $strategy = get_option( 'capiflow_content_id_strategy', 'sku' );

        if ( 'product_id' === $strategy ) {
            $id = (string) $product->get_id();
        } elseif ( 'parent_sku' === $strategy ) {
            // For variations: use the parent product's SKU.
            if ( $product instanceof \WC_Product_Variation ) {
                $parent = wc_get_product( $product->get_parent_id() );
                if ( $parent ) {
                    $parent_sku = $parent->get_sku();
                    $id = ! empty( $parent_sku ) ? $parent_sku : (string) $parent->get_id();
                } else {
                    $id = (string) $product->get_parent_id();
                }
            } else {
                // Simple / variable: use own SKU.
                $sku = $product->get_sku();
                $id  = ! empty( $sku ) ? $sku : (string) $product->get_id();
            }
        } else {
            // Default 'sku' strategy: use variation/product SKU.
            $sku = $product->get_sku();
            $id  = ! empty( $sku ) ? $sku : (string) $product->get_id();
        }

        /**
         * Filter the resolved content_id for custom catalog integrations.
         *
         * @param string      $id       Resolved content ID.
         * @param \WC_Product $product  Product object.
         * @param string      $strategy Active strategy key.
         */
        return (string) apply_filters( 'capiflow_content_id', $id, $product, $strategy );
    }

    /**
     * Resolve content_type based on the configured ID strategy and product context.
     *
     * Meta rule:
     *   content_ids hold individual item IDs → content_type = 'product'
     *   content_ids hold group/parent IDs    → content_type = 'product_group'
     *
     * When $product is null (multi-item order/cart), type is determined by strategy alone.
     * When $product is provided (single-item context), variation check refines the result.
     *
     * @param \WC_Product|null $product Optional. Product for single-item context.
     * @return string 'product' or 'product_group'.
     */
    public static function resolve_content_type( ?\WC_Product $product = null ): string {
        $strategy = get_option( 'capiflow_content_id_strategy', 'sku' );

        if ( 'parent_sku' === $strategy ) {
            if ( null === $product ) {
                // Multi-item: parent IDs → always product_group.
                return 'product_group';
            }
            // Single-item: variation of a variable product uses parent ID → product_group.
            if ( $product instanceof \WC_Product_Variation ) {
                return 'product_group';
            }
            // Simple or variable parent itself → product.
            return 'product';
        }

        // 'sku' and 'product_id' strategies: individual item IDs → product.
        return 'product';
    }

    /**
     * Resolve item_price ensuring tax parity across cart and order contexts.
     *
     * WC_Order_Item_Product::get_total() always returns excl. tax.
     * WC_Product::get_price() returns incl. tax when "Prices entered with tax" = YES.
     * This method normalises both to match the store's pricing display.
     *
     * @param \WC_Order_Item_Product|\WC_Product $product_or_item Order item or product.
     * @return float Per-unit price, tax-inclusive when store prices are tax-inclusive.
     */
    public static function resolve_item_price( $product_or_item ): float {
        $prices_include_tax = wc_prices_include_tax();

        if ( $product_or_item instanceof \WC_Order_Item_Product ) {
            $qty      = max( 1, (int) $product_or_item->get_quantity() );
            $excl_tax = (float) $product_or_item->get_total() / $qty;

            if ( $prices_include_tax ) {
                // Catalog feed prices include tax → add tax back for parity.
                $incl_tax = $excl_tax + ( (float) $product_or_item->get_total_tax() / $qty );
                return round( $incl_tax, 2 );
            }
            return round( $excl_tax, 2 );
        }

        if ( $product_or_item instanceof \WC_Product ) {
            // get_price() already follows the WC tax setting.
            return (float) $product_or_item->get_price();
        }

        return 0.0;
    }

    /**
     * Build a browser event payload (PageView, ViewContent, Search, AddToCart).
     *
     * Used by the REST proxy for fire-and-forget events.
     *
     * @param array $data Client-sent tracking data.
     * @return array CAPI-formatted event payload.
     */
    public function build_browser_event( $data ) {
        $event_name = sanitize_text_field( $data['event_name'] ?? '' );
        $event_id   = sanitize_text_field( $data['event_id'] ?? '' );

        if ( empty( $event_name ) || empty( $event_id ) ) {
            return array();
        }

        $payload = array(
            'event_name'       => $event_name,
            'event_time'       => time(),
            'event_id'         => $event_id,
            'action_source'    => 'website',
            'event_source_url' => esc_url_raw( $data['source_url'] ?? '' ),
            'referrer_url'     => $this->get_referrer_url( $data ),
            'user_data'        => $this->build_browser_user_data( $data ),
            'custom_data'      => $this->sanitize_custom_data( $data['custom_data'] ?? array() ),
        );

        return $payload;
    }

    /**
     * Build Purchase event payload from WC_Order.
     *
     * @param \WC_Order $order    WooCommerce order.
     * @param string    $event_id Pre-generated event ID.
     * @param array     $context  Customer context (IP, UA, cookies).
     * @return array CAPI payload.
     */
    public function build_purchase_event( $order, $event_id, $context = array() ) {
        $contents    = array();
        $content_ids = array();
        $total_qty   = 0;

        foreach ( $order->get_items() as $item ) {
            if ( ! $item instanceof \WC_Order_Item_Product ) {
                continue;
            }
            $product = $item->get_product();
            if ( ! $product ) {
                continue;
            }

            $id = self::resolve_content_id( $product );

            $content_ids[] = $id;
            $total_qty    += $item->get_quantity();

            $content_item = array(
                'id'                => $id,
                'quantity'          => $item->get_quantity(),
                'item_price'        => self::resolve_item_price( $item ),
                'delivery_category' => $this->get_delivery_category( $order ),
            );

            $contents[] = $content_item;
        }

        // Profit VBB calculation.
        $revenue = (float) $order->get_total();
        $cogs    = $this->calculate_order_cogs( $order );
        $profit  = $revenue - $cogs;

        // First content item for content_name / content_category.
        $first_item    = $order->get_items() ? array_values( $order->get_items() )[0] : null;
        $content_name  = $first_item ? $first_item->get_name() : '';
        $content_cat   = '';

        if ( $first_item ) {
            $product = $first_item->get_product();
            if ( $product ) {
                // Fix: use parent ID for variations — variations have no category assignment.
                $cat_post_id = ( $product instanceof \WC_Product_Variation )
                    ? $product->get_parent_id()
                    : $product->get_id();
                $cats = wp_get_post_terms( $cat_post_id, 'product_cat', array( 'fields' => 'names' ) );
                $content_cat = ( is_array( $cats ) && ! is_wp_error( $cats ) && ! empty( $cats ) ) ? $cats[0] : '';
            }
        }

        // multi-item → concatenate names.
        $item_count = count( $order->get_items() );
        if ( $item_count > 1 ) {
            $names = array();
            foreach ( $order->get_items() as $item ) {
                $names[] = $item->get_name();
            }
            $content_name = implode( ', ', array_slice( $names, 0, 5 ) );
            if ( $item_count > 5 ) {
                $content_name .= ' +' . ( $item_count - 5 ) . ' more';
            }
        }

        // Value: ALWAYS send order total as `value` (must match browser pixel for clean dedup).
        $payload = array(
            'event_name'       => 'Purchase',
            'event_time'       => time(),
            'event_id'         => $event_id,
            'action_source'    => 'website',
            'event_source_url' => $order->get_checkout_order_received_url(),
            'referrer_url'     => $this->get_referrer_url( array(), $order ),
            'user_data'        => Data_Normalizer::build_user_data( $order, $context ),
            'custom_data'      => array(
                'value'                 => round( $revenue, 2 ),
                'currency'              => $order->get_currency(),
                'content_type'          => self::resolve_content_type(),
                'content_name'          => $content_name,
                'content_category'      => $content_cat,
                'content_ids'           => $content_ids,
                'num_items'             => $total_qty,
                'order_id'              => $order->get_order_number(),
                'contents'              => $contents,
                'customer_segmentation' => $this->detect_customer_segment( $order ),
            ),
        );

        // Profit VBB: only include predicted_ltv when COGS data is actually populated.
        // Without COGS, profit = revenue which Meta flags as invalid for Value-Based Bidding.
        if ( $cogs > 0 ) {
            $payload['custom_data']['predicted_ltv'] = round( max( 0, $profit ), 2 );
        }

        return $payload;
    }

    /**
     * Build InitiateCheckout event payload.
     *
     * @param string $event_id  Event ID.
     * @param array  $context   Customer context.
     * @return array CAPI payload.
     */
    public function build_initiate_checkout( $event_id, $context = array() ) {
        $cart = WC()->cart;
        if ( ! $cart ) {
            return array();
        }

        $cart_items = array();
        $cart_ids   = array();

        foreach ( $cart->get_cart() as $item ) {
            $product = isset( $item['data'] ) ? $item['data'] : null;
            if ( ! $product instanceof \WC_Product ) {
                continue;
            }

            $id = self::resolve_content_id( $product );

            $cart_ids[]   = $id;
            $cart_items[] = array(
                'id'         => $id,
                'quantity'   => $item['quantity'],
                'item_price' => self::resolve_item_price( $product ),
            );
        }

        return array(
            'event_name'    => 'InitiateCheckout',
            'event_time'    => time(),
            'event_id'      => $event_id,
            'action_source' => 'website',
            'referrer_url'  => $this->get_referrer_url(),
            'user_data'     => Data_Normalizer::build_user_data( null, $context ),
            'custom_data'   => array(
                'value'        => (float) $cart->get_cart_contents_total(),
                'currency'     => get_woocommerce_currency(),
                'content_type' => self::resolve_content_type(),
                'content_ids'  => $cart_ids,
                'contents'     => $cart_items,
                'num_items'    => $cart->get_cart_contents_count(),
            ),
        );
    }

    /**
     * Build AddPaymentInfo event payload.
     *
     * @param string $event_id Event ID.
     * @param array  $context  Customer context.
     * @return array CAPI payload.
     */
    public function build_add_payment_info( $event_id, $context = array() ) {
        $cart = WC()->cart;
        if ( ! $cart ) {
            return array();
        }

        $cart_items = array();
        $cart_ids   = array();

        foreach ( $cart->get_cart() as $item ) {
            $product = isset( $item['data'] ) ? $item['data'] : null;
            if ( ! $product instanceof \WC_Product ) {
                continue;
            }

            $id = self::resolve_content_id( $product );

            $cart_ids[]   = $id;
            $cart_items[] = array(
                'id'         => $id,
                'quantity'   => $item['quantity'],
                'item_price' => self::resolve_item_price( $product ),
            );
        }

        return array(
            'event_name'    => 'AddPaymentInfo',
            'event_time'    => time(),
            'event_id'      => $event_id,
            'action_source' => 'website',
            'referrer_url'  => $this->get_referrer_url(),
            'user_data'     => Data_Normalizer::build_user_data( null, $context ),
            'custom_data'   => array(
                'value'        => (float) $cart->get_cart_contents_total(),
                'currency'     => get_woocommerce_currency(),
                'content_type' => self::resolve_content_type(),
                'content_ids'  => $cart_ids,
                'contents'     => $cart_items,
                'num_items'    => $cart->get_cart_contents_count(),
            ),
        );
    }

    /**
     * Build negative event payload (Order_Cancelled, Order_Refunded, etc.).
     *
     * @param \WC_Order $order      WooCommerce order.
     * @param string    $event_name Negative event name.
     * @param string    $event_id   Event ID.
     * @return array CAPI payload.
     */
    public function build_negative_event( $order, $event_name, $event_id ) {
        $context = $this->get_stored_context( $order );

        // Smart action_source: if order has customer context (IP/UA/fbp),
        // it originated from the website. Meta best practice: reflect the ORIGIN.
        $has_context   = ! empty( $context['client_ip'] ) || ! empty( $context['user_agent'] ) || ! empty( $context['fbp'] );
        $action_source = $has_context ? 'website' : 'system_generated';

        // Extract line items for catalog matching + audience exclusion.
        $line_items = $this->extract_order_line_items( $order );

        return array(
            'event_name'       => $event_name,
            'event_time'       => time(),
            'event_id'         => $event_id,
            'action_source'    => $action_source,
            'event_source_url' => $order->get_checkout_order_received_url(),
            'referrer_url'     => $this->get_referrer_url( array(), $order ),
            'user_data'        => Data_Normalizer::build_user_data( $order, $context ),
            'custom_data'      => array(
                'order_id'         => $order->get_order_number(),
                'value'            => 0,
                'currency'         => $order->get_currency(),
                'original_value'   => (float) $order->get_total(),
                'content_type'     => self::resolve_content_type(),
                'content_ids'      => $line_items['content_ids'],
                'contents'         => $line_items['contents'],
                'num_items'        => $line_items['total_qty'],
                'content_name'     => $line_items['content_name'],
                'content_category' => $line_items['content_category'],
            ),
        );
    }

    /**
     * Build Purchase with value=0 (Strategy 2 for negative signals).
     *
     * @deprecated No longer used — $0 Purchase events mislead Meta's ML algorithm.
     *             Kept for backward compatibility with third-party extensions.
     *
     * @param \WC_Order $order    Order.
     * @param string    $event_id New event ID.
     * @return array CAPI payload.
     */
    public function build_purchase_zero_value( $order, $event_id ) {
        $context = $this->get_stored_context( $order );

        // Smart action_source: same logic as negative events.
        $has_context   = ! empty( $context['client_ip'] ) || ! empty( $context['user_agent'] ) || ! empty( $context['fbp'] );
        $action_source = $has_context ? 'website' : 'system_generated';

        // Extract line items for catalog matching (helps Meta identify which purchase is zeroed).
        $line_items = $this->extract_order_line_items( $order );

        return array(
            'event_name'       => 'Purchase',
            'event_time'       => time(),
            'event_id'         => $event_id,
            'action_source'    => $action_source,
            'event_source_url' => $order->get_checkout_order_received_url(),
            'user_data'        => Data_Normalizer::build_user_data( $order, $context ),
            'custom_data'      => array(
                'order_id'         => $order->get_order_number(),
                'value'            => 0,
                'currency'         => $order->get_currency(),
                'content_type'     => self::resolve_content_type(),
                'content_ids'      => $line_items['content_ids'],
                'contents'         => $line_items['contents'],
                'num_items'        => $line_items['total_qty'],
                'content_name'     => $line_items['content_name'],
                'content_category' => $line_items['content_category'],
            ),
        );
    }

    /**
     * Build user_data from browser context (REST proxy).
     *
     * @param array $data Client data.
     * @return array user_data.
     */
    private function build_browser_user_data( $data ) {
        $user_data = array();

        // IP — from server (NEVER hashed).
        $user_data['client_ip_address'] = Data_Normalizer::get_real_client_ip();
        $user_data['client_user_agent'] = Data_Normalizer::get_user_agent();

        // Cookies (NEVER hashed).
        $cookies = Cookie_Manager::get_cookie_data();
        if ( ! empty( $cookies['fbc'] ) ) {
            $user_data['fbc'] = $cookies['fbc'];
        }
        if ( ! empty( $cookies['fbp'] ) ) {
            $user_data['fbp'] = $cookies['fbp'];
        }

        // ── External ID — MUST match browser Advanced Matching format exactly ──
        // Browser sends raw 'wp_{id}' or cs_uid → SDK sha256s it.
        // Server must send sha256 of the SAME raw value.
        //
        // For logged-in users: serverUserData provides pre-hashed external_id
        // (generated during PHP page render where is_user_logged_in() is TRUE).
        // This avoids the REST API auth scope issue where is_user_logged_in() = false.

        // ── Server-side PII (pre-hashed, generated during PHP page render) ──
        // Because this method runs inside REST API context, is_user_logged_in()
        // returns FALSE (no nonce = no cookie auth). To solve this, PHP embeds
        // pre-hashed PII into capiflowConfig.serverUserData during the page render
        // (where auth IS available). JS passes it to us via server_user_data.
        $server_user_data = isset( $data['server_user_data'] ) && is_array( $data['server_user_data'] )
            ? $data['server_user_data']
            : array();

        // Allowed keys — hashed PII + external_id. Never merge raw IP/UA/cookies from client.
        $allowed_pii_keys = array( 'external_id', 'em', 'ph', 'fn', 'ln', 'ct', 'st', 'zp', 'country' );
        foreach ( $allowed_pii_keys as $key ) {
            if ( ! empty( $server_user_data[ $key ] ) && ! isset( $user_data[ $key ] ) ) {
                $user_data[ $key ] = $server_user_data[ $key ];
            }
        }

        // ── Fallback: guest external_id from cs_uid cookie ──
        // Only used when serverUserData didn't provide external_id (guest users).
        if ( ! isset( $user_data['external_id'] ) ) {
            $cs_uid = ! empty( $_COOKIE['cs_uid'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['cs_uid'] ) ) : '';
            if ( ! empty( $cs_uid ) ) {
                $user_data['external_id'] = array( hash( 'sha256', $cs_uid ) );
            }
        }

        // NOTE: is_user_logged_in() is always FALSE in REST proxy context (no nonce = no cookie auth).
        // Logged-in PII is handled via serverUserData (pre-hashed during PHP page render).

        return $user_data;
    }

    /**
     * Extract order line items for content_ids / contents arrays.
     *
     * Shared by build_purchase_event, build_negative_event, and build_purchase_zero_value.
     * Uses variation-level IDs/SKUs for proper catalog matching.
     *
     * @param \WC_Order $order Order.
     * @return array { content_ids: string[], contents: array[], total_qty: int, content_name: string, content_category: string }
     */
    private function extract_order_line_items( $order ) {
        $content_ids = array();
        $contents    = array();
        $total_qty   = 0;

        foreach ( $order->get_items() as $item ) {
            if ( ! $item instanceof \WC_Order_Item_Product ) {
                continue;
            }
            $product = $item->get_product();
            if ( ! $product ) {
                continue;
            }

            $id = self::resolve_content_id( $product );

            $content_ids[] = $id;
            $total_qty    += $item->get_quantity();

            $contents[] = array(
                'id'         => $id,
                'quantity'   => $item->get_quantity(),
                'item_price' => self::resolve_item_price( $item ),
            );
        }

        // Content name / category from first item.
        $items        = array_values( $order->get_items() );
        $content_name = '';
        $content_cat  = '';

        if ( ! empty( $items ) ) {
            $content_name = $items[0]->get_name();
            $first_product = $items[0]->get_product();
            if ( $first_product ) {
                // Fix: use parent ID for variations — variations have no category assignment.
                $cat_post_id = ( $first_product instanceof \WC_Product_Variation )
                    ? $first_product->get_parent_id()
                    : $first_product->get_id();
                $cats = wp_get_post_terms( $cat_post_id, 'product_cat', array( 'fields' => 'names' ) );
                $content_cat = ( is_array( $cats ) && ! is_wp_error( $cats ) && ! empty( $cats ) ) ? $cats[0] : '';
            }

            // Multi-item: concatenate names (max 5).
            $item_count = count( $items );
            if ( $item_count > 1 ) {
                $names = array();
                foreach ( $items as $item ) {
                    $names[] = $item->get_name();
                }
                $content_name = implode( ', ', array_slice( $names, 0, 5 ) );
                if ( $item_count > 5 ) {
                    $content_name .= ' +' . ( $item_count - 5 ) . ' more';
                }
            }
        }

        return array(
            'content_ids'      => $content_ids,
            'contents'         => $contents,
            'total_qty'        => $total_qty,
            'content_name'     => $content_name,
            'content_category' => $content_cat,
        );
    }

    /**
     * Get stored customer context from order meta.
     *
     * @param \WC_Order $order Order.
     * @return array Context data.
     */
    private function get_stored_context( $order ) {
        return array(
            'client_ip'   => $order->get_meta( '_capiflow_client_ip', true ) ?: '',
            'user_agent'  => $order->get_meta( '_capiflow_user_agent', true ) ?: '',
            'fbc'         => $order->get_meta( '_capiflow_fbc', true ) ?: '',
            'fbp'         => $order->get_meta( '_capiflow_fbp', true ) ?: '',
            'external_id' => $order->get_meta( '_capiflow_external_id', true ) ?: '',
        );
    }

    /**
     * Sanitize custom_data from browser.
     *
     * @param array $data Raw custom data.
     * @return array Sanitized data.
     */
    private function sanitize_custom_data( $data ) {
        if ( ! is_array( $data ) ) {
            return array();
        }

        $sanitized = array();
        $allowed   = array(
            'value', 'currency', 'content_ids', 'content_type',
            'content_name', 'content_category', 'contents',
            'num_items', 'search_string', 'order_id',
        );

        foreach ( $allowed as $key ) {
            if ( isset( $data[ $key ] ) ) {
                if ( is_array( $data[ $key ] ) ) {
                    $sanitized[ $key ] = $data[ $key ];
                } elseif ( is_numeric( $data[ $key ] ) ) {
                    $sanitized[ $key ] = $data[ $key ] + 0; // Cast to number.
                } else {
                    $sanitized[ $key ] = sanitize_text_field( $data[ $key ] );
                }
            }
        }

        return $sanitized;
    }

    /**
     * Get dynamic delivery_category based on WooCommerce shipping method.
     *
     * @param \WC_Order $order Order.
     * @return string 'home_delivery' | 'in_store'
     */
    private function get_delivery_category( $order ) {
        foreach ( $order->get_shipping_methods() as $method ) {
            if ( 'local_pickup' === $method->get_method_id() ) {
                return 'in_store';
            }
        }
        return 'home_delivery';
    }

    /**
     * Get HTTP referrer URL for the current request or from stored order context.
     *
     * Meta Docs: "The HTTP referrer header as observed by the page triggering
     * the Conversions API or Meta Pixel event. This is usually the preceding
     * page in the browser."
     *
     * @param array          $data  Client-sent data (browser events).
     * @param \WC_Order|null $order Order object (server events).
     * @return string Referrer URL or empty string.
     */
    private function get_referrer_url( array $data = array(), $order = null ) {
        // 1. Browser events: JS sends document.referrer.
        if ( ! empty( $data['referrer_url'] ) ) {
            return esc_url_raw( $data['referrer_url'] );
        }

        // 2. Order events: use stored referrer from Context_Capture.
        if ( $order instanceof \WC_Order ) {
            $stored = $order->get_meta( '_capiflow_referrer_url', true );
            if ( ! empty( $stored ) ) {
                return $stored;
            }
        }

        // 3. Live PHP request: HTTP_REFERER header (checkout/cart page loads).
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated
        $referer = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '';

        return $referer;
    }

    /**
     * Detect customer segment for Meta's customer_segmentation parameter.
     *
     * Meta Docs: Allows advertisers to specify the user segment that the user
     * performing the event belongs to. Accepted enum values:
     * - new_customer_to_business
     * - existing_customer_to_business
     *
     * Placed inside custom_data per Meta's official payload example.
     *
     * @param \WC_Order $order WooCommerce order.
     * @return string Meta customer_segmentation enum value.
     */
    private function detect_customer_segment( $order ) {
        $customer_id = $order->get_customer_id();

        // ── Registered user: check completed order count ──
        if ( $customer_id > 0 ) {
            $past_orders = wc_get_customer_order_count( $customer_id );
            // Current order is included in the count, so <= 1 means first purchase.
            return ( $past_orders <= 1 )
                ? 'new_customer_to_business'
                : 'existing_customer_to_business';
        }

        // ── Guest user: check by billing email ──
        $email = $order->get_billing_email();
        if ( ! empty( $email ) ) {
            $existing = wc_get_orders( array(
                'billing_email' => $email,
                'status'        => array( 'wc-completed', 'wc-processing' ),
                'limit'         => 1,
                'exclude'       => array( $order->get_id() ), // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude
                'return'        => 'ids',
            ) );

            if ( ! empty( $existing ) ) {
                return 'existing_customer_to_business';
            }
        }

        return 'new_customer_to_business';
    }

    /**
     * Calculate total COGS for an order.
     * Uses variation-level COGS first, falls back to parent product COGS.
     *
     * @param \WC_Order $order Order.
     * @return float Total COGS.
     */
    private function calculate_order_cogs( $order ) {
        $total_cogs = 0.0;

        foreach ( $order->get_items() as $item ) {
            $qty = $item->get_quantity();

            // Variation-level COGS first, parent COGS fallback.
            $product_id = $item->get_variation_id() ? $item->get_variation_id() : $item->get_product_id();
            $cogs       = (float) get_post_meta( $product_id, '_woo_capi_cogs', true );

            // Fallback to parent product COGS if variation has none.
            if ( 0.0 === $cogs && $item->get_variation_id() ) {
                $cogs = (float) get_post_meta( $item->get_product_id(), '_woo_capi_cogs', true );
            }

            $total_cogs += $cogs * $qty;
        }

        return $total_cogs;
    }

    /**
     * Get content_type for a product.
     *
     * @deprecated 1.1.0 Use self::resolve_content_type() instead.
     * @param \WC_Product $product Product.
     * @return string 'product' or 'product_group'.
     */
    public function get_content_type( $product ) {
        return self::resolve_content_type( $product );
    }

    /**
     * Get content_id for cart context (variation-aware).
     *
     * @deprecated 1.1.0 Use self::resolve_content_id() with a WC_Product object instead.
     * @param int $product_id   Product ID.
     * @param int $variation_id Variation ID (0 if simple).
     * @return string Content ID.
     */
    public function get_content_id( $product_id, $variation_id = 0 ) {
        $actual_id = $variation_id > 0 ? $variation_id : $product_id;
        $product   = wc_get_product( $actual_id );
        if ( $product ) {
            return self::resolve_content_id( $product );
        }
        return (string) $actual_id;
    }
}
