<?php
/**
 * CTX Feed Manager Integration Helper - Multi-Location Support
 *
 * Integrates with CTX Feed Manager (WebAppick Product Feed) to add 
 * WCMLIM location data to product feeds. Generates MULTIPLE feed items
 * per product - one for each location with stock.
 *
 * @package    Wcmlim
 * @subpackage Wcmlim/includes/helper
 * @since      1.0.0
 * 
 * OUTPUT FORMAT (Google Local Inventory Ads):
 * Each product generates multiple <item> entries:
 * - Same product ID across all items
 * - Different store_code, quantity, price per location
 * - location_name, location_stock, location_price per location
 */

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

/**
 * Class WCMLIM_CTX_Feed_Integration
 *
 * Handles integration with CTX Feed Manager for Google product feeds.
 * Expands products into multiple feed items based on WCMLIM locations.
 */
class WCMLIM_CTX_Feed_Integration {

    /**
     * Singleton instance.
     *
     * @var WCMLIM_CTX_Feed_Integration|null
     */
    private static $instance = null;

    /**
     * Whether the integration is enabled.
     *
     * @var bool
     */
    private $is_enabled;

    /**
     * Cache for product locations to avoid repeated queries.
     *
     * @var array
     */
    private $location_cache = array();

    /**
     * Current location context for each product during feed generation.
     *
     * @var array
     */
    private $current_location_context = array();

    /**
     * Flag to indicate if we're currently processing multi-location feed.
     *
     * @var bool
     */
    private $processing_multiloc = false;

    /**
     * Store feed config for later use.
     *
     * @var mixed
     */
    private $current_feed_config = null;

    /**
     * Track products with changed data for batch feed regeneration.
     *
     * @var array
     */
    private $changed_products = array();

    /**
     * Flag to prevent multiple regenerations in same request.
     *
     * @var bool
     */
    private $regeneration_scheduled = false;

    /**
     * Get singleton instance.
     *
     * @return WCMLIM_CTX_Feed_Integration
     */
    public static function get_instance() {
        if (null === self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor.
     */
    private function __construct() {
        $this->is_enabled = get_option('wcmlim_enable_ctx_feed') === 'on';
        
        if ($this->is_enabled && self::is_ctx_feed_active()) {
            $this->init_hooks();
        }
    }

    /**
     * Initialize hooks for CTX Feed integration.
     */
    private function init_hooks() {
        // === DROPDOWN ATTRIBUTES ===
        add_filter('woo_feed_product_attribute_dropdown', array($this, 'add_wcmlim_attributes_to_dropdown'), 10, 1);

        // === ATTRIBUTE VALUE FILTERS ===
        add_filter('woo_feed_get_wcmlim_store_code_attribute', array($this, 'get_store_code_value'), 10, 3);
        add_filter('woo_feed_get_wcmlim_location_name_attribute', array($this, 'get_location_name_value'), 10, 3);
        add_filter('woo_feed_get_wcmlim_location_stock_attribute', array($this, 'get_location_stock_value'), 10, 3);
        add_filter('woo_feed_get_wcmlim_location_price_attribute', array($this, 'get_location_price_value'), 10, 3);
        add_filter('woo_feed_get_wcmlim_all_locations_attribute', array($this, 'get_all_locations_value'), 10, 3);

        // === GENERIC ATTRIBUTE FILTER ===
        add_filter('woo_feed_get_attribute', array($this, 'filter_wcmlim_attribute_value'), 10, 4);

        // === PRODUCT LOOP HOOKS ===
        add_action('woo_feed_before_product_loop', array($this, 'before_product_loop'), 10, 3);
        add_action('woo_feed_after_product_loop', array($this, 'after_product_loop'), 10, 3);

        // === XML FEED BODY MODIFICATION - THE KEY TO MULTI-LOCATION ===
        // V5 filter
        add_filter('ctx_make_xml_feed_body', array($this, 'expand_xml_items_by_location'), 999, 3);
        
        // V3 post-generation hook - process XML file after it's saved
        add_action('after_woo_feed_generate_feed', array($this, 'post_process_xml_feed'), 10, 1);

        // === CACHE MANAGEMENT ===
        add_action('wcmlim_stock_updated', array($this, 'clear_ctx_feed_cache'), 10, 2);
        add_action('wcmlim_location_updated', array($this, 'clear_ctx_feed_cache'), 10, 1);

        // === AUTO-REGENERATION HOOKS ===
        // Hook into post meta updates for WCMLIM-specific meta keys
        add_action('updated_post_meta', array($this, 'on_wcmlim_meta_updated'), 10, 4);
        add_action('added_post_meta', array($this, 'on_wcmlim_meta_added'), 10, 4);
        
        // Hook into product save (after WCMLIM saves its data)
        add_action('woocommerce_process_product_meta', array($this, 'on_product_save'), 999, 1);
        add_action('woocommerce_save_product_variation', array($this, 'on_variation_save'), 999, 2);
        
        // Schedule batch regeneration at end of request
        add_action('shutdown', array($this, 'maybe_regenerate_feeds'));
    }

    /**
     * Check if CTX Feed Manager plugin is active.
     *
     * @return bool
     */
    public static function is_ctx_feed_active() {
        if (!function_exists('is_plugin_active')) {
            include_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        
        return is_plugin_active('webappick-product-feed-for-woocommerce/woo-feed.php') || 
               (is_multisite() && array_key_exists(
                   'webappick-product-feed-for-woocommerce/woo-feed.php',
                   get_site_option('active_sitewide_plugins', array())
               ));
    }

    /**
     * Add WCMLIM location attributes to CTX Feed dropdown.
     * 
     * @param array $attributes Existing attributes array.
     * @return array Modified attributes array with WCMLIM attributes.
     */
    public function add_wcmlim_attributes_to_dropdown($attributes) {
        $wcmlim_group = array(
            'optionGroup' => esc_html__('WCMLIM Location Attributes', 'wcmlim'),
            'options'     => array(
                'wcmlim_store_code'      => esc_html__('Store Code (Location)', 'wcmlim'),
                'wcmlim_location_name'   => esc_html__('Location Name', 'wcmlim'),
                'wcmlim_location_stock'  => esc_html__('Location Stock Quantity', 'wcmlim'),
                'wcmlim_location_price'  => esc_html__('Location Price', 'wcmlim'),
                'wcmlim_all_locations'   => esc_html__('All Locations (comma-separated)', 'wcmlim'),
            ),
        );

        // Insert after Primary Attributes (index 0) or at position 1
        array_splice($attributes, 1, 0, array($wcmlim_group));

        return $attributes;
    }

    /**
     * Before product loop - initialize context.
     *
     * @param array $productIds Product IDs.
     * @param array $feedRules  Feed rules.
     * @param mixed $config     Config object.
     */
    public function before_product_loop($productIds, $feedRules, $config) {
        $this->processing_multiloc = true;
        $this->current_feed_config = $feedRules;
        $this->current_location_context = array();
        
        // Pre-load location data for all products
        foreach ($productIds as $product_id) {
            $locations = $this->get_product_locations($product_id);
            if (!empty($locations)) {
                // Set first in-stock location as default context
                foreach ($locations as $location) {
                    if ($location['in_stock']) {
                        $this->current_location_context[$product_id] = $location;
                        break;
                    }
                }
                // Fallback to first location
                if (!isset($this->current_location_context[$product_id])) {
                    $this->current_location_context[$product_id] = $locations[0];
                }
            }
        }
    }

    /**
     * After product loop - cleanup.
     *
     * @param array $productIds Product IDs.
     * @param array $feedRules  Feed rules.
     * @param mixed $config     Config object.
     */
    public function after_product_loop($productIds, $feedRules, $config) {
        // Keep processing_multiloc true until XML body is processed
    }

    /**
     * MAIN FUNCTION: Expand XML items by location.
     * This takes the generated XML feed body and multiplies each <item>
     * into multiple items - one for each location.
     *
     * @param string $feed_body The XML feed body.
     * @param array  $data      Feed data array.
     * @param mixed  $config    Feed config object.
     * @return string Modified XML feed body with expanded location items.
     */
    public function expand_xml_items_by_location($feed_body, $data, $config) {
        // Check if this feed uses WCMLIM location attributes
        if (!$this->feed_uses_wcmlim_attributes($config)) {
            return $feed_body;
        }

        // Get the item wrapper tag from config or detect from XML
        $item_wrapper = isset($config->itemWrapper) ? $config->itemWrapper : 'item';
        
        // If the config wrapper doesn't exist in XML, try common wrappers
        if (strpos($feed_body, '<' . $item_wrapper . '>') === false) {
            $common_wrappers = array('item', 'product', 'entry', 'row');
            foreach ($common_wrappers as $wrapper) {
                if (strpos($feed_body, '<' . $wrapper . '>') !== false) {
                    $item_wrapper = $wrapper;
                    break;
                }
            }
        }
        
        // Parse and expand items
        $expanded_body = $this->process_xml_items($feed_body, $item_wrapper, $config);
        
        $this->processing_multiloc = false;
        $this->current_location_context = array();
        
        return $expanded_body;
    }

    /**
     * Check if feed configuration uses WCMLIM attributes.
     *
     * @param mixed $config Feed config.
     * @return bool True if WCMLIM attributes are used.
     */
    private function feed_uses_wcmlim_attributes($config) {
        if (!$config) {
            return true; // Assume yes if we can't check
        }

        $feed_config = null;
        if (is_object($config) && method_exists($config, 'get_config')) {
            $feed_config = $config->get_config();
        } elseif (is_array($config)) {
            $feed_config = $config;
        }

        if (!$feed_config || empty($feed_config['attributes'])) {
            return true; // Assume yes
        }

        // Check if any WCMLIM attribute is used
        foreach ($feed_config['attributes'] as $attr) {
            if (strpos($attr, 'wcmlim_') === 0) {
                return true;
            }
        }

        return true; // Process all feeds for now
    }

    /**
     * Process XML items and expand by location.
     *
     * @param string $feed_body   XML feed body.
     * @param string $item_wrapper Item wrapper tag.
     * @param mixed  $config      Feed config.
     * @return string Expanded XML feed body.
     */
    private function process_xml_items($feed_body, $item_wrapper, $config) {
        // Match all items in the feed
        $pattern = '/<' . preg_quote($item_wrapper, '/') . '>(.*?)<\/' . preg_quote($item_wrapper, '/') . '>/s';
        
        $expanded_body = preg_replace_callback($pattern, function($matches) use ($item_wrapper, $config) {
            return $this->expand_single_item($matches[0], $matches[1], $item_wrapper, $config);
        }, $feed_body);
        
        return $expanded_body ?: $feed_body;
    }

    /**
     * Expand a single item into multiple items by location.
     *
     * @param string $full_item     Full item XML including wrapper tags.
     * @param string $item_content  Item content without wrapper tags.
     * @param string $item_wrapper  Item wrapper tag.
     * @param mixed  $config        Feed config.
     * @return string Expanded items XML.
     */
    private function expand_single_item($full_item, $item_content, $item_wrapper, $config) {
        // Extract product ID from the item
        $product_id = $this->extract_product_id($item_content);
        
        if (!$product_id) {
            return $full_item; // Can't determine product, return as-is
        }
        
        // Get locations for this product
        $locations = $this->get_product_locations($product_id);
        
        // Filter to in-stock locations only
        $in_stock_locations = array_filter($locations, function($loc) {
            return $loc['in_stock'];
        });
        
        if (empty($in_stock_locations)) {
            // No in-stock locations, keep original item
            return $full_item;
        }
        
        // Generate an item for each location
        $expanded_items = array();
        
        foreach ($in_stock_locations as $location) {
            $location_item = $this->create_location_item(
                $item_content, 
                $item_wrapper, 
                $product_id, 
                $location,
                $config
            );
            $expanded_items[] = $location_item;
        }
        
        return implode("\n", $expanded_items);
    }

    /**
     * Extract product ID from item content.
     *
     * @param string $item_content Item XML content.
     * @return int|null Product ID or null.
     */
    private function extract_product_id($item_content) {
        // Try g:id first (Google format)
        if (preg_match('/<g:id>(\d+)<\/g:id>/i', $item_content, $matches)) {
            return intval($matches[1]);
        }
        
        // Try id without namespace
        if (preg_match('/<id>(\d+)<\/id>/i', $item_content, $matches)) {
            return intval($matches[1]);
        }
        
        // Try CDATA format
        if (preg_match('/<g:id><!\[CDATA\[(\d+)\]\]><\/g:id>/i', $item_content, $matches)) {
            return intval($matches[1]);
        }
        
        return null;
    }

    /**
     * Create a location-specific item.
     *
     * @param string $item_content Original item content.
     * @param string $item_wrapper Item wrapper tag.
     * @param int    $product_id   Product ID.
     * @param array  $location     Location data.
     * @param mixed  $config       Feed config.
     * @return string Location-specific item XML.
     */
    private function create_location_item($item_content, $item_wrapper, $product_id, $location, $config) {
        $new_content = $item_content;
        
        // Get the product for price fallback
        $product = wc_get_product($product_id);
        $currency = get_woocommerce_currency();
        
        // Calculate prices
        $location_price = !empty($location['price']) ? $location['price'] : ($product ? $product->get_price() : 0);
        $location_sale_price = !empty($location['sale_price']) ? $location['sale_price'] : null;
        
        // Update store_code
        $new_content = $this->update_xml_tag($new_content, 'g:store_code', $location['store_code']);
        $new_content = $this->update_xml_tag($new_content, 'store_code', $location['store_code']);
        
        // Update quantity with location stock
        $new_content = $this->update_xml_tag($new_content, 'g:quantity', (string)$location['stock']);
        $new_content = $this->update_xml_tag($new_content, 'quantity', (string)$location['stock']);
        
        // Update price with location price
        $formatted_price = number_format((float)$location_price, 2, '.', '') . ' ' . $currency;
        $new_content = $this->update_xml_tag($new_content, 'g:price', $formatted_price);
        
        // Update sale_price if available
        if ($location_sale_price) {
            $formatted_sale_price = number_format((float)$location_sale_price, 2, '.', '') . ' ' . $currency;
            $new_content = $this->update_xml_tag($new_content, 'g:sale_price', $formatted_sale_price);
        }
        
        // Update availability
        $availability = $location['in_stock'] ? 'in stock' : 'out of stock';
        $new_content = $this->update_xml_tag($new_content, 'g:availability', $availability);
        $new_content = $this->update_xml_tag($new_content, 'availability', $availability);
        
        // Update WCMLIM-specific fields
        $new_content = $this->update_xml_tag($new_content, 'g:location_id', $location['name']);
        $new_content = $this->update_xml_tag($new_content, 'location_id', $location['name']);
        
        $new_content = $this->update_xml_tag($new_content, 'g:location_name', $location['name']);
        $new_content = $this->update_xml_tag($new_content, 'location_name', $location['name']);
        
        $new_content = $this->update_xml_tag($new_content, 'g:location_stock', (string)$location['stock']);
        $new_content = $this->update_xml_tag($new_content, 'location_stock', (string)$location['stock']);
        
        $new_content = $this->update_xml_tag($new_content, 'g:location_price', $formatted_price);
        $new_content = $this->update_xml_tag($new_content, 'location_price', $formatted_price);
        
        return '<' . $item_wrapper . '>' . $new_content . '</' . $item_wrapper . '>';
    }

    /**
     * Update an XML tag value.
     *
     * @param string $content  XML content.
     * @param string $tag      Tag name to update.
     * @param string $value    New value.
     * @return string Updated XML content.
     */
    private function update_xml_tag($content, $tag, $value) {
        // Match tag with or without CDATA
        $pattern = '/<' . preg_quote($tag, '/') . '>.*?<\/' . preg_quote($tag, '/') . '>/s';
        
        // Check if tag exists
        if (preg_match($pattern, $content)) {
            // Replace existing tag value
            return preg_replace($pattern, '<' . $tag . '>' . htmlspecialchars($value) . '</' . $tag . '>', $content);
        }
        
        return $content;
    }

    /**
     * Get store code value for current location context.
     */
    public function get_store_code_value($output, $product, $config) {
        $location = $this->get_current_location($product->get_id());
        if ($location && !empty($location['store_code'])) {
            return $location['store_code'];
        }
        
        $location = $this->get_primary_location($product->get_id());
        return ($location && !empty($location['store_code'])) ? $location['store_code'] : $output;
    }

    /**
     * Get location name value for current location context.
     */
    public function get_location_name_value($output, $product, $config) {
        $location = $this->get_current_location($product->get_id());
        if ($location && !empty($location['name'])) {
            return $location['name'];
        }
        
        $location = $this->get_primary_location($product->get_id());
        return ($location && !empty($location['name'])) ? $location['name'] : $output;
    }

    /**
     * Get location stock quantity value for current location context.
     */
    public function get_location_stock_value($output, $product, $config) {
        $location = $this->get_current_location($product->get_id());
        if ($location) {
            return (string) $location['stock'];
        }
        
        $location = $this->get_primary_location($product->get_id());
        return $location ? (string) $location['stock'] : $output;
    }

    /**
     * Get location price value for current location context.
     */
    public function get_location_price_value($output, $product, $config) {
        $location = $this->get_current_location($product->get_id());
        if ($location && !empty($location['price'])) {
            return (string) $location['price'];
        }
        
        $location = $this->get_primary_location($product->get_id());
        if ($location && !empty($location['price'])) {
            return (string) $location['price'];
        }
        
        return $product ? (string) $product->get_price() : $output;
    }

    /**
     * Get all locations as comma-separated string.
     */
    public function get_all_locations_value($output, $product, $config) {
        $locations = $this->get_product_locations($product->get_id());
        
        if (empty($locations)) {
            return $output;
        }
        
        $location_names = array_map(function($loc) {
            return $loc['name'];
        }, $locations);
        
        return implode(',', $location_names);
    }

    /**
     * Generic attribute value filter (fallback handler).
     */
    public function filter_wcmlim_attribute_value($output, $product, $config, $merchant_attribute) {
        if (strpos($merchant_attribute, 'wcmlim_') !== 0) {
            return $output;
        }

        $location = $this->get_current_location($product->get_id());
        if (!$location) {
            $location = $this->get_primary_location($product->get_id());
        }
        if (!$location) {
            return $output;
        }

        switch ($merchant_attribute) {
            case 'wcmlim_store_code':
                return !empty($location['store_code']) ? $location['store_code'] : '';
            case 'wcmlim_location_name':
                return !empty($location['name']) ? $location['name'] : '';
            case 'wcmlim_location_stock':
                return (string) $location['stock'];
            case 'wcmlim_location_price':
                return !empty($location['price']) ? (string) $location['price'] : (string) $product->get_price();
            case 'wcmlim_all_locations':
                return $this->get_all_locations_value('', $product, $config);
        }
        
        return $output;
    }

    /**
     * Get the current location context for a product.
     */
    public function get_current_location($product_id) {
        return isset($this->current_location_context[$product_id]) 
            ? $this->current_location_context[$product_id] 
            : null;
    }

    /**
     * Set the current location context for a product.
     */
    public function set_current_location($product_id, $location) {
        $this->current_location_context[$product_id] = $location;
    }

    /**
     * Get all locations for a product.
     */
    public function get_product_locations($product_id) {
        if (isset($this->location_cache[$product_id])) {
            return $this->location_cache[$product_id];
        }

        $locations = array();
        $terms = get_the_terms($product_id, 'locations');
        
        if (!$terms || is_wp_error($terms)) {
            $this->location_cache[$product_id] = $locations;
            return $locations;
        }

        foreach ($terms as $term) {
            $stock_qty = get_post_meta($product_id, "wcmlim_stock_at_{$term->term_id}", true);
            // Use correct meta key: wcmlim_regular_price_at_{term_id} (not _wcmlim_price_at_)
            $location_price = get_post_meta($product_id, "wcmlim_regular_price_at_{$term->term_id}", true);
            $location_sale_price = get_post_meta($product_id, "wcmlim_sale_price_at_{$term->term_id}", true);
            $store_code = get_term_meta($term->term_id, 'wcmlim_store_code', true);
            
            if (empty($store_code)) {
                $store_code = $term->name;
            }

            $stock = !empty($stock_qty) ? intval($stock_qty) : 0;

            $locations[] = array(
                'term_id'    => $term->term_id,
                'name'       => $term->name,
                'slug'       => $term->slug,
                'store_code' => $store_code,
                'stock'      => $stock,
                'price'      => !empty($location_price) ? floatval($location_price) : null,
                'sale_price' => !empty($location_sale_price) ? floatval($location_sale_price) : null,
                'in_stock'   => $stock > 0,
            );
        }

        $this->location_cache[$product_id] = $locations;
        return $locations;
    }

    /**
     * Get the primary/default location for a product.
     */
    public function get_primary_location($product_id) {
        $default_location = get_post_meta($product_id, '_wcmlim_default_location', true);
        $locations = $this->get_product_locations($product_id);
        
        if (empty($locations)) {
            return null;
        }

        if (!empty($default_location)) {
            foreach ($locations as $location) {
                if ($location['term_id'] == $default_location) {
                    return $location;
                }
            }
        }

        foreach ($locations as $location) {
            if ($location['in_stock']) {
                return $location;
            }
        }

        return $locations[0];
    }

    /**
     * Post-process XML feed file to expand items by location.
     * This is called after CTX Feed generates and saves the XML file.
     *
     * @param array $info Feed info/config.
     */
    public function post_process_xml_feed($info) {
        // Only process XML feeds
        if (!isset($info['feedType']) || $info['feedType'] !== 'xml') {
            return;
        }

        // Check if this is a Google Local Inventory feed or has location attributes
        if (!isset($info['provider'])) {
            return;
        }

        // Get the feed file path
        $filename = isset($info['filename']) ? $info['filename'] : '';
        if (empty($filename)) {
            return;
        }

        $file_path = woo_feed_get_file($filename, $info['provider'], $info['feedType']);
        
        if (!file_exists($file_path) || !is_readable($file_path)) {
            return;
        }

        // Read the XML content
        $xml_content = file_get_contents($file_path);
        if (empty($xml_content)) {
            return;
        }

        // Check if this feed should be expanded by location
        // Look for wcmlim attributes in the feed config
        $has_location_attrs = false;
        if (isset($info['attributes']) && is_array($info['attributes'])) {
            foreach ($info['attributes'] as $attr) {
                if (strpos($attr, 'wcmlim_') === 0 || strpos($attr, 'wf_taxo_locations') === 0) {
                    $has_location_attrs = true;
                    break;
                }
            }
        }

        // If it's google_local_inventory provider, always expand
        if ($info['provider'] === 'google_local_inventory') {
            $has_location_attrs = true;
        }

        if (!$has_location_attrs) {
            return;
        }

        // Process the XML to expand items by location
        $expanded_xml = $this->expand_xml_items_by_location($xml_content, array(), (object)$info);

        // Only save if changes were made
        if ($expanded_xml !== $xml_content) {
            file_put_contents($file_path, $expanded_xml);
            error_log(sprintf(
                'WCMLIM CTX Feed: Expanded items by location in feed "%s"',
                $filename
            ));
        }
    }

    /**
     * Clear CTX Feed cache when WCMLIM data changes.
     */
    public function clear_ctx_feed_cache($product_id = null, $data = null) {
        if ($product_id) {
            unset($this->location_cache[$product_id]);
        } else {
            $this->location_cache = array();
        }

        if (function_exists('woo_feed_delete_cache_data')) {
            woo_feed_delete_cache_data('woo_feed_product_attribute_dropdown');
        }
        
        delete_transient('woo_feed_product_attribute_dropdown');
        delete_transient('__woo_feed_cache_woo_feed_product_attribute_dropdown');
    }

    // ====================================================================
    // AUTO-REGENERATION METHODS
    // ====================================================================

    /**
     * Handle post meta update for WCMLIM keys.
     *
     * @param int    $meta_id    Meta ID.
     * @param int    $object_id  Post ID.
     * @param string $meta_key   Meta key.
     * @param mixed  $meta_value Meta value.
     */
    public function on_wcmlim_meta_updated($meta_id, $object_id, $meta_key, $meta_value) {
        $this->check_wcmlim_meta_change($object_id, $meta_key);
    }

    /**
     * Handle post meta add for WCMLIM keys.
     *
     * @param int    $meta_id    Meta ID.
     * @param int    $object_id  Post ID.
     * @param string $meta_key   Meta key.
     * @param mixed  $meta_value Meta value.
     */
    public function on_wcmlim_meta_added($meta_id, $object_id, $meta_key, $meta_value) {
        $this->check_wcmlim_meta_change($object_id, $meta_key);
    }

    /**
     * Check if the meta key is a WCMLIM key and schedule regeneration.
     *
     * @param int    $object_id Post ID.
     * @param string $meta_key  Meta key.
     */
    private function check_wcmlim_meta_change($object_id, $meta_key) {
        // Check if this is a WCMLIM location-specific meta key
        $wcmlim_patterns = array(
            'wcmlim_stock_at_',
            'wcmlim_regular_price_at_',
            'wcmlim_sale_price_at_',
            'wcmlim_cogs_at_',
            '_wcmlim_default_location',
        );

        $is_wcmlim_key = false;
        foreach ($wcmlim_patterns as $pattern) {
            if (strpos($meta_key, $pattern) === 0 || $meta_key === $pattern) {
                $is_wcmlim_key = true;
                break;
            }
        }

        if (!$is_wcmlim_key) {
            return;
        }

        // Verify this is a product
        $post_type = get_post_type($object_id);
        if ($post_type !== 'product' && $post_type !== 'product_variation') {
            return;
        }

        // Get parent product ID for variations
        $product_id = $object_id;
        if ($post_type === 'product_variation') {
            $product_id = wp_get_post_parent_id($object_id);
        }

        // Track this product for batch regeneration
        $this->changed_products[$product_id] = true;
        
        // Clear cache for this product
        $this->clear_ctx_feed_cache($product_id);
        
        // Schedule regeneration
        $this->schedule_feed_regeneration();
    }

    /**
     * Handle product save.
     *
     * @param int $post_id Product ID.
     */
    public function on_product_save($post_id) {
        if (get_post_type($post_id) !== 'product') {
            return;
        }

        // Track product for regeneration
        $this->changed_products[$post_id] = true;
        $this->clear_ctx_feed_cache($post_id);
        $this->schedule_feed_regeneration();
    }

    /**
     * Handle variation save.
     *
     * @param int $variation_id Variation ID.
     * @param int $loop         Loop index.
     */
    public function on_variation_save($variation_id, $loop) {
        $parent_id = wp_get_post_parent_id($variation_id);
        if ($parent_id) {
            $this->changed_products[$parent_id] = true;
            $this->clear_ctx_feed_cache($parent_id);
            $this->schedule_feed_regeneration();
        }
    }

    /**
     * Schedule feed regeneration (debounced).
     */
    private function schedule_feed_regeneration() {
        if ($this->regeneration_scheduled) {
            return;
        }

        // Check if auto-regeneration is enabled
        $auto_regen = get_option('wcmlim_ctx_feed_auto_regenerate', 'on');
        if ($auto_regen !== 'on') {
            return;
        }

        $this->regeneration_scheduled = true;
        
        // Also schedule a WP Cron event as backup (runs in 1 minute)
        if (!wp_next_scheduled('wcmlim_regenerate_ctx_feeds')) {
            wp_schedule_single_event(time() + 60, 'wcmlim_regenerate_ctx_feeds');
        }
    }

    /**
     * Regenerate feeds at the end of the request if products changed.
     * Runs on 'shutdown' hook.
     */
    public function maybe_regenerate_feeds() {
        if (empty($this->changed_products) || !$this->regeneration_scheduled) {
            return;
        }

        // Don't run during AJAX requests to avoid blocking UI
        if (defined('DOING_AJAX') && DOING_AJAX) {
            // For AJAX, rely on cron backup
            return;
        }

        $this->regenerate_all_feeds();
    }

    /**
     * Regenerate all CTX Feed feeds.
     *
     * @return array Array of regenerated feed names.
     */
    public function regenerate_all_feeds() {
        global $wpdb;

        // Check if CTX Feed helper function exists
        if (!function_exists('woo_feed_generate_feed')) {
            // Try to include it
            $helper_file = WP_PLUGIN_DIR . '/webappick-product-feed-for-woocommerce/includes/helper.php';
            if (file_exists($helper_file)) {
                include_once $helper_file;
            } else {
                error_log('WCMLIM CTX Feed: Cannot find helper.php for feed regeneration');
                return array();
            }
        }

        // Get all CTX Feed configurations
        $feeds = $wpdb->get_results(
            "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE 'wf_feed_%'",
            ARRAY_A
        );

        if (empty($feeds)) {
            return array();
        }

        $regenerated = array();
        
        foreach ($feeds as $feed) {
            $option_name = $feed['option_name'];
            $feed_config = maybe_unserialize($feed['option_value']);
            
            // CTX Feed stores data double-serialized, handle both cases
            if (is_string($feed_config)) {
                $feed_config = maybe_unserialize($feed_config);
            }
            
            // Skip if not a valid feed config
            if (!is_array($feed_config) || empty($feed_config)) {
                continue;
            }

            // Skip if no feedrules
            if (!isset($feed_config['feedrules']) || empty($feed_config['feedrules'])) {
                continue;
            }

            try {
                // Call CTX Feed's regenerate function
                // Pass the full config with feedrules, CTX Feed expects this format
                if (function_exists('woo_feed_generate_feed')) {
                    $result = woo_feed_generate_feed($feed_config, $option_name);
                    
                    if ($result !== false) {
                        $regenerated[] = $option_name;
                        
                        // Log success
                        error_log(sprintf(
                            'WCMLIM CTX Feed: Regenerated feed "%s" due to location data change',
                            $option_name
                        ));
                    }
                }
            } catch (Exception $e) {
                error_log(sprintf(
                    'WCMLIM CTX Feed: Error regenerating feed "%s": %s',
                    $option_name,
                    $e->getMessage()
                ));
            }
        }

        // Clear the changed products list
        $this->changed_products = array();
        $this->regeneration_scheduled = false;

        // Fire action for external integrations
        do_action('wcmlim_ctx_feeds_regenerated', $regenerated);

        return $regenerated;
    }

    /**
     * Manual trigger for feed regeneration (can be called from admin).
     *
     * @return array Array of regenerated feed names.
     */
    public static function trigger_feed_regeneration() {
        $instance = self::get_instance();
        return $instance->regenerate_all_feeds();
    }
}

/**
 * Get the CTX Feed integration instance.
 */
function wcmlim_ctx_feed_integration() {
    return WCMLIM_CTX_Feed_Integration::get_instance();
}

// Initialize on plugins_loaded
add_action('plugins_loaded', function() {
    if (class_exists('WCMLIM_CTX_Feed_Integration')) {
        WCMLIM_CTX_Feed_Integration::get_instance();
    }
}, 20);

// Register cron hook for backup regeneration
add_action('wcmlim_regenerate_ctx_feeds', function() {
    if (class_exists('WCMLIM_CTX_Feed_Integration')) {
        WCMLIM_CTX_Feed_Integration::trigger_feed_regeneration();
    }
});

// Set default option for auto-regeneration
add_action('admin_init', function() {
    if (get_option('wcmlim_ctx_feed_auto_regenerate') === false) {
        update_option('wcmlim_ctx_feed_auto_regenerate', 'on');
    }
});
