<?php

namespace Pixeldev\SquareWooSync\Woo;

use Pixeldev\SquareWooSync\Logger\Logger;
use Pixeldev\SquareWooSync\REST\OrdersController;
use Pixeldev\SquareWooSync\Square\SquareHelper;
use Pixeldev\SquareWooSync\Woo\WooImport;

use Automattic\WooCommerce\Utilities\NumberUtil;
use ActionScheduler;


if (!defined('ABSPATH')) {
  exit; // Exit if accessed directly
}

/**
 * Class to handle WooCommerce product interactions.
 */
class SyncProduct
{
  private $inventory_update_in_progress = false;

  private $square_categories = [];
  /**
   * Constructor.
   */
  public function __construct()
  {
    add_action('init', array($this, 'init_woo_product'));
  }

  /**
   * Initialize WooCommerce Product hooks.
   */
  public function init_woo_product()
  {
    if (class_exists('WooCommerce')) {
      add_action('add_meta_boxes', array($this, 'add_sync_meta_box'));


      add_action('admin_enqueue_scripts', [$this, 'enqueue_sync_metabox_scripts']);

      add_action('sws_sync_inventory_after_product_sold_event', [$this, 'sync_inventory_after_product_sold']);
      add_action('sws_sync_order_after_product_sold_event', [$this, 'create_order_in_background']);


      add_action('woocommerce_order_status_changed', array($this, 'create_square_order_after_woo_order'), 20, 4);

      add_action('woocommerce_order_status_changed', array($this, 'update_square_stock'), 10, 4);

      add_action('woocommerce_checkout_order_processed', array($this, 'create_square_order_after_checkout_order'), 10, 1);

      add_action('woocommerce_store_api_checkout_order_processed', [$this, 'create_square_order_after_checkout_order'], 10, 1);



      add_action('woocommerce_checkout_create_order', array($this, 'set_order_meta'), 10, 2);
    }
  }

  /**
   * Fetch all Square categories once and cache them in $this->square_categories.
   */
  private function load_square_categories($square)
  {
    if (!empty($this->square_categories)) {
      return $this->square_categories; // already loaded
    }

    $response = $square->square_api_request('/catalog/list?types=CATEGORY', 'GET');
    if (!empty($response['success']) && !empty($response['data']['objects'])) {
      $this->square_categories = $response['data']['objects'];
    } else {
      $this->square_categories = [];
    }
    return $this->square_categories;
  }

  /**
   * Find an existing Square category by matching the WooCommerce term name (case-insensitive).
   */
  private function find_square_category_by_term(\WP_Term $term)
  {
    $lowerName = strtolower($term->name);
    foreach ($this->square_categories as $catObj) {
      if ($catObj['type'] === 'CATEGORY' && !empty($catObj['category_data']['name'])) {
        if (strtolower($catObj['category_data']['name']) === $lowerName) {
          return $catObj['id']; // Return just the category ID
        }
      }
    }
    return null;
  }

  /**
   * Build an ancestor chain (array of WP_Term) from topmost ancestor down to this term.
   */
  private function build_ancestor_chain(\WP_Term $term, $taxonomy = 'product_cat')
  {
    $chain = [$term];
    $current = $term;
    while ($current->parent) {
      $parent = get_term($current->parent, $taxonomy);
      if ($parent && !is_wp_error($parent)) {
        array_unshift($chain, $parent);
        $current = $parent;
      } else {
        break;
      }
    }
    return $chain;
  }

  /**
   * Create (if needed) and retrieve the final Square category ID for a given Woo category term.
   */
  private function get_or_create_square_category(\WP_Term $term, $square)
  {
    $this->load_square_categories($square); // ensures $this->square_categories is populated
    $chain = $this->build_ancestor_chain($term);

    $parentSquareId = null;
    $finalSquareId  = null;

    foreach ($chain as $index => $currentTerm) {
      $foundId = $this->find_square_category_by_term($currentTerm);
      if ($foundId) {
        // Already exists
        $parentSquareId = $foundId;
        $finalSquareId = $foundId;
        continue;
      }

      // Create a new category
      $tempId = '#cat_' . wp_generate_uuid4();
      $isTopLevel = ($index === 0);

      $newCategoryObject = [
        'id'                        => $tempId,
        'type'                      => 'CATEGORY',
        'is_deleted'               => false,
        'present_at_all_locations' => true,
        'category_data'            => [
          'name'          => $currentTerm->name,
          'is_top_level'  => $isTopLevel,
          'category_type' => 'REGULAR_CATEGORY',
          'online_visibility' => true,
        ]
      ];

      if (!$isTopLevel && $parentSquareId) {
        $newCategoryObject['category_data']['parent_category'] = [
          'id' => $parentSquareId
        ];
      }

      $createResponse = $square->square_api_request('/catalog/object', 'POST', [
        'idempotency_key' => wp_generate_uuid4(),
        'object'          => $newCategoryObject
      ]);

      if (!empty($createResponse['success']) && !empty($createResponse['data']['catalog_object'])) {
        $createdCat = $createResponse['data']['catalog_object'];
        $createdCatId = $createdCat['id'];
        $this->square_categories[] = $createdCat; // push to local cache
        $parentSquareId = $createdCatId;
        $finalSquareId  = $createdCatId;
      } else {
        error_log("Failed to create Square category for term: {$currentTerm->name}");
        return null;
      }
    }

    return $finalSquareId;
  }

  /**
   * Upload a WP attachment to Square as a catalog image, returning the new Square image_id on success.
   */
  public function upload_image_to_square($image_id, $square, $is_primary = false)
  {
    global $wp_filesystem;

    // Initialize WP_Filesystem if not ready
    if (empty($wp_filesystem)) {
      require_once ABSPATH . 'wp-admin/includes/file.php';
      WP_Filesystem();
    }

    $file_path = get_attached_file($image_id);
    if (!$file_path || !$wp_filesystem->exists($file_path)) {
      error_log("File does not exist: $file_path");
      return null;
    }

    $image_name = basename($file_path);

    $token = $square->get_active_token(); // your function to retrieve the OAuth token
    $idempotency_key = wp_generate_uuid4();

    // JSON portion for the request
    $request_data = json_encode([
      'idempotency_key' => $idempotency_key,
      'image' => [
        'id' => '#TEMP_ID',
        'type' => 'IMAGE',
        'image_data' => [
          'caption' => 'Synced from Woo',
          'is_primary' => $is_primary
        ]
      ]
    ]);

    // Build form-data for cURL
    $post_fields = [
      'file' => new \CURLFile($file_path, mime_content_type($file_path), $image_name),
      'request' => $request_data
    ];

    $settings = get_option('square-woo-sync_settings', []);
    $url = (isset($settings['environment']) && $settings['environment'] === 'sandbox')
      ? 'https://connect.squareupsandbox.com/v2/catalog/images'
      : 'https://connect.squareup.com/v2/catalog/images';

    // cURL
    $curl = curl_init();
    curl_setopt_array($curl, [
      CURLOPT_URL => $url,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_POST => true,
      CURLOPT_POSTFIELDS => $post_fields,
      CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . esc_attr($token),
        'Accept: application/json',
        'Square-Version: 2024-09-19',
      ],
    ]);

    $response = curl_exec($curl);
    $error = curl_error($curl);
    curl_close($curl);

    if ($response === false) {
      error_log("Square image upload cURL error: $error");
      return null;
    }

    $response_data = json_decode($response, true);
    if (isset($response_data['image']['id'])) {
      return $response_data['image']['id'];
    } else {
      error_log("Square image upload failed: " . $response);
      return null;
    }
  }

  /**
   * Upload the main + gallery images of a Woo product to Square, return an array of image IDs.
   */
  public function upload_product_images_to_square(\WC_Product $product, $square)
  {
    $image_ids = [];

    // featured (main) image
    $main_id = $product->get_image_id();
    if ($main_id) {
      $square_image_id = $this->upload_image_to_square($main_id, $square, true);
      if ($square_image_id) {
        $image_ids[] = $square_image_id;
      }
    }

    // gallery
    $gallery_ids = $product->get_gallery_image_ids();
    foreach ($gallery_ids as $gallery_id) {
      $square_gal_id = $this->upload_image_to_square($gallery_id, $square, false);
      if ($square_gal_id) {
        $image_ids[] = $square_gal_id;
      }
    }

    return $image_ids;
  }



  /**
   * Build the categories array for this product in Square ("item_data.categories").
   * Returns something like: [ ["id"=>"SQUARE_CAT_ID", "ordinal"=>1], ...]
   */
  public function build_square_category_array(\WC_Product $product, $square)
  {
    $cats = get_the_terms($product->get_id(), 'product_cat');
    if (empty($cats) || is_wp_error($cats)) {
      return [];
    }

    $categoryEntries = [];
    $ordinal = 1;
    foreach ($cats as $term) {
      $squareCatId = $this->get_or_create_square_category($term, $square);
      if ($squareCatId) {
        $categoryEntries[] = [
          'id' => $squareCatId,
          'ordinal' => $ordinal++
        ];
      }
    }

    return $categoryEntries;
  }

  private function normalize_order_data($order_data)
  {
    // Case 1: Single WC_Order object
    if ($order_data instanceof \WC_Order) {
      return [(int) $order_data->get_id()];
    }

    // Case 2: Single numeric ID
    if (is_numeric($order_data)) {
      return [(int) $order_data];
    }

    // Case 3: Already an array
    if (is_array($order_data)) {
      $clean_ids = [];
      foreach ($order_data as $item) {
        if ($item instanceof \WC_Order) {
          $clean_ids[] = (int) $item->get_id();
        } elseif (is_numeric($item)) {
          $clean_ids[] = (int) $item;
        }
      }
      return array_unique($clean_ids); // avoid duplicates
    }

    // Fallback: if we got some unexpected type, just cast to int in an array
    return [(int) $order_data];
  }

  /**
   * update_square_stock() — triggers the inventory sync in background using Action Scheduler
   */
  public function update_square_stock($order_id, $old_status = '', $new_status = '', $order = null)
  {
    $current_settings = get_option('square-woo-sync_settings', []);
    if (empty($current_settings) || ! $current_settings['wooAuto']['isActive'] || ! $current_settings['wooAuto']['stock']) {
      return;
    }

    if (($old_status === 'processing' || $old_status === 'completed') && ($new_status === 'refunded' ||  $new_status === 'cancelled')) {
      // 3) Check if there's already an action scheduled with these exact arguments:
      $scheduled = as_next_scheduled_action(
        'sws_sync_inventory_after_product_sold_event',
        ['order_id' => $order_id]
      );

      // 4) If none is scheduled, queue a new single action for 10 seconds from now.
      if (! $scheduled) {
        as_schedule_single_action(
          time() + 10,
          'sws_sync_inventory_after_product_sold_event',
          ['order_id' => $order_id],
          'squarewoosync' // (optional) group name for Action Scheduler
        );
      }
    }
  }


  public function set_order_meta($order, $data)
  {
    // Initialize an array to store all unique locations
    $order_locations = [];

    // Loop through each line item in the order
    foreach ($order->get_items() as $item_id => $item) {
      // Get the product ID
      $product_id = $item->get_product_id();

      // Retrieve the 'square_locations' meta data for the product
      $locations = get_post_meta($product_id, 'square_locations', true);

      if (!empty($locations)) {
        // Split the comma-separated locations into an array
        $locations_array = explode(',', $locations);

        // Merge with the existing order locations, ensuring uniqueness
        $order_locations = array_unique(array_merge($order_locations, $locations_array));
      }
    }

    if (!empty($order_locations)) {
      // Save the combined, unique locations as a comma-separated string
      $order->update_meta_data('square_locations', implode(',', $order_locations));
    }

    $order->save();
  }


  /**
   * Adds a meta box for syncing with Square.
   */
  public function add_sync_meta_box()
  {
    add_meta_box(
      'sws_sync_square',
      'Unlock SquareSync for Woo Pro',
      array($this, 'sync_meta_box_html'),
      'product',
      'side',
      'high'
    );
  }

  /**
   * Only load your JS on the *product* edit/new pages.
   *
   * @param  string $hook_suffix e.g. 'post.php', 'post-new.php', etc.
   */
  public function enqueue_sync_metabox_scripts($hook_suffix)
  {
    // Only on WP admin post-edit screens:
    if ($hook_suffix !== 'post.php' && $hook_suffix !== 'post-new.php') {
      return;
    }

    // Make sure it’s the WooCommerce “product” post type:
    $screen = get_current_screen();
    if (! $screen || $screen->post_type !== 'product') {
      return;
    }

    // Now enqueue *before* footer so WP can place it cleanly
    wp_enqueue_script(
      'sws-custom-script',
      plugins_url('assets/js/sync-metabox.js', SQUAREWOOSYNC_FILE),
      ['jquery'],
      '1.0.0',
      true
    );
    wp_localize_script('sws-custom-script', 'swsAjax', [
      'ajaxurl' => admin_url('admin-ajax.php'),
      'nonce'   => wp_create_nonce('sws_ajax_nonce'),
    ]);
  }




  public function sync_inventory_after_product_sold($order_id)
  {
    $order = wc_get_order($order_id);
    $current_settings = get_option('square-woo-sync_settings', []);
    // Check if $order is a valid WC_Order object and return early if not.
    if (!$order) {
      return;
    }


    $uniqueProcessId = wp_generate_uuid4();
    $hasSquareLinkedProduct = false;
    /** @var array{square_product_id: mixed, woo_product_id: mixed}[] $square_product_ids */
    $square_product_ids = []; // Store square_product_ids along with their corresponding WooCommerce product IDs.

    // Loop through order items.
    foreach ($order->get_items() as $item) {
      $product = $item->get_product();
      if (! $product || ! $product->managing_stock()) {
        continue; // Skip if the product is not managing stock.
      }

      // Check if the product is a variation.
      if ($product->is_type('variation')) {
        $parent_product    = wc_get_product($product->get_parent_id());
        $square_product_id = $parent_product ? $parent_product->get_meta('square_product_id') : null;
      } else {
        $square_product_id = $product->get_meta('square_product_id');
      }

      if (! $square_product_id) {
        continue; // Skip if the product is not linked to Square.
      }

      // Add the square_product_id and WooCommerce product ID to the list for syncing.
      $square_product_ids[] = [
        'square_product_id' => $square_product_id,
        'woo_product_id'    => $product->get_id(),
      ];

      // At least one product is linked to Square.
      $hasSquareLinkedProduct = true;
    }

    // If there is at least one linked product, add an order note to indicate the sync has started.
    if ($hasSquareLinkedProduct) {
      Logger::log('info', 'Initiating inventory sync from WooCommerce to Square');
      $order->add_order_note("Inventory sync started.");
    }

    // Callback to add a completion note after processing inventory changes.
    $add_completion_note = function () use ($order, $square_product_ids) {
      $stock_message = '';
      // Loop through each product and get its current stock.
      foreach ($square_product_ids as $product_data) {
        $product = wc_get_product($product_data['woo_product_id']);
        if ($product) {
          $stock_qty     = $product->get_stock_quantity();
          $stock_message .= sprintf('%s: %d remaining; ', $product->get_name(), $stock_qty);
        }
      }
      $order->add_order_note("Inventory sync success. Stock remaining: " . $stock_message);
    };

    // Check if allLocationsStock is set and true.
    $update_all_locations = isset($current_settings['wooAuto']['allLocationsStock']) && $current_settings['wooAuto']['allLocationsStock'];

    if ($update_all_locations) {
      // Retrieve all Square locations.
      SquareHelper::queue_request('/locations', 'GET', [], null, function ($location_response) use ($uniqueProcessId, $square_product_ids, $current_settings, $order, $add_completion_note) {
        $current_settings = get_option('square-woo-sync_settings', []);
        if (! $location_response['success']) {
          Logger::log('error', 'Failed to retrieve Square locations: ' . json_encode($location_response));
          // Add an order note indicating failure.
          $order->add_order_note("Inventory sync failed: Unable to retrieve Square locations.");
          $this->inventory_update_in_progress = false;
          return;
        }

        $locations = array_column($location_response['data']['locations'], 'id');

        // Retrieve catalog objects.
        SquareHelper::queue_request('/catalog/batch-retrieve', 'POST', [
          'object_ids' => array_column($square_product_ids, 'square_product_id')
        ], null, function ($response) use ($uniqueProcessId, $square_product_ids, $current_settings, $locations, $order, $add_completion_note) {
          $this->construct_inventory_changes($response, $square_product_ids, $current_settings, $locations, $uniqueProcessId);
          // Add order note upon completion.
          $add_completion_note();
        });
      });
    } else {
      $current_settings = get_option('square-woo-sync_settings', []);
      // Proceed with a single location.
      $locations = [$current_settings['location']];

      // Retrieve catalog objects.
      SquareHelper::queue_request('/catalog/batch-retrieve', 'POST', [
        'object_ids' => array_column($square_product_ids, 'square_product_id')
      ], null, function ($response) use ($uniqueProcessId, $square_product_ids, $current_settings, $locations, $order, $add_completion_note) {
        $this->construct_inventory_changes($response, $square_product_ids, $current_settings, $locations, $uniqueProcessId);
        // Add order note upon completion.
        $add_completion_note();
      });
    }
  }




  private function construct_inventory_changes($response, $square_product_ids, $current_settings, $locations, $uniqueProcessId)
  {
    if (!$response['success']) {
      error_log('Failed to update Square inventory: ' . json_encode($response));

      $this->inventory_update_in_progress = false;
      return;
    }

    if (isset($response['data']['objects']) && !empty($response['data']['objects'])) {

      $changes = [];
      $updated_products = []; // Array to hold information about updated products

      foreach ($square_product_ids as $product_pair) {
        $square_product_id = $product_pair['square_product_id'];
        $woo_product_id = $product_pair['woo_product_id'];

        $woo_product = wc_get_product($woo_product_id);

        if ($woo_product && $woo_product->is_type('simple')) {
          // Handle simple product
          $square_variation_id = $woo_product->get_meta('square_variation_id');

          // Check if square_variation_id exists; if not, fall back to square_product_id
          foreach ($response['data']['objects'] as $square_variation) {
            if (!isset($square_variation['item_data']['variations'])) {
              continue;
            }
            foreach ($square_variation['item_data']['variations'] as $variation) {
              if ($square_variation_id) {
                // Check for matching square_variation_id
                if ($variation['id'] === $square_variation_id) {
                  $stock_quantity = $woo_product->get_stock_quantity();
                  if ($stock_quantity !== null) {
                    foreach ($locations as $location_id) {
                      $changes[] = [
                        'physical_count' => [
                          'quantity' => (string) $stock_quantity,
                          'occurred_at' => current_time('c'),
                          'location_id' => $location_id,
                          'catalog_object_id' => $variation['id'],
                          'state' => 'IN_STOCK'
                        ],
                        'type' => 'PHYSICAL_COUNT'
                      ];

                      // Add product info to updated_products array
                      $updated_products[] = [
                        'product_id' => $woo_product_id,
                        'product_name' => $woo_product->get_name(),
                        'location_id' => $location_id,
                        'stock_quantity' => $stock_quantity
                      ];
                    }
                  }
                  break 2; // Exit both loops after finding the match
                }
              } else {
                // No square_variation_id, fallback to square_product_id to find the match
                if ($variation['item_variation_data']['item_id'] === $square_product_id) {
                  $stock_quantity = $woo_product->get_stock_quantity();
                  if ($stock_quantity !== null) {
                    foreach ($locations as $location_id) {
                      $changes[] = [
                        'physical_count' => [
                          'quantity' => (string) $stock_quantity,
                          'occurred_at' => current_time('c'),
                          'location_id' => $location_id,
                          'catalog_object_id' => $variation['id'],
                          'state' => 'IN_STOCK'
                        ],
                        'type' => 'PHYSICAL_COUNT'
                      ];

                      // Add product info to updated_products array
                      $updated_products[] = [
                        'product_id' => $woo_product_id,
                        'product_name' => $woo_product->get_name(),
                        'location_id' => $location_id,
                        'stock_quantity' => $stock_quantity
                      ];
                    }
                  }
                  break 2; // Exit both loops after finding the match
                }
              }
            }
          }
        } elseif ($woo_product && $woo_product->is_type('variation')) {
          // Handle variable product variation
          $square_product_id = $woo_product->get_meta('square_product_id');
          foreach ($response['data']['objects'] as $square_variation) {
            if (!isset($square_variation['item_data']['variations'])) {
              continue;
            }
            foreach ($square_variation['item_data']['variations'] as $variation) {
              if ($square_product_id && $variation['id'] === $square_product_id) {
                $stock_quantity = $woo_product->get_stock_quantity();
                if ($stock_quantity !== null) {
                  foreach ($locations as $location_id) {
                    $changes[] = [
                      'physical_count' => [
                        'quantity' => (string) $stock_quantity,
                        'occurred_at' => current_time('c'),
                        'location_id' => $location_id,
                        'catalog_object_id' => $variation['id'],
                        'state' => 'IN_STOCK'
                      ],
                      'type' => 'PHYSICAL_COUNT'
                    ];

                    // Add product info to updated_products array
                    $updated_products[] = [
                      'product_id' => $woo_product_id,
                      'product_name' => $woo_product->get_name(),
                      'location_id' => $location_id,
                      'stock_quantity' => $stock_quantity
                    ];
                  }
                }
                break 2; // Exit loops after match
              }
            }
          }
        }
      }

      // Ensure that there are changes to be synced
      if (!empty($changes)) {
        // Split changes into batches of 100
        $batches = array_chunk($changes, 100);
        $product_batches = array_chunk($updated_products, 100);

        // Counter to track completed batches
        $completed_batches = 0;
        $total_batches = count($batches);

        foreach ($batches as $index => $batch) {
          $body = [
            'idempotency_key' => wp_generate_uuid4(),
            'changes' => $batch
          ];

          $batch_products = $product_batches[$index];

          // Queue the request to update the inventory on Square
          SquareHelper::queue_request('/inventory/changes/batch-create', 'POST', $body, null, function ($response) use ($uniqueProcessId, &$completed_batches, $total_batches, $batch_products) {
            if ($response['success']) {
              // Build a detailed message with product names and quantities
              $product_details = array_map(function ($product) {
                return "{$product['product_name']} (ID: {$product['product_id']}, Quantity: {$product['stock_quantity']})";
              }, $batch_products);

              $product_details_message = implode(', ', $product_details);

              // Log success message including product details
              Logger::log('success', "Square inventory batch successfully updated. Products: {$product_details_message}", [
                'process_id' => $uniqueProcessId
              ]);
            } else {
              Logger::log('error', 'Failed to update Square inventory batch: ' . json_encode($response), [
                'error_message' => json_encode($response),
                'process_id' => $uniqueProcessId
              ]);
            }

            $completed_batches++;

            // Reset inventory update flag after all batches are processed
            if ($completed_batches >= $total_batches) {
              $this->inventory_update_in_progress = false;
            }
          });
        }
      } else {
        Logger::log('info', 'No valid inventory changes found to sync with Square.', [
          'process_id' => $uniqueProcessId
        ]);

        // Reset inventory update flag here
        $this->inventory_update_in_progress = false;
      }
    } else {
      Logger::log('info', 'No valid objects returned from Square API.', [
        'process_id' => $uniqueProcessId
      ]);

      // Reset inventory update flag here
      $this->inventory_update_in_progress = false;
    }
  }


  public function create_order_in_background($order_id)
  {


    $order = wc_get_order($order_id);

    // because this scenario is handled by the normal `process_payment`.
    if ($order->get_payment_method() === 'squaresync_credit') {
      return;
    }

    // 1) Check if a transient already exists for this order, preventing re-processing
    if (get_transient('sws_order_processed_' . $order_id)) {
      return; // Already processed recently
    }

    // 2) Set a transient to prevent running again for this order (10 min lock)
    set_transient('sws_order_processed_' . $order_id, true, 10 * MINUTE_IN_SECONDS);

    $uniqueProcessId = wp_generate_uuid4();
    Logger::log(
      'info',
      'Initiating order sync to Square for order #' . $order_id,
      array('process_id' => $uniqueProcessId)
    );

    $ordersController = new OrdersController();
    $square           = new SquareHelper();

    $location_id      = $ordersController->getLocationId($order);

    try {
      // 4) Retrieve or create Square customer
      $square_customer_id = $ordersController->getOrCreateSquareCustomer($order, $square);
      if (isset($square_customer_id['error'])) {
        Logger::log(
          'error',
          'Square Orders error: ' .  $square_customer_id['error'],
          array('parent_id' => $uniqueProcessId)
        );
      }

      // 5) Prepare order data for Square
      $order_data = $ordersController->prepareSquareOrderData($order, $square_customer_id, $location_id);

      // 6) Create the order in Square
      $response = $ordersController->createOrderInSquare($order_data, $square);
      if (isset($response['error'])) {
        Logger::log(
          'error',
          'Square Orders API error: ' .  $response['error'],
          array('parent_id' => $uniqueProcessId)
        );
        return;
      }


      $square_order      = $response['data']['order'] ?? [];
      $square_order_id   = $square_order['id'] ?? null;

      // 8) Initially we do NOT consider it "paid"
      $paid = false;


      $multiplier = $ordersController->get_currency_multiplier();
      $order_total = round($order->get_total() * $multiplier);

      if (empty($square_order_id)) {
        throw new \Exception('No Square order ID returned from createOrderInSquare.');
      }


      // 11 If a gift card is present, handle partial or full coverage
      $payResponse = null;


      // 12) If none of the above triggered a pay response, do normal pay
      if (!$paid) {
        $payResponse = $ordersController->payForOrder($square_order, $square, null, $location_id);
      }

      if (isset($payResponse['error'])) {
        Logger::log('error', 'Square Payment API error: ' . $payResponse['error'], array('parent_id' => $uniqueProcessId));
      }

      // 13) Save the square_data
      if (isset($square_order['id']) && isset($payResponse['data']['payment']['id'])) {
        $square_data =  ['order' => $response, 'payment' => $payResponse];
        $order->update_meta_data('square_data', wp_json_encode($square_data));
        $order->update_meta_data('square_order_id', $square_order['id']);
        $order->save();

        do_action(
          'squarewoosync_square_order_created',
          $square_order['id'],
          $order->get_id()
        );
      }

      if (WC()->session && is_object(WC()->session)) {
        WC()->session->__unset('store_credit_discount_type');
        WC()->session->__unset('store_credit_discount_amount');
      }



      Logger::log(
        'success',
        'Order and Transaction created in Square for order #' . $order_id,
        array('parent_id' => $uniqueProcessId)
      );
    } catch (\Exception $e) {
      if ($e->getMessage() == 'Square location not set') {
        Logger::log('error', 'Square location not set: ' . $e->getMessage(), array('parent_id' => $uniqueProcessId));
      }
      Logger::log('error', 'Failed to create order in background for #' . $order_id . ': ' . $e->getMessage(), array('parent_id' => $uniqueProcessId));
    }
  }



  /**
   * Handle actions on order status change.
   *
   * @param int    $order_id   The order ID.
   * @param string $old_status The old order status.
   * @param string $new_status The new order status.
   * @param \WC_Order $order    The order object.
   */
  public function create_square_order_after_woo_order($order_id, $old_status = '', $new_status = '', $order = null)
  {
    $order = wc_get_order($order_id);
    if ($order && $order->get_created_via() === 'checkout') {
      return;
    }

    if ($order->get_payment_method() === 'squaresync_credit') {
      return;
    }

    if ($new_status === 'pending-release' || $new_status === 'draft' || $new_status === 'pending-payment' || $new_status === 'pending') {
      return;
    }

    // 2) Don't continue if the order already has square_data (meaning we've processed it).
    if ($order && $order->get_meta('square_data')) {
      return;
    }

    // 3) Check if Square credit gateway is enabled.
    $available_gateways = WC()->payment_gateways()->get_available_payment_gateways();
    $square_credit_enabled = isset($available_gateways['squaresync_credit']);

    // 4) If squaresync_credit is NOT enabled, then check plugin settings
    if (!$square_credit_enabled) {
      $current_settings = get_option('square-woo-sync_settings', []);
      if (
        empty($current_settings['orders']) ||
        empty($current_settings['orders']['enabled']) ||
        $current_settings['orders']['enabled'] !== true ||
        empty($current_settings['orders']['stage']) ||
        $current_settings['orders']['stage'] !== $new_status
      ) {
        // Condition for auto-creating Square orders not met.
        return;
      }
    }

    // 5) Finally, schedule the order event if not already scheduled.
    $timestamp_order = as_next_scheduled_action('sws_sync_order_after_product_sold_event', ['order_id' => $order_id]);
    if (!$timestamp_order) {

      as_schedule_single_action(
        time(),
        'sws_sync_order_after_product_sold_event',
        ['order_id' => $order_id],
        'squarewoosync'
      );
    }
  }

  public function create_square_order_after_checkout_order($order)
  {
    if (! is_object($order)) {
      $order_id = absint($order);
      $order    = wc_get_order($order_id);
    } else {
      $order_id = $order->get_id();
    }

    // 2) Don't continue if the order already has square_data (meaning we've processed it).
    if ($order && $order->get_meta('square_data')) {
      return;
    }

    // Don't auto-create Square orders for Square payment gateways (they create their own orders)
    $square_payment_methods = ['squaresync_credit', 'squaresync_cashapp'];
    if (in_array($order->get_payment_method(), $square_payment_methods)) {
      return;
    }

    // 3) Check if Square credit gateway is enabled.
    $available_gateways = WC()->payment_gateways()->get_available_payment_gateways();
    $square_credit_enabled = isset($available_gateways['squaresync_credit']);

    // 4) If squaresync_credit is NOT enabled, then check plugin settings
    if (!$square_credit_enabled) {
      $current_settings = get_option('square-woo-sync_settings', []);
      if (
        empty($current_settings['orders']) ||
        empty($current_settings['orders']['enabled']) ||
        $current_settings['orders']['enabled'] !== true
      ) {
        // Condition for auto-creating Square orders not met.
        return;
      }
    }


    // 5) Finally, schedule the order event if not already scheduled.
    $timestamp_order = as_next_scheduled_action('sws_sync_order_after_product_sold_event', ['order_id' => $order_id]);
    if (!$timestamp_order) {


      as_schedule_single_action(
        time(),
        'sws_sync_order_after_product_sold_event',
        ['order_id' => $order_id],
        'squarewoosync'
      );
    }
  }

  /**
   * AJAX handler for exporting products to Square.
   */
  public function handle_ajax_export_to_square()
  {
    check_ajax_referer('sws_ajax_nonce', 'nonce');

    $product_id = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
    $product    = wc_get_product($product_id);

    // Grab which fields to export
    $export_fields = isset($_POST['export_fields']) ? (array) $_POST['export_fields'] : [];

    // If not provided, or empty, default to "images" and "categories" = true
    $include_images     = (empty($export_fields) || in_array('images', $export_fields, true));
    $include_categories = (empty($export_fields) || in_array('categories', $export_fields, true));

    $uniqueProcessId = wp_generate_uuid4();

    if (!$product_id || !$product) {
      wp_send_json_error(['message' => 'Invalid product ID.']);
    }

    // Create an instance of your exporter
    $exporter = new WooImport();

    // Pass an array or object describing which fields to export
    // The '1' in your code means $exportSynced = 1 (that’s your existing param).
    // We'll add a new 5th param: $fields_to_export
    $fields_to_export = [
      'images'     => $include_images,
      'categories' => $include_categories,
    ];

    // Modify import_products() to accept $fields_to_export
    $result = $exporter->import_products($uniqueProcessId, [$product], 1, 50, $fields_to_export);

    // Attempt to sync inventory
    $exporter->update_square_inventory_counts([$product]);

    if (is_wp_error($result)) {
      wp_send_json_error([
        'message' => $result->get_error_message()
      ]);
    }

    // Now $result is an array with 'failures' + 'square_responses'
    if (!empty($result['failures'])) {
      $fail = $result['failures'][0]; // single product, so handle the first
      $errors_str = implode('; ', $fail['errors']);
      wp_send_json_error([
        'message' => "Failed to export: {$errors_str}"
      ]);
    }

    // Check square_responses
    if (!empty($result['square_responses'])) {
      foreach ($result['square_responses'] as $resp) {
        if (!empty($resp['success']) && !empty($resp['data']['objects'])) {
          foreach ($resp['data']['objects'] as $object) {
            if ($object['type'] === 'ITEM' && isset($object['item_data']['name'])) {
              Logger::log(
                'success',
                'Product exported and linked to Square: ' . $product->get_title(),
                ['product_id' => $product_id]
              );
              wp_send_json_success([
                'message' => 'Successfully exported and linked product to Square.'
              ]);
            }
          }
        }
      }
      // If we never found a success
      wp_send_json_error([
        'message' => 'Failed to export: no valid ITEM object found.'
      ]);
    }

    // If we got here, no responses
    wp_send_json_error([
      'message' => 'Unexpected response format or no Square responses.'
    ]);
  }


  /**
   * Delete Square Product from Woo ID
   */
  public function delete_square_product($post_id)
  {
    // Check if the post type is 'product'
    if (get_post_type($post_id) === 'product') {

      // Get the current settings
      $current_settings = get_option('square-woo-sync_settings', []);

      // Check if auto product deletion is enabled in the settings
      if (!empty($current_settings) && !empty($current_settings['wooAuto']['autoDeleteProduct']) && $current_settings['wooAuto']['autoDeleteProduct'] === true) {

        // Get the square_product_id from post meta
        $square_product_id = get_post_meta($post_id, 'square_product_id', true);

        if ($square_product_id) {
          $product = wc_get_product($post_id);
          $product_name = $product ? $product->get_name() : 'Unknown Product';


          $square_helper = new SquareHelper();
          $uniqueProcessId = wp_generate_uuid4();

          try {
            // Log the initiation of the deletion process
            Logger::log('info', 'Initiating deletion of Square product ' . $product_name, array(
              'product_id' => $post_id,
              'product_name' => $product_name,
              'square_product_id' => $square_product_id,
              'process_id' => $uniqueProcessId
            ));

            // Send a request to the Square API to delete the product
            $response = $square_helper->square_api_request("/catalog/object/" . $square_product_id, 'DELETE');

            if (!$response['success']) {
              // Log the error if the deletion failed
              Logger::log('error', 'Failed to delete ' . $product_name . ' from Square library', array(
                'product_id' => $post_id,
                'product_name' => $product_name,
                'square_product_id' => $square_product_id,
                'error_message' => $response['error'],
                'parent_id' => $uniqueProcessId
              ));
            } else {
              // Log the success if the deletion was successful
              Logger::log('success', 'Successfully deleted ' . $product_name . ' from Square library', array(
                'product_id' => $post_id,
                'product_name' => $product_name,
                'square_product_id' => $square_product_id,
                'parent_id' => $uniqueProcessId
              ));
            }
          } catch (\Exception $e) {
            // Log the exception if an error occurred during the deletion process
            Logger::log('error', 'Exception occurred while deleting Square product', array(
              'product_id' => $post_id,
              'product_name' => $product_name,
              'square_product_id' => $square_product_id,
              'error_message' => $e->getMessage(),
              'process_id' => $uniqueProcessId
            ));
          }
        }
      }
    }
  }


  /**
   * AJAX handler for syncing products to Square.
   */
  public function handle_ajax_sync_to_square()
  {
    check_ajax_referer('sws_ajax_nonce', 'nonce');

    $product_id  = isset($_POST['product_id']) ? intval($_POST['product_id']) : 0;
    $sync_fields = isset($_POST['sync_fields']) ? (array) $_POST['sync_fields'] : array();

    if ($product_id) {
      $product = wc_get_product($product_id);

      // Dynamically build data_to_import based on the selected checkboxes
      $data_to_import = array(
        'stock'       => in_array('stock', $sync_fields, true),
        'title'       => in_array('title', $sync_fields, true),
        'description' => in_array('description', $sync_fields, true),
        'price'       => in_array('price', $sync_fields, true),
        'sku'         => in_array('sku', $sync_fields, true),
        'images'      => in_array('images', $sync_fields, true),
        'categories'  => in_array('categories', $sync_fields, true),
      );

      // Call your sync function
      $result = $this->on_product_update($product_id, $data_to_import, true);

      if ($result && $this->is_sync_successful($result)) {
        Logger::log(
          'success',
          'Successfully synced: ' . $product->get_title() . ' to Square',
          array('product_id' => $product_id)
        );
        wp_send_json_success(array('message' => 'Product synced successfully with Square.'));
      } else {
        $error_msg = isset($result['error']) ? $result['error'] : 'Unknown error occurred.';
        wp_send_json_error(array('message' => $error_msg));
      }
    } else {
      wp_send_json_error(array('message' => 'Invalid product ID.'));
    }
  }



  /**
   * Check if sync result is successful.
   * 
   * @param array $result The result array.
   * 
   * @return bool
   */
  private function is_sync_successful($result)
  {
    return (isset($result['inventoryUpdateStatus']['success']) && $result['inventoryUpdateStatus']['success'] === true) ||
      (isset($result['productUpdateStatus']['success']) && $result['productUpdateStatus']['success'] === true);
  }


  /**
   * Render the SquareWooSync meta-box (free version).
   *
   * Shows an upsell panel that mirrors the markup/style pattern used by
   * Otter Blocks’ WooCommerce Builder box.
   *
   * @param WP_Post $post Current post object.
   */
  public function sync_meta_box_html($post)
  {
?>
    <div class="inside">
      <style type="text/css">
        .o-upsell li {
          display: flex;
          align-items: center;
        }

        .o-upsell li::before {
          content: "\f147";
          /* Dashicons yes/check */
          font-family: 'Dashicons';
          color: #2271B1;
          font-size: 16px;
          margin-right: 5px;
        }
      </style>

      <div class="clear o-upsell">
        <p>
          <?php echo esc_html__('Unlock the full power of SquareSync for Woo with Pro:', 'squarewoosync'); ?>
        </p>

        <ul>
          <li><?php echo esc_html__('Real-time, two-way product & stock sync', 'squarewoosync'); ?></li>
          <li><?php echo esc_html__('Location-aware inventory & taxes', 'squarewoosync'); ?></li>
          <li><?php echo esc_html__('Square Loyalty earn & redeem at checkout', 'squarewoosync'); ?></li>
          <li><?php echo esc_html__('Automatic POS ↔ WooCommerce order sync', 'squarewoosync'); ?></li>
          <li><?php echo esc_html__('Priority support & self-healing scheduler', 'squarewoosync'); ?></li>
        </ul>

        <a href="https://squarewoosync.com/pro"
          target="_blank"
          class="button button-primary">
          <?php echo esc_html__('Go Pro', 'squarewoosync'); ?>
        </a>
      </div>
    </div>
<?php
  }





  /**
   * Handles the product update process.
   * 
   * @param int $product_id The product ID.
   * @return mixed
   */
  public function on_product_update($product_id, $data_to_import, $force = false)
  {


    $settings = get_option('square-woo-sync_settings', []);

    if (empty($settings['wooAuto']) && !$force) {
      return null;
    }

    $product = wc_get_product($product_id);


    if (!$product instanceof \WC_Product) {
      return null; // Optionally log this error.
    }

    $square_product_id = get_post_meta($product_id, 'square_product_id', true);
    $woo_data = $this->get_woo_product_data($product, $square_product_id, $data_to_import);

    if ($square_product_id && !empty($woo_data)) {
      return $this->update_square_product($square_product_id, $woo_data, $data_to_import);
    }
  }

  /**
   * Retrieves WooCommerce product data.
   * 
   * @param \WC_Product $product          WooCommerce product object.
   * @param string      $square_product_id Square product ID.
   * @param array       $data_to_import    Array of booleans for fields: e.g. ['title'=>true,'images'=>true, ...].
   * @return array
   */
  public function get_woo_product_data(\WC_Product $product, $square_product_id,  array $data_to_import = [])
  {
    $defaults = [
      'stock'       => true,
      'title'       => true,
      'description' => true,
      'price'       => true,
      'sku'         => true,
      'images'      => true,
      'categories'  => true,
    ];
    // Merge user-supplied with defaults
    $data_to_import = array_merge($defaults, $data_to_import);

    $woo_data = [
      'name'        => $product->get_name(),
      'description' => $product->get_description(),
      'variation_id' =>  get_post_meta($product->get_id(), 'square_variation_id', true),
      'variations'  => [],
      'id' => $product->get_id()
    ];

    if ($product->is_type('variable')) {
      $this->create_or_update_square_variations($product, $data_to_import);

      $validator = new ValidateWooProduct();

      foreach ($product->get_children() as $variation_id) {
        $variation = wc_get_product($variation_id);
        if (!$variation) {
          continue;
        }

        $validation_result = $validator->validateVariation($variation_id);

        if (empty($validation_result['success'])) {
          $variation_product_id = get_post_meta($variation_id, 'square_product_id', true);
          if ($variation_product_id) {
            // Include invalid variation anyway — but mark as invalid
            $variation_data = $this->format_variation_data($variation, $variation_product_id);
            $variation_data['is_valid'] = false;
            $variation_data['validation_reasons'] = $validation_result['reasons'] ?? [];
            $woo_data['variations'][] = $variation_data;
          }
          continue;
        }


        /**
         * Get raw “skip sync” meta.
         *
         * @var string|null $raw_skip_meta 'yes' if user checked “Do not sync/export to Square”, otherwise 'no' or null.
         */
        $raw_skip_meta = get_post_meta($variation_id, 'square_skip_sync', true);
        $skip_by_meta  = ($raw_skip_meta === 'yes');

        /**
         * Allow external code to override skip logic.
         *
         * @param bool       $skip_by_meta Whether skip is true based on meta.
         * @param int        $variation_id Variation ID.
         * @param WC_Product $variation    Variation product object (may be null; use ID to fetch if needed).
         */
        $should_skip = apply_filters('squarewoosync_skip_variation_sync', $skip_by_meta, $variation_id, $variation);

        if ($should_skip) {
          continue;
        }

        $variation_product_id = get_post_meta($variation->get_id(), 'square_product_id', true);
        $woo_data['variations'][] = $this->format_variation_data($variation, $variation_product_id);
      }
    } else {
      $woo_data['variations'][] = $this->format_variation_data($product, $square_product_id);
    }

    return $woo_data;
  }

  /**
   * Formats variation data for synchronization.
   * 
   * @param \WC_Product $product          WooCommerce product object.
   * @param string      $square_product_id Square product ID.
   * @return array
   */
  private function format_variation_data(\WC_Product $product, $square_product_id)
  {
    return [
      'price'     => $product->get_price(),
      'sku'       => $product->get_sku(),
      'stock'     => $product->get_stock_quantity(),
      'square_id' => $square_product_id,
      'variation_id' => get_post_meta($product->get_id(), 'square_variation_id', true),
      'id' => $product->get_id(),
    ];
  }

  /**
   * Updates the Square product with WooCommerce data.
   * 
   * @param string $square_product_id Square product ID.
   * @param array  $woo_data          WooCommerce product data.
   * @param array  $data_to_import    Array of booleans for fields: ['images'=>true, ...].
   * @return mixed
   */
  public function update_square_product($square_product_id, $woo_data, $data_to_import)
  {
    $square_helper = new SquareHelper();
    $woo_import = new WooImport();

    // 1) Retrieve existing Square object
    $square_product_data = $square_helper->get_square_item_details($square_product_id);

    if (!isset($square_product_data['object']) || !isset($square_product_data['object']['type'])) {
      return $square_product_data;
    }

    $square_obj = $square_product_data['object'];

    $product_id = $woo_data['id'] ?? null;
    if ($product_id) {
      $product = wc_get_product($product_id);
      if ($product && $square_obj['type'] === 'ITEM') {
        // === Add categories ===
        if (!empty($data_to_import['categories'])) {
          $terms = get_the_terms($product_id, 'product_cat');
          $categories_arr = [];

          if (!empty($terms) && !is_wp_error($terms)) {
            foreach ($terms as $t) {
              $cat_id = $woo_import->get_or_create_square_category($t, $square_helper);
              if ($cat_id) {
                $categories_arr[] = ['id' => $cat_id];
              }
            }
          }

          if (!empty($categories_arr)) {
            $square_obj['item_data']['categories'] = $categories_arr;
          }
        }

        // === Add images ===
        if (!empty($data_to_import['images'])) {
          $square_image_ids = $this->upload_product_images_to_square($product, $square_helper);
          if (!empty($square_image_ids)) {
            $square_obj['item_data']['image_ids'] = $square_image_ids;
          }
        }
      }
    }

    // === Filter and preserve variations ===
    if (
      $square_obj['type'] === 'ITEM' &&
      isset($square_obj['item_data']['variations']) &&
      is_array($square_obj['item_data']['variations'])
    ) {
      $existing_variations = $square_obj['item_data']['variations'];
      $final_variations = [];

      foreach ($woo_data['variations'] as $woo_var) {
        $is_valid = $woo_var['is_valid'] ?? true;

        if (!empty($woo_var['variation_id'])) {
          $square_id = $woo_var['variation_id'];
        } else {
          $square_id = $woo_var['square_id'];
        }


        if ($is_valid) {
          // Find and retain matching variation from existing
          foreach ($existing_variations as $existing_var) {
            if ($existing_var['id'] === $square_id) {
              $final_variations[] = $existing_var;
              continue 2;
            }
          }

          // If not found in existing, fallback to creating minimal stub
          if ($square_id) {
            $final_variations[] = [
              'id' => $square_id,
              'type' => 'ITEM_VARIATION',
              'present_at_all_locations' => true,
            ];
          }
        } elseif ($square_id) {
          // Variation is invalid but already in Square → retain as-is
          foreach ($existing_variations as $existing_var) {
            if ($existing_var['id'] === $square_id) {
              $final_variations[] = $existing_var;
              continue 2;
            }
          }
        }
        // If variation is invalid and has no Square link, skip entirely
      }

      $square_obj['item_data']['variations'] = $final_variations;
    }

    // === Single variation ID fallback
    if ($square_obj['type'] === 'ITEM' && count($woo_data['variations']) === 1) {
      $woo_data['variations'][0]['square_id'] = $square_obj['item_data']['variations'][0]['id'] ?? null;
    } elseif ($square_obj['type'] === 'ITEM_VARIATION' && count($woo_data['variations']) === 1) {
      $woo_data['variations'][0]['square_id'] = $square_obj['item_variation_data']['id'] ?? null;
    }

    return $square_helper->update_square_product($woo_data, $square_obj, $data_to_import);
  }




  public function export_to_square($new_status, $old_status, $post)
  {
    // Ensure we're dealing with a newly published product
    if ($post->post_type !== 'product' || $new_status !== 'publish') {
      return;
    }

    if (get_post_meta($post->ID, '_pmxi_import_id', true)) {
      return;
    }

    // (Optional) Check if the product was just published today
    $published_date = get_the_date('Y-m-d', $post);
    $current_date   = current_time('Y-m-d');
    if ($published_date !== $current_date) {
      // Not a "brand new" publish; skip
      return;
    }

    // (Optional) Check your plugin settings
    $settings = get_option('square-woo-sync_settings', []);
    if (empty($settings['wooAuto']['autoCreateProduct']) || !$settings['wooAuto']['autoCreateProduct']) {
      return;
    }

    // If the product already has a Square ID, skip
    $product_id        = $post->ID;
    $square_product_id = get_post_meta($product_id, 'square_product_id', true);
    if ($square_product_id) {
      return;
    }

    // Now we schedule a single action via Action Scheduler
    // We'll run "my_delayed_export_hook" 30 seconds from now,
    // passing the product ID as an argument.
    $delay_in_seconds = 30;
    if (! as_next_scheduled_action('squarewoosync_delayed_export', [$product_id])) {
      as_schedule_single_action(
        time() + $delay_in_seconds,      // run 30s from now
        'squarewoosync_delayed_export',        // the hook name
        ['product_id' => $product_id],                   // args
        'squarewoo-group'               // (optional) action group
      );
    }
  }

  /**
   * Runs the final export logic for a single product ID.
   */
  public function delayed_export_to_square($product_id)
  {
    // Ensure WooCommerce loaded
    if (! class_exists('WooCommerce')) {
      return;
    }

    // Grab the product
    $product = wc_get_product($product_id);
    if (! $product) {
      return;
    }

    if (get_post_meta($product_id, '_pmxi_import_id', true)) {
      return;
    }

    // Check if it already has Square ID
    $square_product_id = get_post_meta($product_id, 'square_product_id', true);
    if ($square_product_id) {
      return;
    }

    // Check if auto-create is enabled
    $settings = get_option('square-woo-sync_settings', []);
    if (empty($settings['wooAuto']['autoCreateProduct']) || ! $settings['wooAuto']['autoCreateProduct']) {
      return;
    }

    // Logging
    $uniqueProcessId = wp_generate_uuid4();
    Logger::log('info', 'Attempting auto export for product.', [
      'product_id' => $product_id,
      'process_id' => $uniqueProcessId
    ]);

    // Use your exporter
    $exporter = new WooImport();

    // Run the import
    $result = $exporter->import_products($uniqueProcessId, [$product], 1);

    // Possibly sync inventory
    $exporter->update_square_inventory_counts([$product]);

    // Check results
    if (is_wp_error($result)) {
      $error_message = $result->get_error_message();
      Logger::log('error', 'Error exporting product to Square: ' . $error_message, [
        'parent_id' => $uniqueProcessId,
        'product_id' => $product_id
      ]);
      return;
    }

    // Check if product failed validation or formatting
    if (! empty($result['failures'])) {
      $fail        = $result['failures'][0];
      $fail_name   = $fail['title'];
      $fail_reasons = implode('; ', $fail['errors']);

      Logger::log('error', "Failed to export product ID {$fail['product_id']} ({$fail_name}): {$fail_reasons}", [
        'parent_id' => $uniqueProcessId,
        'product_id' => $fail['product_id']
      ]);
      return;
    }

    // Otherwise, see if Square returned a success object
    if (! empty($result['square_responses'])) {
      foreach ($result['square_responses'] as $resp) {
        if (! empty($resp['success']) && ! empty($resp['data']['objects'])) {
          foreach ($resp['data']['objects'] as $object) {
            if (isset($object['type']) && $object['type'] === 'ITEM' && ! empty($object['item_data']['name'])) {
              $product_name = $object['item_data']['name'];
              Logger::log('success', 'Product exported and linked to Square: ' . $product_name, [
                'parent_id' => $uniqueProcessId,
                'product_id' => $product_id
              ]);
              // Optionally set a transient for admin notice
              set_transient(
                'square_sync_success',
                'Product "' . $product_name . '" was successfully created in Square and linked for automatic syncing (if enabled).',
                30
              );
              return; // Done
            }
          }
        }
      }
      Logger::log('error', 'Export may have failed or no ITEM objects returned.', [
        'parent_id' => $uniqueProcessId,
        'product_id' => $product_id
      ]);
    } else {
      Logger::log('error', 'Unexpected response format or no Square responses.', [
        'parent_id' => $uniqueProcessId,
        'product_id' => $product_id
      ]);
    }
  }



  /**
   * Registers a new bulk action 'Sync to Square' in the Products admin list.
   *
   * @param array $bulk_actions Existing bulk actions.
   * @return array Modified bulk actions.
   */
  public function register_sync_to_square_bulk_action($bulk_actions)
  {
    $bulk_actions['squarewoosync_schedule_sync_to_square'] = __('Sync to Square', 'squarewoosync');
    return $bulk_actions;
  }

  /**
   * Handles the 'Sync to Square' bulk action by scheduling a cron job.
   *
   * @param string $redirect_to URL to redirect to after action.
   * @param string $action The action being performed.
   * @param array $post_ids Array of selected product IDs.
   * @return string Modified redirect URL.
   */
  public function handle_sync_to_square_bulk_action($redirect_to, $action, $post_ids)
  {
    if ($action !== 'squarewoosync_schedule_sync_to_square') {
      return $redirect_to;
    }

    // Check if the cron job is already scheduled
    $next_scheduled = wp_next_scheduled('squarewoosync_sync_bulk_products');

    if ($next_scheduled) {
      // Cron job is already scheduled, so display a notice and do not schedule a new one
      $redirect_to = add_query_arg('squarewoosync_sync_cron_already_scheduled', '1', $redirect_to);
      return $redirect_to;
    }

    $uniqueProcessId = wp_generate_uuid4();

    // Schedule the cron job if no other job is scheduled
    wp_schedule_single_event(time() + 10, 'squarewoosync_sync_bulk_products', array(array('process_id' => $uniqueProcessId, 'post_ids' => $post_ids)));

    Logger::log('info', 'Product sync to Square initiated', array('process_id' => $uniqueProcessId));

    // Add a success notice for the user by redirecting with a query parameter
    $redirect_to = add_query_arg('squarewoosync_sync_cron_scheduled', '1', $redirect_to);

    return $redirect_to;
  }

  /**
   * Displays an admin notice after the bulk action is performed.
   */
  public function sync_to_square_bulk_action_admin_notice()
  {
    // Check if a cron job was successfully scheduled
    if (!empty($_REQUEST['squarewoosync_sync_cron_scheduled'])) {
      echo '<div class="notice notice-success is-dismissible"><p>' . __('Sync to Square initiated.', 'squarewoosync') . '</p></div>';
    }

    // Check if a cron job is already scheduled
    if (!empty($_REQUEST['squarewoosync_sync_cron_already_scheduled'])) {
      echo '<div class="notice notice-warning is-dismissible"><p>' . __('A sync job is already running. Please wait until the current job finishes.', 'squarewoosync') . '</p></div>';
    }
  }

  /**
   * Cron job function to sync selected products to Square.
   *
   * @param array $args Array containing 'process_id' and 'post_ids'.
   */
  public function squarewoosync_sync_products($args)
  {
    // Ensure the argument is an array and contains the expected keys
    if (!is_array($args) || !isset($args['process_id']) || !isset($args['post_ids'])) {
      // Handle error or return early if arguments are incorrect
      return;
    }

    $process_id = $args['process_id'];
    $post_ids = $args['post_ids'];

    // Set a transient to indicate the job is running
    set_transient('squarewoosync_sync_running', true, 15 * MINUTE_IN_SECONDS);

    $products = array();
    foreach ($post_ids as $post_id) {
      $product = wc_get_product($post_id);
      if ($product) {
        $products[] = $product;
      }
    }

    if (!empty($products)) {
      Logger::log('info', 'Started syncing products to Square', array('parent_id' => $process_id, 'product_count' => count($products)));

      $synced_count = 0;
      $error_count = 0;

      foreach ($products as $product) {
        $product_id = $product->get_id();
        $data_to_import = array(
          'stock' => true,
          'title' => true,
          'description' => true,
          'price' => true,
          'sku' => true,
          'images' => true,
          'categories' => true,
        );

        $result = $this->on_product_update($product_id, $data_to_import, true);

        if ($result && $this->is_sync_successful($result)) {
          Logger::log('success', 'Successfully synced: ' . $product->get_name() . ' to Square', array('product_id' => $product_id, 'parent_id' => $process_id));
          $synced_count++;
        } else {
          Logger::log('error', 'Failed to sync: ' . $product->get_name() . ' to Square: ' . json_encode($result), array('product_id' => $product_id, 'parent_id' => $process_id));
          $error_count++;
        }
      }

      // Log the summary
      Logger::log('info', 'Product sync to Square completed', array('parent_id' => $process_id, 'synced_count' => $synced_count, 'error_count' => $error_count));
    } else {
      Logger::log('info', 'No products found to sync', array('parent_id' => $process_id));
    }

    // Job finished, delete the transient
    delete_transient('squarewoosync_sync_running');
  }


  /**
   * Create or remove Square variations for a variable Woo product.
   * 
   * @param \WC_Product $product
   * @param array       $data_to_import  e.g. ['title'=>true, 'price'=>true, 'images'=>true, ...]
   * @return array
   */
  public function create_or_update_square_variations(\WC_Product $product, array $data_to_import = [])
  {
    // Provide defaults if not passed
    $defaults = [
      'stock'       => true,
      'title'       => true,
      'description' => true,
      'price'       => true,
      'sku'         => true,
      'images'      => true,
      'categories'  => true,
    ];
    $data_to_import = array_merge($defaults, $data_to_import);

    // Must be variable
    if (!$product->is_type('variable')) {
      return ['success' => false, 'message' => 'Not a variable product'];
    }

    $square_helper = new SquareHelper();
    $parent_sq_id  = $product->get_meta('square_product_id');
    if (!$parent_sq_id) {
      return ['success' => false, 'message' => 'Parent item not linked to Square'];
    }

    // 2) Fetch existing parent ITEM from Square
    $resp = $square_helper->square_api_request(
      '/catalog/batch-retrieve',
      'POST',
      ['object_ids' => [$parent_sq_id]]
    );
    if (empty($resp['success']) || empty($resp['data']['objects'][0])) {
      return ['success' => false, 'message' => 'Failed retrieving square item'];
    }

    $square_item    = $resp['data']['objects'][0];
    $square_version = $square_item['version'] ?? null;
    if ($square_item['type'] !== 'ITEM') {
      return ['success' => false, 'message' => 'Square object is not an ITEM type'];
    }

    // Gather existing variations + item options
    $existing_variations   = $square_item['item_data']['variations']   ?? [];
    $existing_item_options = $square_item['item_data']['item_options'] ?? [];

    $final_variations = [];

    // Possibly fetch item_options
    $woo_import            = new WooImport();
    $square_options_catalog = $woo_import->fetch_square_item_options($square_helper);

    // Loop over WC children
    $variation_ids = $product->get_children();
    foreach ($variation_ids as $child_id) {
      $child_var  = wc_get_product($child_id);
      if (!$child_var) {
        continue;
      }

      $validator = new ValidateWooProduct();
      $validation_result = $validator->validateVariation($child_id);

      if (empty($validation_result['success'])) {
        // Check if variation already exists in Square
        $existing_square_id = $child_var->get_meta('square_product_id', true);
        if ($existing_square_id) {
          // Try to find this variation in Square's current catalog snapshot
          foreach ($existing_variations as $existing_var) {
            if (!empty($existing_var['id']) && $existing_var['id'] === $existing_square_id) {
              $final_variations[] = $existing_var; // Retain untouched
              break;
            }
          }
        }
        continue; // skip invalid variation logic
      }

      /**
       * Get raw “skip sync” meta.
       *
       * @var string|null $raw_skip_meta 'yes' if user checked “Do not sync/export to Square”, otherwise 'no' or null.
       */
      $raw_skip_meta = get_post_meta($child_id, 'square_skip_sync', true);
      $skip_by_meta  = ($raw_skip_meta === 'yes');

      /**
       * Allow external code to override skip logic.
       *
       * @param bool       $skip_by_meta Whether skip is true based on meta.
       * @param int        $variation_id Variation ID.
       * @param WC_Product $variation    Variation product object (may be null; use ID to fetch if needed).
       */
      $should_skip = apply_filters('squarewoosync_skip_variation_sync', $skip_by_meta, $child_id, $child_var);

      if ($should_skip) {
        continue;
      }

      $child_sq_id = $child_var->get_meta('square_product_id', true);

      // Build item_option_values (always needed)
      $item_option_values = [];
      foreach ($child_var->get_variation_attributes() as $attr_key => $attr_value) {
        $taxonomy   = str_replace('attribute_', '', $attr_key);
        $attr_label = wc_attribute_label($taxonomy);
        $term_obj   = get_term_by('slug', $attr_value, $taxonomy);
        $attr_slug  = $term_obj ? $term_obj->slug : $attr_value;
        $term_name  = $term_obj ? $term_obj->name : $attr_value;


        $option_id = $woo_import->get_square_option_id($attr_label, $square_options_catalog, $square_helper, $term_name);
        if (!$option_id) {
          // skip this variation entirely if we can't map/create the option
          continue 2;
        }

        // Make sure parent references option
        if (!in_array($option_id, array_column($existing_item_options, 'item_option_id'))) {
          $existing_item_options[] = ['item_option_id' => $option_id];
        }

        $option_val_id = $woo_import->get_square_option_value_id($option_id, $term_name, $square_options_catalog, $square_helper);
        if (!$option_val_id) {
          continue 2;
        }

        $item_option_values[] = [
          'item_option_id'       => $option_id,
          'item_option_value_id' => $option_val_id,
        ];
      }

      // Variation name for Square display
      $att_values = [];
      foreach ($child_var->get_variation_attributes() as $attr_key => $attr_value) {
        $taxonomy   = str_replace('attribute_', '', $attr_key);
        $term_obj   = get_term_by('slug', $attr_value, $taxonomy);
        $attr_label = wc_attribute_label($taxonomy);
        $val_name   = $term_obj ? $term_obj->name : $attr_value;
        $att_values[] = "{$attr_label}: {$val_name}";
      }
      // $variation_name = !empty($att_values) ? implode(', ', $att_values) : 'Variation ' . $child_id;

      // Price in cents if we want it (or 0 if new variation)
      $price_cents = (int) round($child_var->get_price() * 100);

      // Sku
      $sku = $child_var->get_sku();

      // Variation image(s)
      $variation_image_ids = [];
      if ($child_var->get_image_id()) {
        // We'll upload only if new variation or $data_to_import['images'] => true (if existing)
        // but let's do the upload logic once, then decide to set it or not below
        $sq_img_id = $woo_import->upload_image_to_square($child_var->get_image_id(), $square_helper, false);
        if ($sq_img_id) {
          $variation_image_ids[] = $sq_img_id;
        }
      }

      // == CASE A: Brand-new variation in Square ==
      // (no square_product_id => we push everything unconditionally)
      if (empty($child_sq_id)) {
        $temp_id = '#var_' . $child_id . '_' . wp_generate_uuid4();
        update_post_meta($child_id, '_tmp_square_var_id', $temp_id);

        // Always set all fields for new variation
        $newVar = [
          'id'   => $temp_id,
          'type' => 'ITEM_VARIATION',
          'present_at_all_locations' => true,
          'item_variation_data' => [
            'sku'  => $sku,            // always use Woo SKU
            'pricing_type' => 'FIXED_PRICING',
            'price_money' => [
              'amount'   => $price_cents,
              'currency' => get_woocommerce_currency(),
            ],
            'track_inventory' => $child_var->managing_stock(),
            'sellable'  => true,
            'stockable' => true,
            'image_ids' => $variation_image_ids,
            'item_option_values' => $item_option_values,
          ]
        ];
        $final_variations[] = $newVar;
      }

      // == CASE B: Existing variation in Square ==
      else {
        // Find the existing variation object
        $found_existing = null;
        foreach ($existing_variations as $sqv) {
          if (!empty($sqv['id']) && $sqv['id'] === $child_sq_id) {
            $found_existing = $sqv;
            break;
          }
        }

        if ($found_existing) {
          // Conditionals: only update if $data_to_import[...] is true. Otherwise, do not overwrite Square's existing value.

          if (!empty($data_to_import['price'])) {
            $found_existing['item_variation_data']['price_money'] = [
              'amount'   => $price_cents,
              'currency' => get_woocommerce_currency(),
            ];
          }
          if (!empty($data_to_import['sku'])) {
            $found_existing['item_variation_data']['sku'] = $sku;
          }
          if (!empty($data_to_import['images']) && !empty($variation_image_ids)) {
            $found_existing['item_variation_data']['image_ids'] = $variation_image_ids;
          }
          if (!empty($data_to_import['stock'])) {
            $found_existing['item_variation_data']['track_inventory'] = $child_var->managing_stock();
          }

          // item_option_values = always updated to reflect current attribute mapping
          $found_existing['item_variation_data']['item_option_values'] = $item_option_values;

          $final_variations[] = $found_existing;
        } else {
          // Possibly treat it as new, or skip if we can't find it
        }
      }
    }

    // Optionally remove old variations that no longer exist in Woo

    // Filter item_options for only used options
    $used_option_ids = [];
    foreach ($final_variations as $var_data) {
      $ovals = $var_data['item_variation_data']['item_option_values'] ?? [];
      foreach ($ovals as $o) {
        $used_option_ids[] = $o['item_option_id'];
      }
    }
    $filtered_item_options = [];
    foreach ($existing_item_options as $opt_entry) {
      if (
        !empty($opt_entry['item_option_id']) &&
        in_array($opt_entry['item_option_id'], $used_option_ids, true)
      ) {
        $filtered_item_options[] = $opt_entry;
      }
    }
    // Limit to 6
    $filtered_item_options = array_slice($filtered_item_options, 0, 6);

    // Assign final arrays
    $square_item['item_data']['item_options'] = $filtered_item_options;
    $square_item['item_data']['variations']   = $final_variations;

    // If $data_to_import['categories'], assign categories; otherwise skip
    if (!empty($data_to_import['categories'])) {
      $terms = get_the_terms($product->get_id(), 'product_cat');
      $categories_arr = [];
      if (!empty($terms) && !is_wp_error($terms)) {
        foreach ($terms as $t) {
          $cat_id = $woo_import->get_or_create_square_category($t, $square_helper);
          if ($cat_id) {
            $categories_arr[] = ['id' => $cat_id];
          }
        }
      }
      if (!empty($categories_arr)) {
        $square_item['item_data']['categories'] = $categories_arr;
      }
    } else {
      // Remove existing categories if we're not updating?
      if (isset($square_item['item_data']['categories'])) {
        unset($square_item['item_data']['categories']);
      }
    }

    // Upsert final
    $payload = [
      'idempotency_key' => wp_generate_uuid4(),
      'batches' => [
        [
          'objects' => [
            [
              'id'      => $square_item['id'],
              'type'    => 'ITEM',
              'version' => $square_version,
              'item_data' => $square_item['item_data'],
            ]
          ]
        ]
      ]
    ];
    $upsert = $square_helper->square_api_request('/catalog/batch-upsert', 'POST', $payload);
    if (empty($upsert['success'])) {
      return ['success' => false, 'message' => 'Upsert failed: ' . ($upsert['error'] ?? '')];
    }

    // Map #temp IDs to real IDs if new variations were created
    if (!empty($upsert['data']['id_mappings'])) {
      foreach ($upsert['data']['id_mappings'] as $map) {
        $client_id = $map['client_object_id'];
        if (strpos($client_id, '#var_') === 0) {
          $real_id  = $map['object_id'];
          $child_id = $this->wc_find_variation_by_temp_id($client_id);
          if ($child_id) {
            update_post_meta($child_id, 'square_product_id', $real_id);
            delete_post_meta($child_id, '_tmp_square_var_id');
          }
        }
      }
    }

    return ['success' => true, 'message' => 'Successfully updated item + variations in Square'];
  }



  /**
   * Helper to find variation by the temp ID we stored in _tmp_square_var_id
   */
  private function wc_find_variation_by_temp_id($temp_id)
  {
    global $wpdb;
    return (int) $wpdb->get_var($wpdb->prepare("
        SELECT post_id
        FROM {$wpdb->postmeta}
        WHERE meta_key = '_tmp_square_var_id'
        AND meta_value = %s
        LIMIT 1
    ", $temp_id));
  }
}

// Display the WooCommerce notice
add_action('admin_notices', function () {
  if ($message = get_transient('square_sync_success')) {
    echo '<div class="notice notice-success is-dismissible">';
    echo '<p>' . esc_html($message) . '</p>';
    echo '</div>';
    delete_transient('square_sync_success');
  }
});
