<?php
/**
 * Square API Integration Class
 *
 * @package FunnelKit_Square
 */

namespace FKWCSQ\Includes;

use FKWCSQ\Square;
use FKWCSQ\Includes\Modules\Connection\MiddleWare;
use FKWCSQ\Includes\Traits\Instance;
use Square\Models\CatalogItem;
use Square\Models\CatalogItemVariation;
use Square\Models\CatalogObject;
use Square\Models\CatalogObjectType;
use Square\Models\UpsertCatalogObjectRequest;
use Square\SquareClient;
use Square\Environment;
use Square\Exceptions\ApiException;
use WP_Error;
use Exception;

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

/**
 * API Class
 */
class API {
	use Instance;

	/**
	 * Square client instance
	 *
	 * @var SquareClient
	 */
	protected $client;

	/**
	 * Last environment used for client initialization
	 *
	 * @var string|null
	 */
	protected $last_environment;

	/**
	 * Square settings
	 *
	 * @var array
	 */
	protected $settings;

	/**
	 * Whether to enable logging
	 *
	 * @var bool
	 */
	protected $logging_enabled;

	/**
	 * API endpoints
	 *
	 * @var array
	 */
	protected $endpoints = array();

	/**
	 * Initialize the API
	 */
	public static function init() {
		$instance = self::get_instance();
		// Initialize any necessary hooks or setup here
		$instance->maybe_init_client();
	}

	/**
	 * Constructor
	 */
	private function __construct() {
		// Load settings
		$this->settings        = get_option( 'fkwcsq_settings', array() );
		$this->logging_enabled = isset( $this->settings['enable_logging'] ) && $this->settings['enable_logging'] === 'yes';

		// Set up API endpoints
		$this->setup_endpoints();

		// Initialize client if possible
		$this->maybe_init_client();
	}

	/**
	 * Set up API endpoints
	 */
	private function setup_endpoints() {

		$this->endpoints = array(
			'payments'  => Square::get_api_url() . '/v2/payments',
			'refunds'   => Square::get_api_url() . '/v2/refunds',
			'customers' => Square::get_api_url() . '/v2/customers',
			'locations' => Square::get_api_url() . '/v2/locations',
			'catalog'   => Square::get_api_url() . '/v2/catalog',
			'inventory' => Square::get_api_url() . '/v2/inventory',
			'orders'    => Square::get_api_url() . '/v2/orders',
		);
	}

	/**
	 * Initialize Square client if not already initialized
	 *
	 * @return bool True if client is initialized, false otherwise
	 * @since 1.0.0
	 * @access public
	 */
	public function maybe_init_client() {
		// Get current environment
		$current_environment = \FKWCSQ\Square::is_sandbox_mode() ? 'sandbox' : 'production';

		// Check if environment has changed since last initialization
		if ( $this->client && $this->last_environment !== $current_environment ) {
			// Environment changed, reset client
			$this->client           = null;
			$this->last_environment = null;
		}

		if ( $this->client ) {
			return true;
		}

		$tokens = \FKWCSQ\Square::get_tokens();
		if ( ! $tokens || ! isset( $tokens['access_token'] ) ) {
			return false;
		}

		try {
			$this->client = new SquareClient(
				array(
					'accessToken' => $tokens['access_token'],
					'environment' => Square::is_sandbox_mode() ? Environment::SANDBOX : Environment::PRODUCTION,
				)
			);

			$this->last_environment = $current_environment;

			return true;
		} catch ( Exception $e ) {
			Logger::error( 'Failed to initialize Square client: ' . $e->getMessage() );

			return false;
		}
	}

	/**
	 * Gets the client instance.
	 *
	 * @return object The client instance.
	 * @since 1.0.0
	 * @access public
	 */
	public function get_client() {
		$this->maybe_init_client();

		return $this->client;
	}

	/**
	 * Stored plugin options (WooCommerce → Settings → Square).
	 *
	 * @return array
	 */
	public function get_plugin_settings() {
		return get_option( 'fkwcsq_settings', array() );
	}

	/**
	 * Force re-initialization of the client
	 * Useful when switching environments or refreshing tokens
	 *
	 * @return bool True if re-initialization successful, false otherwise
	 * @since 1.0.0
	 * @access public
	 */
	public function force_reinit_client() {
		// Reset client instance and environment tracking
		$this->client           = null;
		$this->last_environment = null;

		// Re-initialize with current environment
		return $this->maybe_init_client();
	}

	/**
	 * Get current environment status
	 *
	 * @return array Array containing current environment info
	 * @since 1.0.0
	 * @access public
	 */
	public function get_environment_status() {
		$current_environment = \FKWCSQ\Square::is_sandbox_mode() ? 'sandbox' : 'production';

		return array(
			'current_environment'          => $current_environment,
			'last_initialized_environment' => $this->last_environment,
			'client_initialized'           => ! empty( $this->client ),
			'client_class'                 => $this->client ? get_class( $this->client ) : null,
		);
	}

	/**
	 * Clear environment cache and force re-initialization
	 * This should be called when settings are updated to ensure proper environment switching
	 *
	 * @return bool True if cache cleared successfully
	 * @since 1.0.0
	 * @access public
	 */
	public function clear_environment_cache() {
		// Clear client and environment tracking
		$this->client           = null;
		$this->last_environment = null;

		// Clear any cached locations or other environment-specific data
		if ( \FKWCSQ\Square::is_sandbox_mode() ) {
			delete_option( 'fkwcsq_sandbox_locations' );
		} else {
			delete_option( 'fkwcsq_locations' );
		}

		return true;
	}

	/**
	 * Enhanced error logging with context
	 *
	 * @param string $message Error message
	 * @param array  $context Additional context data
	 * @param string $level Log level (error, warning, debug)
	 */
	private function log( $message, $context = array(), $level = 'error' ) {
		if ( ! $this->logging_enabled ) {
			return;
		}

		Logger::log( $message, $level );
	}

	/**
	 * Refresh access token
	 *
	 * @return bool
	 */
	public function refresh_access_token() {
		return MiddleWare::get_instance()->refresh_access_token_middleware();
	}

	/**
	 * Log debug message
	 *
	 * @param string $message Message to log
	 */
	private function log_debug( $message ) {
		if ( $this->logging_enabled ) {
			Logger::debug( $message );
		}
	}

	/**
	 * Log error message
	 *
	 * @param string $message Message to log
	 */
	private function log_error( $message ) {
		if ( $this->logging_enabled ) {
			Logger::error( $message );
		}
	}

	/**
	 * Make API request using Square SDK
	 *
	 * @param string $endpoint API endpoint
	 * @param array  $args Request arguments
	 * @param string $method HTTP method
	 * @param int    $max_retries Maximum number of retries
	 * @param int    $retry_delay Initial delay between retries in seconds
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 */
	public function make_request( $endpoint, $args = array(), $method = 'GET', $max_retries = 3, $retry_delay = 1 ) {
		if ( ! $this->maybe_init_client() ) {
			return new WP_Error( 'client_not_initialized', 'Square client not initialized' );
		}
		$tokens = \FKWCSQ\Square::get_tokens();
		if ( empty( $tokens['access_token'] ) ) {
			$this->log_error( 'No access token found' );

			return new \WP_Error( 'no_token', 'No access token found' );
		}

		$attempt    = 0;
		$last_error = null;

		while ( $attempt < $max_retries ) {
			++$attempt;
			try {
				// Parse endpoint to determine which API to use
				$api = $this->get_api_from_endpoint( $endpoint );
				if ( ! $api ) {
					return new WP_Error( 'invalid_endpoint', 'Invalid API endpoint' );
				}

				// Make the request using the appropriate API method
				$response = $this->execute_api_request( $api, $endpoint, $args, $method );

				if ( is_wp_error( $response ) ) {
					$last_error = $response;

					// Check if we should retry
					if ( $this->should_retry_request( $response, $attempt, $max_retries ) ) {
						$delay = $retry_delay * pow( 2, $attempt - 1 );
						sleep( $delay );
						continue;
					}

					return $response;
				}

				return $response;

			} catch ( ApiException $e ) {
				$last_error = new WP_Error( 'square_api_error', $e->getMessage(), array( 'exception' => $e ) );

				// Check if we should retry
				if ( $this->should_retry_exception( $e ) ) {
					$delay = $retry_delay * pow( 2, $attempt - 1 );
					sleep( $delay );
					continue;
				}

				return $last_error;
			} catch ( Exception $e ) {
				$last_error = new WP_Error( 'request_error', $e->getMessage() );

				return $last_error;
			}
		}

		return $last_error;
	}


	/**
	 * Get the appropriate API instance from endpoint
	 *
	 * @param string $endpoint API endpoint
	 *
	 * @return object|null API instance or null if not found
	 */
	private function get_api_from_endpoint( $endpoint ) {
		$api_map = array(
			'/v2/catalog'   => 'catalog',
			'/v2/customers' => 'customers',
			'/v2/locations' => 'locations',
			'/v2/orders'    => 'orders',
			'/v2/payments'  => 'payments',
			'/v2/refunds'   => 'refunds',
			'/v2/inventory' => 'inventory',
		);

		foreach ( $api_map as $prefix => $api_name ) {
			if ( strpos( $endpoint, $prefix ) === 0 ) {
				$method = 'get' . ucfirst( $api_name ) . 'Api';

				return $this->client->$method();
			}
		}

		return null;
	}

	/**
	 * Execute API request using the appropriate method
	 *
	 * @param object $api API instance
	 * @param string $endpoint API endpoint
	 * @param array  $args Request arguments
	 * @param string $method HTTP method
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 */
	private function execute_api_request( $api, $endpoint, $args, $method ) {
		// Extract the action from the endpoint
		$parts  = explode( '/', trim( $endpoint, '/' ) );
		$action = end( $parts );

		// Convert method to camelCase for SDK method names
		$method_name = strtolower( $method );
		$action      = str_replace( '-', '', ucwords( $action, '-' ) );

		// Build the method name
		$method_name = $method_name . $action;

		// Check if the method exists
		if ( ! method_exists( $api, $method_name ) ) {
			return new WP_Error( 'invalid_method', 'Invalid API method: ' . $method_name );
		}

		try {
			// Execute the method
			$result = $api->$method_name( $args );

			return $result->getResult();
		} catch ( ApiException $e ) {
			return new WP_Error( 'api_error', $e->getMessage(), array( 'exception' => $e ) );
		}
	}

	/**
	 * Determine if a request should be retried based on error
	 *
	 * @param WP_Error $error Error object
	 * @param int      $attempt Current attempt number
	 * @param int      $max_retries Maximum number of retries
	 *
	 * @return bool Whether to retry the request
	 */
	private function should_retry_request( $error, $attempt, $max_retries ) {
		$error_code = $error->get_error_code();

		// Retry on these error codes
		$retry_codes = array(
			'rate_limit_exceeded',
			'timeout',
			'connection_error',
		);

		return in_array( $error_code, $retry_codes, true ) && $attempt < $max_retries;
	}

	/**
	 * Determine if an exception should be retried
	 *
	 * @param ApiException $e Exception
	 * @param int          $attempt Current attempt number
	 * @param int          $max_retries Maximum number of retries
	 *
	 * @return bool Whether to retry the request
	 */
	private function should_retry_exception( $e ) {
		// Square ApiException doesn't have getErrors() method
		// We'll retry based on the exception message and code instead
		$error_code    = $e->getCode();
		$error_message = $e->getMessage();

		// Retry on specific error codes that indicate temporary issues
		if ( $error_code >= 500 || $error_code === 429 ) {
			return true;
		}

		// Retry on specific error messages that indicate temporary issues
		$retryable_messages = array(
			'timeout',
			'temporary',
			'rate limit',
			'server error',
			'connection',
		);

		foreach ( $retryable_messages as $retryable_message ) {
			if ( stripos( $error_message, $retryable_message ) !== false ) {
				return true;
			}
		}
	}

	/**
	 * Determine whether the application fee is supported
	 *
	 * Square application fees are only supported for USD currency
	 * Similar to Stripe Connect, Square has limitations on which currencies support application fees
	 *
	 * @since 1.0.0
	 *
	 * @param string $currency Currency code (optional, defaults to order/cart currency)
	 *
	 * @return bool True if application fees are supported, false otherwise
	 */
	public function is_application_fee_supported( $currency = '' ) {
		// Check if Sublium plugin class exists
		if ( ! class_exists( '\Sublium_WCS\Includes\Main\Gateways' ) ) {
			return false;
		}

		// Check if get_instance method exists
		if ( ! method_exists( '\Sublium_WCS\Includes\Main\Gateways', 'get_instance' ) ) {
			return false;
		}

		$gateways_instance = \Sublium_WCS\Includes\Main\Gateways::get_instance();

		// Check if get_gateway method exists
		if ( ! method_exists( $gateways_instance, 'get_gateway' ) ) {
			return false;
		}

		$gateway = $gateways_instance->get_gateway( 'fkwcsq_square' );

		// Check if gateway was retrieved successfully
		if ( ! $gateway ) {
			return false;
		}

		// Check if gateway has is_connected method and is connected
		if ( ! method_exists( $gateway, 'is_connected' ) || ! $gateway->is_connected() ) {
			return false;
		}

		// If no currency provided, try to get from current context
		if ( empty( $currency ) ) {
			// Try to get currency from WooCommerce
			$currency = get_woocommerce_currency();
		}

		// Square application fees are only supported for USD
		// Reference: https://developer.squareup.com/docs/payments-api/take-payments
		return strtoupper( $currency ) === 'USD';
	}

	/**
	 * Process payment
	 *
	 * @param \WC_Order $order
	 * @param string    $source_id
	 * @param string    $capture_method
	 *
	 * @return array|\WP_Error
	 * @throws \Exception If payment processing fails.
	 */
	public function process_payment( $order, $source_id, $capture_method = 'automatic' ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new \Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Prepare payment data
			$amount          = $order->get_total();
			$currency        = $order->get_currency();
			$amount_in_cents = (int) ( $amount * 100 );

			$payment_data = array(
				'source_id'       => $source_id,
				'amount_money'    => array(
					'amount'   => $amount_in_cents,
					'currency' => $currency,
				),
				'idempotency_key' => fkwcsq_idempotency_key( 'fkwcsq_payment_' ),
				'autocomplete'    => $capture_method === 'automatic',
			);

			$response = $this->make_request( '/v2/payments', $payment_data, 'POST' );
			if ( is_wp_error( $response ) ) {
				throw new \Exception( $response->get_error_message() );
			}

			// Update order with payment details
			if ( isset( $response['payment'] ) ) {
				$payment = $response['payment'];
				$order->update_meta_data( '_square_payment_id', $payment['id'] );
				$order->update_meta_data( '_square_payment_status', $payment['status'] );
				$order->save();
			}

			return $response;

		} catch ( \Exception $e ) {
			$this->log_error( 'Payment error: ' . $e->getMessage() );
			throw $e;
		}
	}

	/**
	 * Capture authorized payment
	 *
	 * @param string $payment_id Square payment ID
	 *
	 * @return array|\WP_Error
	 * @throws \Exception If capture processing fails.
	 */
	public function capture_payment( $payment_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new \Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}
			$body = new \Square\Models\CompletePaymentRequest();

			$payments_api = $this->client->getPaymentsApi();
			$response     = $payments_api->completePayment( $payment_id, $body );

			if ( $response->isSuccess() ) {
				$payment = $response->getResult()->getPayment();

				return array(
					'payment' => array(
						'id'       => $payment->getId(),
						'status'   => $payment->getStatus(),
						'amount'   => $payment->getAmountMoney()->getAmount() / 100,
						'currency' => $payment->getAmountMoney()->getCurrency(),
					),
				);
			}

			$errors = $response->getErrors();
			if ( ! empty( $errors ) ) {
				throw new \Exception( $errors[0]->getDetail() );
			}

			throw new \Exception( __( 'Failed to capture payment.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );

		} catch ( \Exception $e ) {
			$this->log_error( 'Payment capture error: ' . $e->getMessage() );

			return new \WP_Error( 'capture_error', $e->getMessage() );
		}
	}

	/**
	 * Complete authorized payment.
	 *
	 * @param string $payment_id Square payment ID
	 *
	 * @return array|\WP_Error
	 */
	public function complete_payment( $payment_id ) {
		return $this->capture_payment( $payment_id );
	}

	/**
	 * Cancel authorized payment.
	 *
	 * @param string $payment_id Square payment ID
	 *
	 * @return array|\WP_Error
	 */
	public function cancel_payment( $payment_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new \Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			$payments_api = $this->client->getPaymentsApi();
			$response     = $payments_api->cancelPayment( $payment_id );

			if ( $response->isSuccess() ) {
				$payment = $response->getResult()->getPayment();

				return array(
					'payment' => array(
						'id'       => $payment->getId(),
						'status'   => $payment->getStatus(),
						'amount'   => $payment->getAmountMoney() ? $payment->getAmountMoney()->getAmount() / 100 : 0,
						'currency' => $payment->getAmountMoney() ? $payment->getAmountMoney()->getCurrency() : '',
					),
				);
			}

			$errors = $response->getErrors();
			if ( ! empty( $errors ) ) {
				throw new \Exception( $errors[0]->getDetail() );
			}

			throw new \Exception( __( 'Failed to cancel payment.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );

		} catch ( \Exception $e ) {
			$this->log_error( 'Payment cancel error: ' . $e->getMessage() );

			return new \WP_Error( 'cancel_error', $e->getMessage() );
		}
	}

	/**
	 * Process refund
	 *
	 * @param string|int $order_id Order ID or transaction ID
	 * @param float      $amount Refund amount
	 * @param string     $reason Refund reason
	 *
	 * @return array|\WP_Error Refund result or error
	 * @throws \Exception If process refund fails.
	 */
	public function process_refund( $order_id, $amount = null, $reason = '' ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new \Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Get tokens for fallback implementation
			$tokens = \FKWCSQ\Square::get_tokens();
			if ( empty( $tokens['access_token'] ) ) {
				throw new \Exception( __( 'No access token found.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// If order_id is numeric, it's a WooCommerce order ID
			if ( is_numeric( $order_id ) ) {
				$order = wc_get_order( $order_id );
				if ( ! $order ) {
					throw new \Exception( __( 'Order not found.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
				}

				// Get transaction ID from multiple possible sources
				$transaction_id = $order->get_transaction_id();
				if ( empty( $transaction_id ) ) {
					$transaction_id = get_post_meta( $order_id, 'fkwcsq_square_transaction_id', true );
				}
				if ( empty( $transaction_id ) ) {
					$transaction_id = get_post_meta( $order_id, '_transaction_id', true );
				}
				if ( empty( $transaction_id ) ) {
					throw new \Exception( __( 'Transaction ID not found.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
				}

				// If amount is not specified, use order total
				if ( $amount === null ) {
					$amount = $order->get_total();
				}
			} else {
				// If order_id is a string, assume it's a transaction ID
				$transaction_id = $order_id;
			}

			// Format amount for Square (in cents)
			$amount_in_cents = (int) ( $amount * 100 );

			// Get original payment to determine currency
			$payment_info = $this->get_payment( $transaction_id );
			if ( ! $payment_info ) {
				throw new \Exception( __( 'Could not find original payment.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Check if payment is captured
			if ( $payment_info['status'] !== 'COMPLETED' ) {
				throw new \Exception( __( 'Payment must be captured before refunding.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			$currency        = $payment_info['currency'];
			$idempotency_key = fkwcsq_idempotency_key( 'fkwcsq_refund_' );

			// Log refund attempt
			$this->log_debug( sprintf( 'Processing refund. Transaction ID: %s, Amount: %s, Currency: %s', $transaction_id, $amount, $currency ) );

			// Use Square SDK for refund
			if ( $this->client instanceof \Square\SquareClient ) {
				$refunds_api = $this->client->getRefundsApi();

				// Create money object for amount
				$amount_money = new \Square\Models\Money();
				$amount_money->setAmount( $amount_in_cents );
				$amount_money->setCurrency( $currency );

				// Create refund request
				$request = new \Square\Models\RefundPaymentRequest( $idempotency_key, $amount_money );

				// Set payment ID
				$request->setPaymentId( $transaction_id );

				if ( ! empty( $reason ) ) {
					$request->setReason( $reason );
				}

				// Execute refund
				$response = $refunds_api->refundPayment( $request );

				if ( $response->isSuccess() ) {
					$refund = $response->getResult()->getRefund();
					$result = array(
						'refund_id' => $refund->getId(),
						'status'    => $refund->getStatus(),
					);

					// Log successful refund
					$this->log_debug( sprintf( 'Refund successful. Refund ID: %s, Status: %s', $result['refund_id'], $result['status'] ) );

					// If we have an order, add a note
					if ( isset( $order ) ) {
						/* translators: 1: Refunded amount, 2: Refund ID, 3: Refund reason */
						$refund_message = sprintf( __( 'Refunded %1$s - Refund ID: %2$s - Reason: %3$s', 'funnelkit-payment-gateway-square-for-woocommerce' ), wc_price( $amount, array( 'currency' => $order->get_currency() ) ), $result['refund_id'], $reason );
						$order->add_order_note( $refund_message );
					}

					return $result;
				}

				$errors = $response->getErrors();
				if ( ! empty( $errors ) ) {
					throw new \Exception( $errors[0]->getDetail() );
				}
			}
		} catch ( \Exception $e ) {
			$this->log_error( 'Payment error: ' . $e->getMessage() );
			return new \WP_Error( 'refund_error', $e->getMessage() );
		}
	}

	/**
	 * Get payment information
	 *
	 * @param string $payment_id
	 *
	 * @return array|bool Payment information or false on failure
	 */
	public function get_payment( $payment_id ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				return false;
			}

			// Check if we're using Square SDK
			if ( $this->client instanceof \Square\SquareClient ) {
				$payments_api = $this->client->getPaymentsApi();
				$response     = $payments_api->getPayment( $payment_id );

				if ( $response->isSuccess() ) {
					$payment = $response->getResult()->getPayment();

					return array(
						'id'       => $payment->getId(),
						'status'   => $payment->getStatus(),
						'currency' => $payment->getAmountMoney()->getCurrency(),
						'amount'   => $payment->getAmountMoney()->getAmount() / 100,
					);
				}

				return false;
			}

			return false;

		} catch ( Exception $e ) {
			$this->log_error( 'Error getting payment: ' . $e->getMessage() );

			return false;
		}
	}

	/**
	 * Retrieve gift card details from a nonce (non-destructive balance check).
	 *
	 * Uses Square's GiftCardsApi::retrieveGiftCardFromNonce — does NOT charge or
	 * lock any funds. Safe to call on every checkout review update.
	 *
	 * @param string $token Gift card nonce from Square Web Payments SDK.
	 * @return array|\WP_Error  Array with 'id' and 'balance' (float, dollars) or WP_Error.
	 */
	public function retrieve_gift_card( $token ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new \Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			$gift_cards_api = $this->client->getGiftCardsApi();
			$request        = new \Square\Models\RetrieveGiftCardFromNonceRequest( $token );
			$response       = $gift_cards_api->retrieveGiftCardFromNonce( $request );

			if ( $response->isSuccess() ) {
				$gift_card     = $response->getResult()->getGiftCard();
				$balance_money = $gift_card->getBalanceMoney();

				return array(
					'id'      => $gift_card->getId(),
					'balance' => $balance_money ? ( $balance_money->getAmount() / 100 ) : 0.0,
				);
			}

			$errors = $response->getErrors();
			$detail = ! empty( $errors ) ? $errors[0]->getDetail() : __( 'Failed to retrieve gift card.', 'funnelkit-payment-gateway-square-for-woocommerce' );

			return new \WP_Error( 'retrieve_gift_card_error', $detail );

		} catch ( \Exception $e ) {
			$this->log_error( 'Error retrieving gift card: ' . $e->getMessage() );
			return new \WP_Error( 'retrieve_gift_card_error', $e->getMessage() );
		}
	}

	/**
	 * Get locations from Square API
	 *
	 * @return array|WP_Error Array of locations or WP_Error on failure
	 * @throws \Exception If retrieving locations fails.
	 * @since 1.0.0
	 */
	public function get_locations() {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( esc_html__( 'Square client not initialized', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Get Locations API instance.
			$locations_api = $this->client->getLocationsApi();

			// Make API request.
			$api_response = $locations_api->listLocations();

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? esc_html( $errors[0]->getDetail() ) : esc_html__( 'Failed to retrieve locations', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			$result    = $api_response->getResult();
			$locations = $result->getLocations();

			$locations_array = array_map(
				function ( $location ) {
					return array(
						'id'   => $location->getId(),
						'name' => $location->getName(),
					);
				},
				$locations
			);

			// Cache locations in non-sandbox mode.
			if ( ! \FKWCSQ\Square::is_sandbox_mode() ) {
				update_option( 'fkwcsq_locations', $locations_array, 'no' );
			} elseif ( \FKWCSQ\Square::is_sandbox_mode() ) {
				update_option( 'fkwcsq_sandbox_locations', $locations_array, 'no' );
			}

			return $locations_array;

		} catch ( Exception $e ) {
			$this->log_error( 'Failed to get locations: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Generates a reference ID for Square payments, limited to 40 characters
	 *
	 * Combines the site ID (if available) and order number, then truncates to 40 chars.
	 * Format: 'si:{site_id}|oi:{order_number}' or 'oi:{order_number}'
	 *
	 * @param string|int $order_number The WooCommerce order number
	 *
	 * @return string Formatted reference ID for Square, max 40 chars
	 */
	public function get_reference_id( $order_number ) {
		$site_id  = $this->get_site_id();
		$order_id = $order_number;
		if ( ! empty( $site_id ) ) {
			$reference_id = 'si:' . $site_id . '|oi:' . $order_id;
		} else {
			$reference_id = 'oi:' . $order_id;
		}

		// Ensure the reference ID is no longer than 40 characters
		if ( strlen( $reference_id ) > 40 ) {
			$reference_id = substr( $reference_id, 0, 40 );
		}

		return $reference_id;
	}

	/**
	 * Get location ID based on current environment
	 *
	 * @return string|null Location ID or null if not set
	 */
	public function get_location_id() {
		$settings = get_option( 'fkwcsq_settings', array() );
		if ( \FKWCSQ\Square::is_sandbox_mode() ) {
			return isset( $settings['sandbox_location_id'] ) ? $settings['sandbox_location_id'] : null;
		} else {
			return isset( $settings['location_id'] ) ? $settings['location_id'] : null;
		}
	}

	/**
	 * Create a payment
	 *
	 * @param array $payment_data Payment data
	 *
	 * @return array|WP_Error Payment response or WP_Error on failure
	 * @throws \Exception If retrieving locations fails.
	 */
	public function create_payment( $payment_data ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Validate required fields
			$required_fields = array(
				'source_id'       => __( 'Payment source ID is required.', 'funnelkit-payment-gateway-square-for-woocommerce' ),
				'idempotency_key' => __( 'Idempotency key is required.', 'funnelkit-payment-gateway-square-for-woocommerce' ),
				'amount_money'    => __( 'Payment amount is required.', 'funnelkit-payment-gateway-square-for-woocommerce' ),
				'location_id'     => __( 'Location ID is required.', 'funnelkit-payment-gateway-square-for-woocommerce' ),
			);

			foreach ( $required_fields as $field => $message ) {
				if ( empty( $payment_data[ $field ] ) ) {
					throw new Exception( $message );
				}
			}

			if ( empty( $payment_data['amount_money']['amount'] ) || empty( $payment_data['amount_money']['currency'] ) ) {
				throw new Exception( __( 'Payment amount and currency are required.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			$this->log_debug( 'Creating payment with data: ' . wp_json_encode( $payment_data, true ) );

			// Create Money object for amount
			$amount_money = new \Square\Models\Money();
			$amount_money->setAmount( (int) $payment_data['amount_money']['amount'] );
			$amount_money->setCurrency( $payment_data['amount_money']['currency'] );

			// Create CreatePaymentRequest object
			$request = new \Square\Models\CreatePaymentRequest( $payment_data['source_id'], $payment_data['idempotency_key'], $amount_money );

			// Set location ID
			$request->setLocationId( $payment_data['location_id'] );
			$request->setAmountMoney( $amount_money );

			if ( isset( $payment_data['is_renewal'] ) ) {
				$customer_details = new \Square\Models\CustomerDetails();
				$customer_details->setCustomerInitiated( false );
				$request->setCustomerDetails( $customer_details );
			}

			// Set optional fields if provided
			if ( isset( $payment_data['customer_id'] ) ) {
				$request->setCustomerId( $payment_data['customer_id'] );
			}

			if ( isset( $payment_data['note'] ) ) {
				$request->setNote( $payment_data['note'] );
			}

			if ( isset( $payment_data['autocomplete'] ) ) {
				$request->setAutocomplete( $payment_data['autocomplete'] );
			}

			// Allow partial authorization for gift card source when requested.
			if ( isset( $payment_data['accept_partial_authorization'] ) ) {
				$request->setAcceptPartialAuthorization( (bool) $payment_data['accept_partial_authorization'] );
			}

			// Handle billing address if provided
			if ( ! empty( $payment_data['billing_address'] ) ) {
				$address = new \Square\Models\Address();
				$address->setAddressLine1( $payment_data['billing_address']['address_line_1'] );
				if ( ! empty( $payment_data['billing_address']['address_line_2'] ) ) {
					$address->setAddressLine2( $payment_data['billing_address']['address_line_2'] );
				}
				$address->setLocality( $payment_data['billing_address']['locality'] );
				$address->setAdministrativeDistrictLevel1( $payment_data['billing_address']['administrative_district_level_1'] );
				$address->setPostalCode( $payment_data['billing_address']['postal_code'] );
				$address->setCountry( $payment_data['billing_address']['country'] );
				$request->setBillingAddress( $address );
			}

			// Handle shipping address if provided
			if ( ! empty( $payment_data['shipping_address'] ) ) {
				$address = new \Square\Models\Address();
				$address->setAddressLine1( $payment_data['shipping_address']['address_line_1'] );
				if ( ! empty( $payment_data['shipping_address']['address_line_2'] ) ) {
					$address->setAddressLine2( $payment_data['shipping_address']['address_line_2'] );
				}
				$address->setLocality( $payment_data['shipping_address']['locality'] );
				$address->setAdministrativeDistrictLevel1( $payment_data['shipping_address']['administrative_district_level_1'] );
				$address->setPostalCode( $payment_data['shipping_address']['postal_code'] );
				$country = $payment_data['shipping_address']['country'];
				if ( empty( $country ) ) {
					$country = get_option( 'woocommerce_default_country' );
					if ( strpos( $country, ':' ) !== false ) {
						$country = explode( ':', $country )[0];
					}
				}
				if ( ! empty( $country ) ) {
					$address->setCountry( $country );
				}
				$request->setShippingAddress( $address );
			}

			// Handle buyer email if provided
			if ( ! empty( $payment_data['buyer_email_address'] ) ) {
				$request->setBuyerEmailAddress( $payment_data['buyer_email_address'] );
			}

			// Handle reference ID if provided
			if ( ! empty( $payment_data['reference_id'] ) ) {
				$request->setReferenceId( $payment_data['reference_id'] );
			}

			// Handle verification token if provided
			if ( ! empty( $payment_data['verification_token'] ) ) {
				$request->setVerificationToken( $payment_data['verification_token'] );
			}

			// Handle application fee for Sublium subscriptions
			try {
				if ( $this->is_application_fee_supported( $payment_data['amount_money']['currency'] ) &&
				class_exists( '\Sublium_WCS\Includes\Helpers\AccessPermission' ) &&
				\Sublium_WCS\Includes\Helpers\AccessPermission::is_application_fee_applicable() ) {

					$order = null;
					if ( ! empty( $payment_data['order_id'] ) ) {
						$order = wc_get_order( $payment_data['order_id'] );
					} elseif ( ! empty( $payment_data['reference_id'] ) ) {
						// Parse reference_id format: "si:25OBQZ47U4|oi:8155"
						$order_id = null;
						if ( strpos( $payment_data['reference_id'], 'oi:' ) !== false ) {
							$parts = explode( '|', $payment_data['reference_id'] );
							foreach ( $parts as $part ) {
								if ( strpos( $part, 'oi:' ) === 0 ) {
									$order_id = str_replace( 'oi:', '', $part );
									break;
								}
							}
						} else {
							// Fallback to old format for backward compatibility
							$order_id = str_replace( 'Order-', '', $payment_data['reference_id'] );
						}

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

					if ( $order instanceof \WC_Order ) {
						$application_fee_amount = \Sublium_WCS\Includes\Helpers\AccessPermission::get_application_fee_by_order( $order );

						if ( $application_fee_amount > 0 ) {
							$fee_in_cents = (int) ( $application_fee_amount * 100 );

							$app_fee_money = new \Square\Models\Money();
							$app_fee_money->setAmount( $fee_in_cents );
							$app_fee_money->setCurrency( $payment_data['amount_money']['currency'] );
							$request->setAppFeeMoney( $app_fee_money );
						}
					}
				}
			} catch ( \Throwable $e ) {
				$this->log_error( 'Application fee calculation error: ' . $e->getMessage() );
			}

			// Get Payments API instance
			$payments_api = $this->client->getPaymentsApi();

			// Make the API request
			$api_response = $payments_api->createPayment( $request );

			if ( ! $api_response->isSuccess() ) {
				$errors        = $api_response->getErrors();
				$error_message = '';
				foreach ( $errors as $error ) {
					$error_message .= $error->getDetail() . ' ';
				}
				throw new Exception( $error_message );
			}

			$this->log_debug( 'Payment creation response: ' . wp_json_encode( $api_response->getBody(), true ) );

			return json_decode( $api_response->getBody(), true );

		} catch ( Exception $e ) {
			$this->log_error( 'Create payment error: ' . $e->getMessage() );

			return new WP_Error( 'payment_error', $e->getMessage() );
		}
	}

	/**
	 * Create customer in Square if not exists
	 *
	 * @param \WC_Order $order WooCommerce order
	 *
	 * @return array|WP_Error Customer data or error
	 * @throws \Exception If creating customer fails.
	 */
	public function maybe_create_customer( $order ) {
		try {
			// Get current user ID
			$user_id = $order->get_user_id();

			// Prepare customer data
			$customer_data = array(
				'given_name'    => $order->get_billing_first_name(),
				'family_name'   => $order->get_billing_last_name(),
				'email_address' => $order->get_billing_email(),
				'phone_number'  => $order->get_billing_phone(),
				'address'       => array(
					'address_line_1'                  => $order->get_billing_address_1(),
					'address_line_2'                  => $order->get_billing_address_2(),
					'locality'                        => $order->get_billing_city(),
					'administrative_district_level_1' => $order->get_billing_state(),
					'postal_code'                     => $order->get_billing_postcode(),
					'country'                         => $order->get_billing_country(),
				),
				'reference_id'  => (string) $user_id, // Store WP user ID as reference
			);

			// Remove empty values
			$customer_data = array_filter(
				$customer_data,
				function ( $value ) {
					return ! empty( $value ) || $value === 0;
				}
			);

			// Get Customers API instance
			$customers_api = $this->client->getCustomersApi();

			// First, try to find existing customer by email
			$search_request     = new \Square\Models\SearchCustomersRequest();
			$customer_query     = new \Square\Models\CustomerQuery();
			$customer_filter    = new \Square\Models\CustomerFilter();
			$CustomerTextFilter = new \Square\Models\CustomerTextFilter();
			$CustomerTextFilter->setExact( $order->get_billing_email() );
			$customer_filter->setEmailAddress( $CustomerTextFilter );
			$customer_query->setFilter( $customer_filter );
			$search_request->setQuery( $customer_query );

			$search_response = $customers_api->searchCustomers( $search_request );

			if ( ! $search_response->isSuccess() ) {
				$errors = $search_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to search for customer' );
			}

			$result    = $search_response->getResult();
			$customers = $result->getCustomers();

			// If customer exists, get their full details
			if ( ! empty( $customers ) ) {
				$billing_email = $order->get_billing_email();
				foreach ( $customers as $customer ) {
					$customer_email = $customer->getEmailAddress();
					if ( ! empty( $billing_email ) && ! empty( $customer_email ) && strtolower( trim( $customer_email ) ) === strtolower( trim( $billing_email ) ) ) {
						$this->log_debug( 'Found existing customer in Square with ID: ' . $customer->getId() );
						update_user_meta( $user_id, '_square_customer_id', $customer->getId() );
						return $customer->getId();
					}
				}
			}
			// If no customer found, create a new one
			$this->log_debug( 'Creating new customer in Square' );

			// Create address object if address data exists
			$address = null;
			if ( isset( $customer_data['address'] ) ) {
				$address = new \Square\Models\Address();
				$address->setAddressLine1( $customer_data['address']['address_line_1'] );
				if ( ! empty( $customer_data['address']['address_line_2'] ) ) {
					$address->setAddressLine2( $customer_data['address']['address_line_2'] );
				}
				if ( ! empty( $customer_data['address']['locality'] ) ) {
					$address->setLocality( $customer_data['address']['locality'] );
				}
				if ( ! empty( $customer_data['address']['administrative_district_level_1'] ) ) {
					$address->setAdministrativeDistrictLevel1( $customer_data['address']['administrative_district_level_1'] );
				}
				if ( ! empty( $customer_data['address']['postal_code'] ) ) {
					$address->setPostalCode( $customer_data['address']['postal_code'] );
				}
				if ( ! empty( $customer_data['address']['country'] ) ) {
					$address->setCountry( $customer_data['address']['country'] );
				}
			}

			// Create customer object
			$create_request = new \Square\Models\CreateCustomerRequest();
			if ( ! empty( $customer_data['given_name'] ) ) {
				$create_request->setGivenName( $customer_data['given_name'] );
			}
			if ( ! empty( $customer_data['family_name'] ) ) {
				$create_request->setFamilyName( $customer_data['family_name'] );
			}
			if ( ! empty( $customer_data['email_address'] ) ) {
				$create_request->setEmailAddress( $customer_data['email_address'] );
			}
			if ( ! empty( $customer_data['phone_number'] ) ) {
				$create_request->setPhoneNumber( $customer_data['phone_number'] );
			}
			if ( $address ) {
				$create_request->setAddress( $address );
			}
			if ( ! empty( $customer_data['reference_id'] ) ) {
				$create_request->setReferenceId( $customer_data['reference_id'] );
			}

			$create_response = $customers_api->createCustomer( $create_request );

			if ( ! $create_response->isSuccess() ) {
				$errors     = $create_response->getErrors();
				$error_code = isset( $errors[0] ) && method_exists( $errors[0], 'getCode' ) ? (int) $errors[0]->getCode() : 0;
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to create customer', $error_code );
			}

			$customer = $create_response->getResult()->getCustomer();
			$this->log_debug( 'Customer created successfully with ID: ' . $customer->getId() );

			return $customer->getId();
		} catch ( Exception $e ) {

			$status_code    = method_exists( $e, 'getCode' ) ? $e->getCode() : '';
			$status_message = $e->getMessage();
			$message        = sprintf(
				/* translators: 1: status code, 2: error message */
				__( 'Square Transaction Held for Review (Tokenization Request Failed: Status code %1$s: %2$s)', 'funnelkit-payment-gateway-square-for-woocommerce' ),
				$status_code,
				$status_message
			);
			$this->mark_order_hold( $order, $message );

			$this->log_error( 'Failed to create/find customer: ' . $e->getMessage() );

			return new WP_Error( 'customer_creation_failed', $e->getMessage() );
		}
	}

	/**
	 * Create a card for a customer
	 *
	 * @param array $card_data Card data
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If create card fails.
	 */
	public function create_card( $card_data, $order = null ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Validate required fields
			if ( empty( $card_data['source_id'] ) ) {
				throw new Exception( __( 'Missing required card data.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Get Cards API instance
			$cards_api = $this->client->getCardsApi();

			// Create card object
			$card = new \Square\Models\Card();
			$card->setCustomerId( $card_data['card']['customer_id'] );

			// Create CreateCardRequest object
			$request = new \Square\Models\CreateCardRequest( $card_data['idempotency_key'], $card_data['source_id'], $card );

			// Set verification token if provided
			if ( ! empty( $card_data['verification_token'] ) ) {
				$request->setVerificationToken( $card_data['verification_token'] );
			}

			// Make API request
			$api_response = $cards_api->createCard( $request );
			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to create card' );
			}

			$result = $api_response->getBody();

			Logger::debug( 'Card created successfully for customer: ' . $card_data['card']['customer_id'] );

			return json_decode( $result, true );

		} catch ( Exception $e ) {

			$status_code    = method_exists( $e, 'getCode' ) ? $e->getCode() : '';
			$status_message = $e->getMessage();
			$message        = sprintf(
				/* translators: 1: status code, 2: error message */
				__( 'Square Transaction Held for Review (Tokenization Request Failed: Status code %1$s: %2$s)', 'funnelkit-payment-gateway-square-for-woocommerce' ),
				$status_code,
				$status_message
			);
			$this->mark_order_hold( $order, $message );
			Logger::error( 'Failed to Create Card: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Get customer cards from Square
	 *
	 * @param string $customer_id Square customer ID
	 * @return array|WP_Error Response data or WP_Error on failure
	 */
	public function get_customer_cards( $customer_id ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Get Cards API instance
			$cards_api = $this->client->getCardsApi();

			// Make API request with customer ID parameter
			$api_response = $cards_api->listCards( null, $customer_id );
			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to get customer cards' );
			}

			$result = $api_response->getBody();
			$data   = json_decode( $result, true );

			Logger::debug( 'Retrieved cards for customer: ' . $customer_id );

			return $data;

		} catch ( Exception $e ) {
			Logger::error( 'Failed to get customer cards: ' . $e->getMessage() );
			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Disable a card in Square (Square doesn't support deletion, only disabling)
	 *
	 * @param string $card_id Square card ID
	 * @return array|WP_Error Response data or WP_Error on failure
	 */
	public function delete_card( $card_id ) {
		try {
			// Check if client is initialized
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( __( 'Square API is not properly configured.', 'funnelkit-payment-gateway-square-for-woocommerce' ) );
			}

			// Get Cards API instance
			$cards_api = $this->client->getCardsApi();

			// Make API request to disable the card
			$api_response = $cards_api->disableCard( $card_id );
			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to disable card' );
			}

			$result = $api_response->getBody();
			$data   = json_decode( $result, true );

			Logger::debug( 'Disabled card: ' . $card_id );

			return $data;

		} catch ( Exception $e ) {
			Logger::error( 'Failed to disable card: ' . $e->getMessage() );
			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Create an order in Square
	 *
	 * @param array $order_data Order data to create
	 *
	 * @return array|WP_Error Response from Square API
	 * @throws \Exception If the Square API call returns an error.
	 */
	public function create_order( $order_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$orders_api = $this->client->getOrdersApi();

			// Log order data for debugging
			Logger::debug( 'Order data for Square: ' . wp_json_encode( $order_data ) );

			// Validate location ID
			if ( empty( $order_data['location_id'] ) ) {
				throw new Exception( 'Location ID is required but was empty' );
			}

			// Create order object
			$order = new \Square\Models\Order( $order_data['location_id'] );

			// Set customer ID if available
			if ( ! empty( $order_data['customer_id'] ) ) {
				$order->setCustomerId( $order_data['customer_id'] );
			}

			// Add line items - support for both catalog and custom items
			if ( ! empty( $order_data['line_items'] ) ) {
				$line_items = array();

				foreach ( $order_data['line_items'] as $item ) {
					// Create line item with quantity
					$line_item = new \Square\Models\OrderLineItem( $item['quantity'] );

					// Determine if catalog item or custom line item
					if ( ! empty( $item['catalog_object_id'] ) ) {
						// Catalog item
						$line_item->setCatalogObjectId( $item['catalog_object_id'] );
					} elseif ( ! empty( $item['name'] ) ) {
						// Custom line item (requires a name)
						$line_item->setName( $item['name'] );

						// Add note if provided
						if ( ! empty( $item['note'] ) ) {
							$line_item->setNote( $item['note'] );
						}
					} else {
						// Skip invalid items
						continue;
					}

					// Set price for both types
					if ( ! empty( $item['base_price_money'] ) ) {
						$price_money = new \Square\Models\Money();
						$price_money->setAmount( $item['base_price_money']['amount'] );
						$price_money->setCurrency( $item['base_price_money']['currency'] );
						$line_item->setBasePriceMoney( $price_money );
					}

					$line_items[] = $line_item;
				}

				// Only set line items if we have valid ones
				if ( ! empty( $line_items ) ) {
					$order->setLineItems( $line_items );
				} else {
					throw new Exception( 'Order must have at least one valid line item' );
				}
			} else {
				throw new Exception( 'Order must have at least one line item' );
			}

			// Add taxes if present
			if ( ! empty( $order_data['taxes'] ) ) {
				$taxes = array();
				foreach ( $order_data['taxes'] as $tax ) {
					$order_tax = new \Square\Models\OrderLineItemTax();
					$order_tax->setName( $tax['name'] );
					// Ensure percentage is a string as required by Square
					$percentage = is_string( $tax['percentage'] ) ? $tax['percentage'] : strval( $tax['percentage'] );
					$order_tax->setPercentage( $percentage );
					$taxes[] = $order_tax;
				}
				$order->setTaxes( $taxes );
			}

			// Add discounts if present
			if ( ! empty( $order_data['discounts'] ) ) {
				$discounts = array();
				foreach ( $order_data['discounts'] as $discount ) {
					$order_discount = new \Square\Models\OrderLineItemDiscount();
					$order_discount->setName( $discount['name'] );
					// Ensure percentage is a string as required by Square
					$percentage = is_string( $discount['percentage'] ) ? $discount['percentage'] : strval( $discount['percentage'] );
					$order_discount->setPercentage( $percentage );
					$discounts[] = $order_discount;
				}
				$order->setDiscounts( $discounts );
			}

			// Handle fulfillments (shipping)
			if ( ! empty( $order_data['fulfillments'] ) ) {
				$fulfillments = array();

				foreach ( $order_data['fulfillments'] as $fulfillment_data ) {
					// Create a direct shipment fulfillment to ensure the type is set correctly
					$fulfillment_type = 'SHIPMENT'; // Default to SHIPMENT type

					// If type is provided in the data, use it (but still validate it)
					if ( ! empty( $fulfillment_data['type'] ) ) {
						$valid_types           = array( 'SHIPMENT', 'PICKUP', 'DELIVERY' );
						$fulfillment_data_type = strtoupper( $fulfillment_data['type'] );

						if ( in_array( $fulfillment_data_type, $valid_types ) ) {
							$fulfillment_type = $fulfillment_data_type;
						}
					}

					// Create the fulfillment with the validated type
					Logger::debug( 'Creating fulfillment with type: ' . $fulfillment_type );
					$fulfillment = new \Square\Models\OrderFulfillment( $fulfillment_type );

					// Ensure the type is properly set
					$fulfillment->setType( $fulfillment_type );

					// Add shipment details if this is a shipment
					if ( $fulfillment_type === 'SHIPMENT' && ! empty( $fulfillment_data['shipment_details'] ) ) {
						// Create shipment details
						$shipment_details = new \Square\Models\OrderFulfillmentShipmentDetails();

						// Set recipient if present
						if ( ! empty( $fulfillment_data['shipment_details']['recipient'] ) ) {
							$recipient_data = $fulfillment_data['shipment_details']['recipient'];
							$recipient      = new \Square\Models\OrderFulfillmentRecipient();

							// Set recipient fields if present
							if ( ! empty( $recipient_data['display_name'] ) ) {
								$recipient->setDisplayName( $recipient_data['display_name'] );
							}

							if ( ! empty( $recipient_data['email_address'] ) ) {
								$recipient->setEmailAddress( $recipient_data['email_address'] );
							}

							if ( ! empty( $recipient_data['phone_number'] ) ) {
								$recipient->setPhoneNumber( $recipient_data['phone_number'] );
							}

							// Handle recipient address
							if ( ! empty( $recipient_data['address'] ) ) {
								$address_data = $recipient_data['address'];
								$address      = new \Square\Models\Address();

								// Only set fields that are not empty
								if ( isset( $address_data['address_line_1'] ) ) {
									$address->setAddressLine1( $address_data['address_line_1'] );
								}

								if ( ! empty( $address_data['address_line_2'] ) ) {
									$address->setAddressLine2( $address_data['address_line_2'] );
								}

								if ( ! empty( $address_data['locality'] ) ) {
									$address->setLocality( $address_data['locality'] );
								}

								if ( ! empty( $address_data['administrative_district_level_1'] ) ) {
									$address->setAdministrativeDistrictLevel1( $address_data['administrative_district_level_1'] );
								}

								if ( ! empty( $address_data['postal_code'] ) ) {
									$address->setPostalCode( $address_data['postal_code'] );
								}

								if ( ! empty( $address_data['country'] ) ) {
									$address->setCountry( $address_data['country'] );
								}

								$recipient->setAddress( $address );
							}

							$shipment_details->setRecipient( $recipient );
						}

						$fulfillment->setShipmentDetails( $shipment_details );
					}

					$fulfillments[] = $fulfillment;
				}

				// Set the fulfillments on the order
				$order->setFulfillments( $fulfillments );
			}

			// Create the order request
			$request = new \Square\Models\CreateOrderRequest();
			$request->setOrder( $order );
			$request->setIdempotencyKey( fkwcsq_idempotency_key( 'fkwcsq_order_' ) );

			// Log the request for debugging
			Logger::debug( 'Creating Square order with idempotency key: ' . $request->getIdempotencyKey() );

			// Send the request to Square
			$response = $orders_api->createOrder( $request );

			// Check for errors in the response
			if ( ! $response->isSuccess() ) {
				$errors        = $response->getErrors();
				$error_details = array();

				if ( ! empty( $errors ) ) {
					foreach ( $errors as $error ) {
						$error_details[] = sprintf( 'Code: %s, Category: %s, Field: %s, Detail: %s', $error->getCode(), $error->getCategory(), $error->getField(), $error->getDetail() );
					}

					throw new Exception( 'Failed to create order: ' . implode( '; ', $error_details ) );
				} else {
					throw new Exception( 'Failed to create order: Unknown error' );
				}
			}

			// Get the created order
			$result = $response->getResult()->getOrder();
			Logger::info( 'Successfully created Square order: ' . $result->getId() );

			$order_data = array(
				'id'          => $result->getId(),
				'location_id' => $result->getLocationId(),
			);

			if ( $result->getCustomerId() ) {
				$order_data['customer_id'] = $result->getCustomerId();
			}

			if ( $result->getState() ) {
				$order_data['state'] = $result->getState();
			}

			if ( $result->getLineItems() ) {
				$line_items = array();
				foreach ( $result->getLineItems() as $item ) {
					$line_item = array(
						'quantity' => $item->getQuantity(),
					);

					if ( $item->getName() ) {
						$line_item['name'] = $item->getName();
					}

					if ( $item->getCatalogObjectId() ) {
						$line_item['catalog_object_id'] = $item->getCatalogObjectId();
					}

					$line_items[] = $line_item;
				}
				$order_data['line_items'] = $line_items;
			}

			if ( $result->getFulfillments() ) {
				$fulfillments = array();
				foreach ( $result->getFulfillments() as $fulfillment ) {
					$fulfillments[] = array(
						'type'  => $fulfillment->getType(),
						'state' => $fulfillment->getState(),
					);
				}
				$order_data['fulfillments'] = $fulfillments;
			}

			if ( $result->getCreatedAt() ) {
				$order_data['created_at'] = $result->getCreatedAt();
			}

			if ( $result->getUpdatedAt() ) {
				$order_data['updated_at'] = $result->getUpdatedAt();
			}

			if ( $result->getTotalMoney() ) {
				$order_data['total_money'] = array(
					'amount'   => $result->getTotalMoney()->getAmount(),
					'currency' => $result->getTotalMoney()->getCurrency(),
				);
			}

			return $order_data;

		} catch ( Exception $e ) {
			Logger::error( 'Failed to create Square order: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Update an order in Square
	 *
	 * @param string $order_id Square order ID
	 * @param array  $order_data Order data to update
	 *
	 * @return array|WP_Error Response from Square API
	 * @throws \Exception If the Square API call returns an error.
	 */
	public function update_order( $order_id, $order_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$orders_api = $this->client->getOrdersApi();
			Logger::debug( 'Updating Square order: ' . $order_id . ' with data: ' . wp_json_encode( $order_data ) );

			// Get current order
			$response = $orders_api->retrieveOrder( $order_id );
			if ( ! $response->isSuccess() ) {
				$errors = $response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve order' );
			}

			$order = $response->getResult()->getOrder();

			// Update order state if provided
			if ( ! empty( $order_data['state'] ) ) {
				$order->setState( $order_data['state'] );
			}

			// Update the order
			$request = new \Square\Models\UpdateOrderRequest();
			$request->setOrder( $order );
			$request->setIdempotencyKey( fkwcsq_idempotency_key( 'fkwcsq_order_upd_' ) );

			$response = $orders_api->updateOrder( $order_id, $request );

			if ( ! $response->isSuccess() ) {
				$errors = $response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to update order' );
			}

			$result = $response->getResult()->getOrder();
			Logger::info( 'Successfully updated Square order: ' . $result->getId() );

			return $result->toArray();

		} catch ( Exception $e ) {
			Logger::error( 'Failed to update Square order: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Create a refund in Square
	 *
	 * @param string $order_id Square order ID
	 * @param array  $refund_data Refund data
	 *
	 * @return array|WP_Error Response from Square API
	 * @throws \Exception If the Square API call returns an error.
	 */
	public function create_refund( $order_id, $refund_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$refunds_api = $this->client->getRefundsApi();
			Logger::debug( 'Creating refund for order: ' . $order_id . ' with data: ' . wp_json_encode( $refund_data ) );

			// Get payment ID for the order
			$payments_api     = $this->client->getPaymentsApi();
			$payment_response = $payments_api->listPayments( null, null, null, $order_id );

			if ( ! $payment_response->isSuccess() ) {
				$errors = $payment_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to find payment for order' );
			}

			$payments = $payment_response->getResult()->getPayments();
			if ( empty( $payments ) ) {
				throw new Exception( 'No payments found for order: ' . $order_id );
			}

			$payment_id = $payments[0]->getId();

			// Create refund request
			$amount_money = new \Square\Models\Money();
			$amount_money->setAmount( $refund_data['amount_money']['amount'] );
			$amount_money->setCurrency( $refund_data['amount_money']['currency'] );

			$refund_body = new \Square\Models\RefundPaymentRequest( fkwcsq_idempotency_key( 'fkwcsq_refund_' ), $payment_id, $amount_money );

			// Set reason if provided
			if ( ! empty( $refund_data['reason'] ) ) {
				$refund_body->setReason( $refund_data['reason'] );
			}

			// Send the refund request
			$response = $refunds_api->refundPayment( $refund_body );

			if ( ! $response->isSuccess() ) {
				$errors = $response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to create refund' );
			}

			$result = $response->getResult()->getRefund();
			Logger::info( 'Successfully created refund for order: ' . $order_id . ', refund ID: ' . $result->getId() );

			return $result->toArray();

		} catch ( Exception $e ) {
			Logger::error( 'Failed to create Square refund: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Create a customer in Square
	 *
	 * @param array $customer_data Customer data to create
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If create customer fails.
	 */
	public function create_customer( $customer_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Customers API instance
			$customers_api = $this->client->getCustomersApi();

			// Create address object if address data exists
			$address = null;
			if ( isset( $customer_data['address'] ) ) {
				$address = new \Square\Models\Address();
				$address->setAddressLine1( $customer_data['address']['address_line_1'] );
				if ( ! empty( $customer_data['address']['address_line_2'] ) ) {
					$address->setAddressLine2( $customer_data['address']['address_line_2'] );
				}
				$address->setLocality( $customer_data['address']['locality'] );
				$address->setAdministrativeDistrictLevel1( $customer_data['address']['administrative_district_level_1'] );
				$address->setPostalCode( $customer_data['address']['postal_code'] );
				$address->setCountry( $customer_data['address']['country'] );
			}

			// Create customer object
			$customer = new \Square\Models\CreateCustomerRequest();
			$customer->setGivenName( $customer_data['given_name'] );
			$customer->setFamilyName( $customer_data['family_name'] );
			$customer->setEmailAddress( $customer_data['email_address'] );
			if ( ! empty( $customer_data['phone_number'] ) ) {
				$customer->setPhoneNumber( $customer_data['phone_number'] );
			}
			if ( $address ) {
				$customer->setAddress( $address );
			}
			if ( ! empty( $customer_data['reference_id'] ) ) {
				$customer->setReferenceId( $customer_data['reference_id'] );
			}

			$api_response = $customers_api->createCustomer( $customer );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to create customer' );
			}

			$result = $api_response->getBody();

			return json_decode( $result, true );

		} catch ( Exception $e ) {
			Logger::error( 'Failed to create customer in Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}


	/**
	 * Update a customer in Square
	 *
	 * @param string $customer_id Square customer ID
	 * @param array  $customer_data Customer data to update
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If update customer fails.
	 */
	public function update_customer( $customer_id, $customer_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Customers API instance
			$customers_api = $this->client->getCustomersApi();

			// Create address object if address data exists
			$address = null;
			if ( isset( $customer_data['address'] ) ) {
				$address = new \Square\Models\Address();
				$address->setAddressLine1( $customer_data['address']['address_line_1'] );
				if ( ! empty( $customer_data['address']['address_line_2'] ) ) {
					$address->setAddressLine2( $customer_data['address']['address_line_2'] );
				}
				$address->setLocality( $customer_data['address']['locality'] );
				$address->setAdministrativeDistrictLevel1( $customer_data['address']['administrative_district_level_1'] );
				$address->setPostalCode( $customer_data['address']['postal_code'] );
				$address->setCountry( $customer_data['address']['country'] );
			}

			// Create customer object
			$customer = new \Square\Models\UpdateCustomerRequest();
			$customer->setGivenName( $customer_data['given_name'] );
			$customer->setFamilyName( $customer_data['family_name'] );
			$customer->setEmailAddress( $customer_data['email_address'] );
			if ( ! empty( $customer_data['phone_number'] ) ) {
				$customer->setPhoneNumber( $customer_data['phone_number'] );
			}
			if ( $address ) {
				$customer->setAddress( $address );
			}
			if ( ! empty( $customer_data['reference_id'] ) ) {
				$customer->setReferenceId( $customer_data['reference_id'] );
			}

			$api_response = $customers_api->updateCustomer( $customer_id, $customer );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to update customer' );
			}

			$result = $api_response->getBody();

			return json_decode( $result, true );

		} catch ( Exception $e ) {
			Logger::error( 'Failed to update customer in Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Delete a customer from Square
	 *
	 * @param string $customer_id Square customer ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If delete customer fails.
	 */
	public function delete_customer( $customer_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Customers API instance
			$customers_api = $this->client->getCustomersApi();

			$api_response = $customers_api->deleteCustomer( $customer_id );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to delete customer' );
			}

			return array(
				'success' => true,
			);

		} catch ( Exception $e ) {
			Logger::error( 'Failed to delete customer from Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Retrieve a customer from Square
	 *
	 * @param string $customer_id Square customer ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If get customer fails.
	 */
	public function get_customer( $customer_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Customers API instance
			$customers_api = $this->client->getCustomersApi();

			$api_response = $customers_api->retrieveCustomer( $customer_id );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve customer' );
			}

			$result = $api_response->getBody();

			return json_decode( $result, true );

		} catch ( Exception $e ) {
			Logger::error( 'Failed to retrieve customer from Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Change inventory in Square
	 *
	 * @param array $inventory_data Inventory data to update
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If change inventory fails.
	 */
	public function change_inventory( $inventory_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// First get the catalog item to find its variations
			$catalog_api   = $this->client->getCatalogApi();
			$item_response = $catalog_api->retrieveCatalogObject( $inventory_data['catalog_object_id'], true );

			if ( ! $item_response->isSuccess() ) {
				$errors = $item_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve catalog item' );
			}

			$catalog_item = $item_response->getResult()->getObject();

			// If this is already a variation, use it directly
			if ( $catalog_item->getType() === 'ITEM_VARIATION' ) {
				$variation_id = $inventory_data['catalog_object_id'];
			} else {
				// Get the first variation ID from the item
				$variations = $catalog_item->getItemData()->getVariations();
				if ( empty( $variations ) ) {
					throw new Exception( 'No variations found for the item' );
				}
				$variation_id = $variations[0]->getId();
			}

			// Update inventory_data with the correct variation ID
			$inventory_data['catalog_object_id'] = $variation_id;

			// Get Inventory API instance
			$inventory_api = $this->client->getInventoryApi();

			// Format the occurred_at timestamp in RFC3339 format
			$dt          = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
			$occurred_at = $dt->format( 'Y-m-d\TH:i:s.000\Z' );

			// Create physical count
			$physical_count = new \Square\Models\InventoryPhysicalCount();
			$physical_count->setCatalogObjectId( $inventory_data['catalog_object_id'] );
			$physical_count->setLocationId( $inventory_data['location_id'] );
			$physical_count->setQuantity( (string) $inventory_data['quantity'] );
			$physical_count->setState( $inventory_data['state'] ?? 'IN_STOCK' );
			$physical_count->setOccurredAt( $occurred_at );

			// Create inventory change
			$change = new \Square\Models\InventoryChange();
			$change->setType( 'PHYSICAL_COUNT' );
			$change->setPhysicalCount( $physical_count );

			// Create batch change request
			$batch_change = new \Square\Models\BatchChangeInventoryRequest( $inventory_data['idempotency_key'] );
			$batch_change->setChanges( array( $change ) );

			// Make the API request
			$api_response = $inventory_api->batchChangeInventory( $batch_change );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to change inventory' );
			}

			return json_decode( $api_response->getBody(), true );

		} catch ( Exception $e ) {
			Logger::error( 'Failed to change inventory: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Get inventory for a catalog object
	 *
	 * @param string $catalog_object_id Square catalog object ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If get inventory fails.
	 */
	public function get_inventory( $catalog_object_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// First get the catalog item to find its variations
			$catalog_api   = $this->client->getCatalogApi();
			$item_response = $catalog_api->retrieveCatalogObject( $catalog_object_id, true );

			if ( ! $item_response->isSuccess() ) {
				$errors = $item_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve catalog item' );
			}

			$catalog_item = $item_response->getResult()->getObject();

			// If this is already a variation, use it directly
			if ( $catalog_item->getType() === 'ITEM_VARIATION' ) {
				$variation_id = $catalog_object_id;
			} else {
				// Get the first variation ID from the item
				$variations = $catalog_item->getItemData()->getVariations();
				if ( empty( $variations ) ) {
					throw new Exception( 'No variations found for the item' );
				}
				$variation_id = $variations[0]->getId();
			}

			// Get Inventory API instance
			$inventory_api = $this->client->getInventoryApi();

			// Get the specific location_id
			$location_id = $this->get_location_id();
			if ( empty( $location_id ) ) {
				throw new Exception( 'Location ID not configured' );
			}

			// Now get inventory for the variation at the specific location
			$api_response = $inventory_api->retrieveInventoryCount( $variation_id, $location_id );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve inventory' );
			}

			return json_decode( $api_response->getBody(), true );

		} catch ( Exception $e ) {
			Logger::error( 'Failed to get inventory: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Create a product in Square
	 *
	 * @param array $product_data Product data to create
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If create product fails.
	 */
	public function create_product( $product_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Catalog API instance
			$catalog_api = $this->client->getCatalogApi();

			// Get the configured location ID
			$location_id = $this->get_location_id();
			if ( empty( $location_id ) ) {
				throw new Exception( 'Square location ID not configured' );
			}

			$body_idempotencyKey = fkwcsq_idempotency_key( 'fkwcsq_product_sync_' );
			$body_object_type    = CatalogObjectType::ITEM;
			$body_object_id      = '#' . ( $product_data['id'] );
			$product             = wc_get_product( $product_data['id'] );
			$body_object         = new CatalogObject( $body_object_type, $body_object_id );

			// Create item data object
			$item_data = new \Square\Models\CatalogItem();
			$item_data->setName( $product_data['name'] ?? '' );
			$item_data->setDescription( $product_data['description'] ?? '' );
			$item_data->setProductType( 'REGULAR' );

			// FIXED: Modern category assignment using categories array and reporting category
			if ( ! empty( $product_data['category_id'] ) ) {

				// Create category reference object
				$category_reference = new \Square\Models\CatalogObjectCategory();
				$category_reference->setId( $product_data['category_id'] );

				// Set categories array (can contain multiple categories)
				$item_data->setCategories( array( $category_reference ) );

				// Set reporting category (primary category for analytics)
				$reporting_category = new \Square\Models\CatalogObjectCategory();
				$reporting_category->setId( $product_data['category_id'] );
				$item_data->setReportingCategory( $reporting_category );

			}

			// ADDITIONAL FIX: Handle multiple categories if needed
			if ( ! empty( $product_data['category_ids'] ) && is_array( $product_data['category_ids'] ) ) {

				$categories = array();
				foreach ( $product_data['category_ids'] as $category_id ) {
					$category_reference = new \Square\Models\CatalogObjectCategory();
					$category_reference->setId( $category_id );
					$categories[] = $category_reference;
				}
				$item_data->setCategories( $categories );

				// Use first category as reporting category
				if ( ! empty( $categories ) ) {
					$item_data->setReportingCategory( $categories[0] );
				}
			}

			// Set the item data to the body object
			$body_object->setItemData( $item_data );
			$body_object->setPresentAtLocationIds( array( $location_id ) );
			$body_object->setPresentAtAllLocations( false );

			if ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
				$body_object->getItemData()->setImageIds( $product_data['image_ids'] );
			}

			$variation_object_type = CatalogObjectType::ITEM_VARIATION;

			// Store variation IDs for inventory updates
			$variation_ids = array();

			// Create variations
			$variations = array();
			if ( ! empty( $product_data['variations'] ) ) {
				foreach ( $product_data['variations'] as $variation ) {
					$money = new \Square\Models\Money();
					$price = isset( $variation['price'] ) && is_numeric( $variation['price'] ) ? (int) ( floatval( $product_data['price'] ) * 100 ) : 0;

					$money->setAmount( $price );
					$money->setCurrency( $variation['currency'] ?? get_woocommerce_currency() );
					$variation_object_id = '#var_' . $variation['id'];
					$variation_object    = new CatalogObject( $variation_object_type, $variation_object_id );
					$variation_object->setItemVariationData( new CatalogItemVariation() );
					$variation_object->getItemVariationData()->setItemId( $body_object_id );
					$variation_object->getItemVariationData()->setName( $variation['name'] );
					$variation_object->getItemVariationData()->setPricingType( 'FIXED_PRICING' );
					$variation_object->getItemVariationData()->setPriceMoney( $money );
					$variation_object->getItemVariationData()->setSku( $variation['sku'] ?? '' );

					// Get WooCommerce variation product to check stock management
					$wc_variation  = wc_get_product( $variation['id'] );
					$manages_stock = $wc_variation && $wc_variation->get_manage_stock();

					// CRITICAL FIX: Only enable inventory tracking if WooCommerce manages stock
					$variation_object->getItemVariationData()->setTrackInventory( $manages_stock );

					if ( $manages_stock ) {
						$variation_object->getItemVariationData()->setInventoryAlertThreshold( 5 );
					}

					$variation_object->setPresentAtLocationIds( array( $location_id ) );
					$variation_object->setPresentAtAllLocations( false );

					// Handle variation-specific image
					if ( isset( $variation['image_id'] ) && ! empty( $variation['image_id'] ) ) {
						$variation_object->getItemVariationData()->setImageIds( array( $variation['image_id'] ) );
					} elseif ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
						$variation_object->getItemVariationData()->setImageIds( $product_data['image_ids'] );
					}

					// CRITICAL FIX: Only store stock data if WooCommerce manages stock
					if ( $manages_stock ) {
						$stock_quantity = isset( $variation['stock'] ) ? intval( $variation['stock'] ) : $wc_variation->get_stock_quantity();
						if ( $stock_quantity < 0 ) {
							$stock_quantity = 0; // Ensure non-negative stock
						}

						$variation_ids[] = array(
							'temp_id'       => $variation_object_id,
							'stock'         => $stock_quantity,
							'manages_stock' => true,
						);
					} else {
						// Don't track inventory for this variation
						$variation_ids[] = array(
							'temp_id'       => $variation_object_id,
							'manages_stock' => false,
						);
					}

					$variations[] = $variation_object;
				}
			} else {
				$money = new \Square\Models\Money();
				$price = isset( $product_data['price'] ) && is_numeric( $product_data['price'] ) ? (int) ( floatval( $product_data['price'] ) * 100 ) : 0;

				$money->setAmount( $price );
				$money->setCurrency( $product_data['currency'] ?? get_woocommerce_currency() );
				$variation_object_id = '#var_' . $product_data['id'];
				$variation_object    = new CatalogObject( $variation_object_type, $variation_object_id );
				$variation_object->setItemVariationData( new CatalogItemVariation() );
				$variation_object->getItemVariationData()->setItemId( $body_object_id );
				$variation_object->getItemVariationData()->setName( $product_data['name'] );
				$variation_object->getItemVariationData()->setPricingType( 'FIXED_PRICING' );
				$variation_object->getItemVariationData()->setPriceMoney( $money );
				$variation_object->getItemVariationData()->setSku( $product_data['sku'] ?? '' );

				// CRITICAL FIX: Check if main product manages stock
				$manages_stock = $product && $product->get_manage_stock();

				// Only enable inventory tracking if WooCommerce manages stock
				$variation_object->getItemVariationData()->setTrackInventory( $manages_stock );

				if ( $manages_stock ) {
					$variation_object->getItemVariationData()->setInventoryAlertThreshold( 5 );
				}

				$variation_object->setPresentAtLocationIds( array( $location_id ) );
				$variation_object->setPresentAtAllLocations( false );

				if ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
					$variation_object->getItemVariationData()->setImageIds( $product_data['image_ids'] );
				}

				// CRITICAL FIX: Only store stock data if WooCommerce manages stock
				if ( $manages_stock ) {
					$stock_quantity = $product->get_stock_quantity();
					if ( $stock_quantity < 0 ) {
						$stock_quantity = 0; // Ensure non-negative stock
					}

					$variation_ids[] = array(
						'temp_id'       => $variation_object_id,
						'stock'         => $stock_quantity,
						'manages_stock' => true,
					);
				} else {
					// Don't track inventory for this variation
					$variation_ids[] = array(
						'temp_id'       => $variation_object_id,
						'manages_stock' => false,
					);
				}

				$variations[] = $variation_object;
			}
			$body_object->getItemData()->setVariations( $variations );

			// Create upsert request
			$request = new \Square\Models\UpsertCatalogObjectRequest( $body_idempotencyKey, $body_object );

			$api_response = $catalog_api->upsertCatalogObject( $request );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				Logger::error( 'Square API Error: ' . wp_json_encode( $errors ) );
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to create product' );
			}

			$result         = $api_response->getResult();
			$catalog_object = $result->getCatalogObject();

			// Get the ID mappings from temporary IDs to real Square IDs
			$id_mappings   = $result->getIdMappings();
			$mapping_array = array();

			// Create array mapping from temp IDs to real IDs
			foreach ( $id_mappings as $mapping ) {
				$mapping_array[ $mapping->getClientObjectId() ] = $mapping->getObjectId();
			}
			// CRITICAL FIX: Only update inventory for variations that manage stock
			sleep( 1 ); // Add a small delay to ensure the catalog item is fully processed

			$inventory_update_success = false;

			foreach ( $variation_ids as $variation ) {
				// Skip inventory update if this variation doesn't manage stock
				if ( ! $variation['manages_stock'] ) {
					Logger::log( 'Skipping inventory update for variation (stock not managed): ' . $variation['temp_id'] );
					continue;
				}

				// Skip if we don't have a mapping for this temp ID
				if ( ! isset( $mapping_array[ $variation['temp_id'] ] ) ) {
					Logger::error( 'No mapping found for temp ID: ' . $variation['temp_id'] );
					continue;
				}

				$square_variation_id = $mapping_array[ $variation['temp_id'] ];
				$stock_quantity      = intval( $variation['stock'] );
				$temp_id             = $variation['temp_id'];
				$variation_post_id   = intval( str_replace( '#var_', '', $temp_id ) );

				if ( $variation_post_id > 0 ) {
					update_post_meta( $variation_post_id, '_square_variation_id', $square_variation_id );
				}

				// Ensure stock quantity is valid (non-negative)
				if ( $stock_quantity < 0 ) {
					$stock_quantity = 0;
				}

				// Create inventory data array
				$inventory_data = array(
					'catalog_object_id' => $square_variation_id,
					'location_id'       => $location_id,
					'quantity'          => $stock_quantity,
					'state'             => 'IN_STOCK',
					'idempotency_key'   => fkwcsq_idempotency_key( 'fkwcsq_inventory_' ),
				);

				// Update inventory using the change_inventory method
				$inventory_result = $this->change_inventory( $inventory_data );

				if ( ! is_wp_error( $inventory_result ) ) {
					$inventory_update_success = true;
				} else {
					Logger::error( 'Failed to update inventory for variation ID ' . $square_variation_id . ': ' . $inventory_result->get_error_message() );
				}
			}

			// Only log inventory update failure if we actually tried to update inventory
			$has_managed_stock = array_filter(
				$variation_ids,
				function ( $v ) {
					return $v['manages_stock'];
				}
			);
			if ( ! empty( $has_managed_stock ) && ! $inventory_update_success ) {
				Logger::error( 'None of the inventory updates succeeded for product ID ' . $catalog_object->getId() );
			}

			return array(
				'id'             => $catalog_object->getId(),
				'catalog_object' => (array) $catalog_object,
			);

		} catch ( Exception $e ) {
			Logger::error( 'Failed to create product in Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Update a product in Square using batch upsert
	 *
	 * @param string $object_id Square object ID
	 * @param array  $product_data Product data to update
	 * @param string $square_variation_id Square variation ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws \Exception If the Square API call returns an error.
	 */
	public function update_product( $object_id, $product_data, $square_variation_id ) {
		try {

			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$catalog_api = $this->client->getCatalogApi();

			$retrieve_response = $catalog_api->retrieveCatalogObject( $object_id, true );
			if ( ! $retrieve_response->isSuccess() ) {
				$errors = $retrieve_response->getErrors();
				Logger::error( 'Failed to retrieve existing product: ' . wp_json_encode( $errors ) );
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve existing product' );
			}

			$existing_object = $retrieve_response->getResult()->getObject();
			$current_version = $existing_object->getVersion();

			// Map existing variations by ID and SKU for version tracking
			$existing_variations = array();
			$sku_map             = array();
			if ( $existing_object->getItemData() && $existing_object->getItemData()->getVariations() ) {
				foreach ( $existing_object->getItemData()->getVariations() as $variation ) {
					$existing_variations[ $variation->getId() ] = $variation->getVersion();
					$sku                                        = $variation->getItemVariationData()->getSku();
					if ( $sku ) {
						$sku_map[ $sku ] = array( $variation->getId(), $variation->getVersion() );
					}
				}
			}

			// Create main product object
			$product = wc_get_product( $product_data['id'] );
			if ( ! $product ) {
				Logger::error( 'WooCommerce product not found: ' . $product_data['id'] );
				throw new Exception( 'WooCommerce product not found' );
			}

			$body_object = new \Square\Models\CatalogObject( \Square\Models\CatalogObjectType::ITEM, $object_id );
			$body_object->setVersion( $current_version );
			$body_object->setItemData( new \Square\Models\CatalogItem() );
			$body_object->getItemData()->setName( $product_data['name'] );
			$body_object->getItemData()->setDescription( $product_data['description'] ?? '' );
			$body_object->getItemData()->setProductType( 'REGULAR' );

			// Set location ID and ensure present_at_all_locations is false
			$location_id = $this->get_location_id();
			if ( ! empty( $location_id ) ) {
				$body_object->setPresentAtLocationIds( array( $location_id ) );
				$body_object->setPresentAtAllLocations( false );
			}

			if ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
				$body_object->getItemData()->setImageIds( $product_data['image_ids'] );
			}

			// FIXED: Modern category assignment using categories array and reporting category
			if ( ! empty( $product_data['category_id'] ) ) {
				// Create category reference object
				$category_reference = new \Square\Models\CatalogObjectCategory();
				$category_reference->setId( $product_data['category_id'] );

				// Set categories array (can contain multiple categories)
				$body_object->getItemData()->setCategories( array( $category_reference ) );

				// Set reporting category (primary category for analytics)
				$reporting_category = new \Square\Models\CatalogObjectCategory();
				$reporting_category->setId( $product_data['category_id'] );
				$body_object->getItemData()->setReportingCategory( $reporting_category );

			}

			// ADDITIONAL FIX: Handle multiple categories if needed
			if ( ! empty( $product_data['category_ids'] ) && is_array( $product_data['category_ids'] ) ) {

				$categories = array();
				foreach ( $product_data['category_ids'] as $category_id ) {
					$category_reference = new \Square\Models\CatalogObjectCategory();
					$category_reference->setId( $category_id );
					$categories[] = $category_reference;
				}
				$body_object->getItemData()->setCategories( $categories );

				// Use first category as reporting category
				if ( ! empty( $categories ) ) {
					$body_object->getItemData()->setReportingCategory( $categories[0] );
				}
			}

			// Prepare variations array for the product and track inventory updates needed
			$variations        = array();
			$inventory_updates = array(); // Track which variations need inventory updates

			if ( ! empty( $product_data['variations'] ) ) {

				// For variable products
				foreach ( $product_data['variations'] as $variation ) {
					$woo_var_id = $variation['id'] ?? $square_variation_id;
					$sku        = $variation['sku'] ?? '';

					// Determine the Square variation ID
					$square_var_id = get_post_meta( $woo_var_id, '_square_variation_id', true );
					if ( ! $square_var_id && $sku && isset( $sku_map[ $sku ] ) ) {
						$square_var_id = $sku_map[ $sku ][0];
						update_post_meta( $woo_var_id, '_square_variation_id', $square_var_id );
					}

					if ( ! $square_var_id ) {
						// For new variations, use a temporary ID
						$square_var_id = '#var_' . $woo_var_id;
					}

					// Create variation object
					$variation_object = new \Square\Models\CatalogObject( \Square\Models\CatalogObjectType::ITEM_VARIATION, $square_var_id );

					// Set version only for existing variations
					if ( isset( $existing_variations[ $square_var_id ] ) ) {
						$variation_object->setVersion( $existing_variations[ $square_var_id ] );
					}

					// Set location ID and ensure present_at_all_locations is false for variations
					if ( ! empty( $location_id ) ) {
						$variation_object->setPresentAtLocationIds( array( $location_id ) );
						$variation_object->setPresentAtAllLocations( false );
					}

					// Set variation data
					$variation_object->setItemVariationData( new \Square\Models\CatalogItemVariation() );
					$variation_object->getItemVariationData()->setItemId( $object_id );
					$variation_object->getItemVariationData()->setName( $variation['name'] );
					$variation_object->getItemVariationData()->setPricingType( 'FIXED_PRICING' );

					// Set price
					$money = new \Square\Models\Money();
					$price = isset( $variation['price'] ) && is_numeric( $variation['price'] ) ? (int) ( floatval( $variation['price'] ) * 100 ) : 0;
					$money->setAmount( $price );
					$money->setCurrency( $variation['currency'] ?? get_woocommerce_currency() );
					$variation_object->getItemVariationData()->setPriceMoney( $money );

					// CRITICAL FIX: Check if WooCommerce variation manages stock
					$wc_variation  = wc_get_product( $woo_var_id );
					$manages_stock = $wc_variation && $wc_variation->get_manage_stock();

					// Set SKU and inventory settings based on stock management
					$variation_object->getItemVariationData()->setSku( $sku );
					$variation_object->getItemVariationData()->setTrackInventory( $manages_stock );

					if ( $manages_stock ) {
						$variation_object->getItemVariationData()->setInventoryAlertThreshold( 5 );

						// Track this variation for inventory update
						$stock_quantity      = $wc_variation->get_stock_quantity();
						$inventory_updates[] = array(
							'square_var_id'  => $square_var_id,
							'woo_var_id'     => $woo_var_id,
							'stock_quantity' => $stock_quantity,
							'temp_id'        => ( strpos( $square_var_id, '#var_' ) === 0 ),
						);
					} else {
						Logger::debug( "Skipping inventory tracking for variation {$woo_var_id} - stock not managed" );
					}

					// Handle variation images
					if ( isset( $variation['image_id'] ) && ! empty( $variation['image_id'] ) ) {
						$variation_object->getItemVariationData()->setImageIds( array( $variation['image_id'] ) );
					} elseif ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
						$variation_object->getItemVariationData()->setImageIds( $product_data['image_ids'] );
					}

					$variations[] = $variation_object;
				}
			} else {

				// For simple products
				$money = new \Square\Models\Money();
				$price = isset( $product_data['price'] ) && is_numeric( $product_data['price'] ) ? (int) ( floatval( $product_data['price'] ) * 100 ) : 0;
				$money->setAmount( $price );
				$money->setCurrency( $product_data['currency'] ?? get_woocommerce_currency() );

				if ( ! $square_variation_id || $square_variation_id === '#var_' . $product_data['id'] ) {
					// For new variations or if no valid ID provided
					$square_variation_id = '#var_' . $product_data['id'];
				}

				$variation_object = new \Square\Models\CatalogObject( \Square\Models\CatalogObjectType::ITEM_VARIATION, $square_variation_id );

				// Set version if it's an existing variation
				if ( isset( $existing_variations[ $square_variation_id ] ) ) {
					$variation_object->setVersion( $existing_variations[ $square_variation_id ] );
				}

				// Set location ID and ensure present_at_all_locations is false for variations
				if ( ! empty( $location_id ) ) {
					$variation_object->setPresentAtLocationIds( array( $location_id ) );
					$variation_object->setPresentAtAllLocations( false );
				}

				$variation_object->setItemVariationData( new \Square\Models\CatalogItemVariation() );
				$variation_object->getItemVariationData()->setItemId( $object_id );
				$variation_object->getItemVariationData()->setName( $product_data['name'] );
				$variation_object->getItemVariationData()->setPricingType( 'FIXED_PRICING' );
				$variation_object->getItemVariationData()->setPriceMoney( $money );
				$variation_object->getItemVariationData()->setSku( $product_data['sku'] ?? '' );

				// CRITICAL FIX: Check if main product manages stock
				$manages_stock = $product && $product->get_manage_stock();

				$variation_object->getItemVariationData()->setTrackInventory( $manages_stock );

				if ( $manages_stock ) {
					$variation_object->getItemVariationData()->setInventoryAlertThreshold( 5 );

					// Track this variation for inventory update
					$stock_quantity = $product->get_stock_quantity();

					$inventory_updates[] = array(
						'square_var_id'  => $square_variation_id,
						'woo_var_id'     => $product_data['id'],
						'stock_quantity' => $stock_quantity,
						'temp_id'        => ( strpos( $square_variation_id, '#var_' ) === 0 ),
					);
				} else {
					Logger::debug( 'Skipping inventory tracking for simple product - stock not managed' );
				}

				if ( isset( $product_data['image_ids'] ) && ! empty( $product_data['image_ids'] ) ) {
					$variation_object->getItemVariationData()->setImageIds( $product_data['image_ids'] );
				}

				$variations[] = $variation_object;
			}

			// Set variations on main product object
			$body_object->getItemData()->setVariations( $variations );

			// Create batch upsert request
			$batches         = array( new \Square\Models\CatalogObjectBatch( array( $body_object ) ) );
			$idempotency_key = fkwcsq_idempotency_key( 'fkwcsq_product_upd_' );
			$request         = new \Square\Models\BatchUpsertCatalogObjectsRequest( $idempotency_key, $batches );

			// Execute batch upsert
			$api_response = $catalog_api->batchUpsertCatalogObjects( $request );
			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				Logger::error( 'Failed to update product catalog: ' . wp_json_encode( $errors ) );
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to update product' );
			}

			$result  = $api_response->getResult();
			$objects = $result->getObjects();

			$id_mappings = array();
			if ( $result->getIdMappings() ) {
				foreach ( $result->getIdMappings() as $mapping ) {
					$id_mappings[ $mapping->getClientObjectId() ] = $mapping->getObjectId();
				}
			}

			// Update local meta for variations and prepare for inventory updates
			if ( ! empty( $objects ) ) {
				$updated_product = $objects[0]; // Main product object
				if ( $updated_product->getType() === \Square\Models\CatalogObjectType::ITEM && $updated_product->getItemData() && $updated_product->getItemData()->getVariations() ) {

					foreach ( $updated_product->getItemData()->getVariations() as $var_obj ) {
						$variation_id  = $var_obj->getId();
						$variation_sku = $var_obj->getItemVariationData()->getSku();

						// Find WooCommerce variation ID either by SKU or by known mappings
						$woo_var_id = null;
						if ( $variation_sku ) {
							$woo_var_id = wc_get_product_id_by_sku( $variation_sku );
						}

						// If no product found by SKU, check by temporary ID pattern
						if ( ! $woo_var_id && preg_match( '/#var_(\d+)/', $variation_id, $matches ) ) {
							$woo_var_id = $matches[1];
						}

						if ( $woo_var_id ) {
							update_post_meta( $woo_var_id, '_square_variation_id', $variation_id );
							update_post_meta( $woo_var_id, '_square_variation_version', $var_obj->getVersion() );
						}
					}
				}
			}

			// Now handle inventory updates for variations that manage stock
			$inventory_update_success = false;
			$inventory_update_count   = 0;

			if ( ! empty( $inventory_updates ) ) {

				sleep( 1 );

				foreach ( $inventory_updates as $update ) {
					try {
						$final_square_var_id = $update['square_var_id'];
						if ( $update['temp_id'] && isset( $id_mappings[ $update['square_var_id'] ] ) ) {
							$final_square_var_id = $id_mappings[ $update['square_var_id'] ];

							update_post_meta( $update['woo_var_id'], '_square_variation_id', $final_square_var_id );
						}

						$stock_quantity = intval( $update['stock_quantity'] );
						if ( $stock_quantity < 0 ) {
							$stock_quantity = 0;
						}

						// Create inventory data for physical count update
						$inventory_data = array(
							'catalog_object_id' => $final_square_var_id,
							'location_id'       => $location_id,
							'quantity'          => $stock_quantity,
							'state'             => 'IN_STOCK',
							'idempotency_key'   => fkwcsq_idempotency_key( 'fkwcsq_inv_upd_' ),
						);

						// Use the change_inventory method to update stock
						$inventory_result = $this->change_inventory( $inventory_data );

						if ( ! is_wp_error( $inventory_result ) ) {
							$inventory_update_success = true;
							++$inventory_update_count;
						} else {
							Logger::error( "Failed to update inventory for variation {$final_square_var_id}: " . $inventory_result->get_error_message() );
						}
					} catch ( Exception $e ) {
						Logger::error( "Exception updating inventory for variation {$update['square_var_id']}: " . $e->getMessage() );
					}

					usleep( 200000 );
				}
			} else {
				Logger::debug( 'No inventory updates needed - no variations manage stock' );
			}

			$response = array(
				'id'                => $object_id,
				'version'           => isset( $objects[0] ) ? $objects[0]->getVersion() : null,
				'catalog_object'    => isset( $objects[0] ) ? $objects[0] : null,
				'inventory_updated' => $inventory_update_success,
				'inventory_count'   => $inventory_update_count,
			);

			return $response;

		} catch ( Exception $e ) {
			Logger::error( 'Failed to update product in Square: ' . $e->getMessage() );

			return new \WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Delete a product from Square
	 *
	 * @param string $object_id Square object ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If deletion fails or client is not initialized.
	 */
	public function delete_product( $object_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Catalog API instance
			$catalog_api = $this->client->getCatalogApi();

			$api_response = $catalog_api->deleteCatalogObject( $object_id );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				Logger::error( 'Failed to delete product: ' . $errors[0]->getDetail() );
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to delete product' );
			}

			$result = $api_response->getResult();

			return array(
				'success'            => true,
				'deleted_object_ids' => $result->getDeletedObjectIds(),
			);

		} catch ( Exception $e ) {
			Logger::error( 'Failed to delete product from Square: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Create a category in Square
	 *
	 * @param array $category_data Category data
	 *
	 * @return array|WP_Error Category data or WP_Error on failure
	 */
	public function create_category( $category_data ) {
		try {
			$client = $this->maybe_init_client();
			if ( is_wp_error( $client ) ) {
				return $client;
			}

			$catalog_api = $this->client->getCatalogApi();

			// Create category object
			$category = new \Square\Models\CatalogCategory();
			$category->setName( $category_data['name'] );

			// Create catalog object
			$object = new \Square\Models\CatalogObject( 'CATEGORY', '#' . uniqid( 'fkwcsq_category_', true ) );
			$object->setCategoryData( $category );

			// Create request
			$request = new \Square\Models\UpsertCatalogObjectRequest( fkwcsq_idempotency_key( 'fkwcsq_cat_' ), $object );

			// Make API call
			$response = $catalog_api->upsertCatalogObject( $request );
			$body     = $response->getBody();
			$result   = json_decode( $body, true );

			if ( ! empty( $result['errors'] ) ) {
				return new \WP_Error( 'square_api_error', $result['errors'][0]['detail'] );
			}

			if ( empty( $result['catalog_object'] ) ) {
				return new \WP_Error( 'square_api_error', 'Failed to create category' );
			}

			return array(
				'id'          => $result['catalog_object']['id'],
				'name'        => $result['catalog_object']['category_data']['name'],
				'description' => $result['catalog_object']['category_data']['description'] ?? '',
			);
		} catch ( \Exception $e ) {
			return new \WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Update a category in Square
	 *
	 * @param string $category_id Square category ID
	 * @param array  $category_data Category data
	 *
	 * @return array|WP_Error Response data or error
	 * @throws Exception If category creation fails.
	 */
	public function update_category( $category_id, $category_data ) {
		try {
			$client = $this->maybe_init_client();
			if ( is_wp_error( $client ) ) {
				return $client;
			}

			$catalog_api = $this->client->getCatalogApi();

			// First retrieve the current object to get its version
			$retrieve_response = $catalog_api->retrieveCatalogObject( $category_id );
			if ( ! $retrieve_response->isSuccess() ) {
				$errors = $retrieve_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve existing category' );
			}

			$existing_object = $retrieve_response->getResult()->getObject();
			$current_version = $existing_object->getVersion();

			// Create category object
			$category = new \Square\Models\CatalogCategory();
			$category->setName( $category_data['name'] );

			// Create catalog object
			$object = new \Square\Models\CatalogObject( 'CATEGORY', $category_id );
			$object->setVersion( $current_version );
			$object->setCategoryData( $category );

			// Create request
			$request = new \Square\Models\UpsertCatalogObjectRequest( fkwcsq_idempotency_key( 'fkwcsq_cat_upd_' ), $object );

			// Make API call
			$response = $catalog_api->upsertCatalogObject( $request );

			if ( ! $response->isSuccess() ) {
				$errors = $response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to update category' );
			}

			$result         = $response->getResult();
			$catalog_object = $result->getCatalogObject();

			return array(
				'id'      => $catalog_object->getId(),
				'name'    => $catalog_object->getCategoryData()->getName(),
				'version' => $catalog_object->getVersion(),
			);
		} catch ( \Exception $e ) {
			$this->log(
				'Failed to update category',
				array(
					'category_id'   => $category_id,
					'category_data' => $category_data,
					'error'         => $e->getMessage(),
				),
				'error'
			);

			return new \WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Delete a category from Square
	 *
	 * @param string $category_id Square category ID
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If category update fails.
	 */
	public function delete_category( $category_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			// Get Catalog API instance
			$catalog_api = $this->client->getCatalogApi();

			// Make API call to delete the category
			$api_response = $catalog_api->deleteCatalogObject( $category_id );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to delete category' );
			}

			$result = $api_response->getResult();

			return array(
				'success'            => true,
				'deleted_object_ids' => $result->getDeletedObjectIds(),
			);

		} catch ( Exception $e ) {
			$this->log(
				'Failed to delete category',
				array(
					'category_id' => $category_id,
					'error'       => $e->getMessage(),
				),
				'error'
			);

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Upload an image to Square
	 *
	 * @param string $image_path Path to image file
	 *
	 * @return array|WP_Error Image data or WP_Error on failure
	 */
	public function upload_image( $image_path ) {
		try {
			$client = $this->maybe_init_client();
			if ( is_wp_error( $client ) ) {
				return $client;
			}

			$catalog_api = $this->client->getCatalogApi();

			// Check if file exists
			if ( ! file_exists( $image_path ) ) {
				return new \WP_Error( 'file_not_found', 'Image file not found' );
			}

			// Create image object
			$image = new \Square\Models\CatalogImage();
			$image->setName( time() . '_' . basename( $image_path ) );

			// Create catalog object
			$object = new \Square\Models\CatalogObject( 'IMAGE', '#' . uniqid( 'fkwcsq_image_', true ) );
			$object->setImageData( $image );

			// Create FileWrapper for the image
			$file_wrapper = \Square\Utils\FileWrapper::createFromPath( $image_path );

			// Create image upload request
			$upload_request = new \Square\Models\CreateCatalogImageRequest( fkwcsq_idempotency_key( 'fkwcsq_image_' ), $object );

			// Upload image data
			$upload_response = $catalog_api->createCatalogImage( $upload_request, $file_wrapper );
			$upload_body     = $upload_response->getBody();
			$upload_result   = json_decode( $upload_body, true );

			if ( ! empty( $upload_result['errors'] ) ) {
				return new \WP_Error( 'square_api_error', $upload_result['errors'][0]['detail'] );
			}

			if ( empty( $upload_result['image'] ) ) {
				return new \WP_Error( 'square_api_error', 'Failed to upload image data' );
			}

			return array(
				'id'      => $upload_result['image']['id'],
				'url'     => $upload_result['image']['image_data']['url'],
				'caption' => $upload_result['image']['caption'] ?? '',
			);
		} catch ( \Exception $e ) {
			return new \WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Update inventory in Square for a WooCommerce product
	 *
	 * @param array $data {
	 *     Required. Array of data for updating inventory.
	 *
	 * @type string $catalog_object_id Square catalog object ID for the variation
	 * @type int $quantity New quantity to set
	 * @type string $from_state Optional. Current inventory state. Default 'IN_STOCK'
	 * @type string $to_state Optional. New inventory state. Default 'IN_STOCK'
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If image upload fails.
	 */
	public function update_inventory( $square_id, $data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			if ( ! isset( $data['quantity'] ) ) {
				throw new Exception( 'Quantity is required' );
			}

			// Get current inventory count (old stock)
			$current_inventory = $this->get_inventory( $square_id );
			if ( is_wp_error( $current_inventory ) ) {
				throw new Exception( $current_inventory->get_error_message() );
			}

			// Extract current quantity (you'll need to adjust based on actual response structure)
			$old_quantity = 0;
			if ( isset( $current_inventory['counts'] ) && is_array( $current_inventory['counts'] ) && count( $current_inventory['counts'] ) > 0 ) {
				$old_quantity = intval( $current_inventory['counts'][0]['quantity'] );
			}

			$new_quantity = intval( $data['quantity'] );

			// Determine from_state and to_state based on quantity difference
			if ( $new_quantity < $old_quantity ) {
				// Stock reduced (sold)
				$from_state    = 'IN_STOCK';
				$to_state      = 'SOLD';
				$quantity_diff = $old_quantity - $new_quantity;
			} elseif ( $new_quantity > $old_quantity ) {
				// Stock added (restocked)
				$from_state    = 'NONE';
				$to_state      = 'IN_STOCK';
				$quantity_diff = $new_quantity - $old_quantity;
			} else {
				// No change
				return new WP_Error( 'no_inventory_change', 'Inventory quantity unchanged.' );
			}
			$gmt_timestamp  = time();
			$occurred_at    = gmdate( 'Y-m-d\TH:i:sP', $gmt_timestamp );
			$inventory_data = array(
				'idempotency_key'         => fkwcsq_idempotency_key( 'fkwcsq_inventory_sync_' ),
				'changes'                 => array(
					array(
						'type'       => 'ADJUSTMENT',
						'adjustment' => array(
							'from_state'        => $from_state,
							'to_state'          => $to_state,
							'location_id'       => $this->get_location_id(),
							'catalog_object_id' => $square_id,
							'quantity'          => $quantity_diff,
							'occurred_at'       => $occurred_at,
						),
					),
				),
				'ignore_unchanged_counts' => true,
			);

			$result = $this->update_product_inventory( $inventory_data );

			if ( is_wp_error( $result ) ) {
				throw new Exception( $result->get_error_message() );
			}

			// Log successful inventory update
			$this->log_debug(
				'Inventory updated successfully ' . wp_json_encode(
					array(
						'new_quantity' => $inventory_data['changes'][0]['adjustment']['quantity'],
						'location_id'  => $inventory_data['changes'][0]['adjustment']['location_id'],
					)
				)
			);

			return $result;

		} catch ( Exception $e ) {
			$error_message = 'Failed to update inventory: ' . $e->getMessage();
			Logger::error( $error_message );

			return new WP_Error( 'square_api_error', $error_message );
		}
	}

	/**
	 * Update_product_inventory  in Square
	 *
	 * @param array $inventory_data Inventory data to update
	 *
	 * @return array|WP_Error Response data or WP_Error on failure
	 * @throws Exception If change inventory fails.
	 */
	public function update_product_inventory( $inventory_data ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$catalog_api   = $this->client->getCatalogApi();
			$inventory_api = $this->client->getInventoryApi();

			// Extract catalog object ID from nested changes array
			if ( empty( $inventory_data['changes'][0]['adjustment']['catalog_object_id'] ) ) {
				throw new Exception( 'Catalog object ID missing in inventory data' );
			}
			$catalog_object_id = $inventory_data['changes'][0]['adjustment']['catalog_object_id'];

			// Retrieve catalog object with variations info
			$item_response = $catalog_api->retrieveCatalogObject( $catalog_object_id, true );
			if ( ! $item_response->isSuccess() ) {
				$errors = $item_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to retrieve catalog item' );
			}
			$catalog_object = $item_response->getResult()->getObject();

			// Determine the variation ID to update inventory for
			if ( $catalog_object->getType() === 'ITEM_VARIATION' ) {
				$variation_id = $catalog_object_id;
			} else {
				$item_data = $catalog_object->getItemData();
				if ( empty( $item_data ) ) {
					throw new Exception( 'Catalog object missing item data' );
				}
				$variations = $item_data->getVariations();
				if ( empty( $variations ) || ! is_array( $variations ) ) {
					throw new Exception( 'No variations found for the item' );
				}
				$variation_id = $variations[0]->getId();
			}

			$adjustment_data = $inventory_data['changes'][0]['adjustment'];

			// Required data
			$location_id = $adjustment_data['location_id'] ?? $this->get_location_id();
			if ( empty( $location_id ) ) {
				throw new Exception( 'Location ID is required for inventory update' );
			}

			$quantity = (string) ( $adjustment_data['quantity'] ?? '' );
			if ( $quantity === '' ) {
				throw new Exception( 'Quantity is required for inventory update' );
			}

			$from_state = $adjustment_data['from_state'] ?? 'NONE';
			$to_state   = $adjustment_data['to_state'] ?? 'IN_STOCK';

			// Use occurred_at from data or generate current timestamp in RFC3339 format
			if ( ! empty( $adjustment_data['occurred_at'] ) ) {
				$occurred_at = $adjustment_data['occurred_at'];
			} else {
				$dt          = new \DateTime( 'now', new \DateTimeZone( 'UTC' ) );
				$occurred_at = $dt->format( 'Y-m-d\TH:i:s.000\Z' );
			}

			// Build InventoryAdjustment object
			$adjustment = new \Square\Models\InventoryAdjustment();
			$adjustment->setFromState( $from_state );
			$adjustment->setToState( $to_state );
			$adjustment->setLocationId( $location_id );
			$adjustment->setCatalogObjectId( $variation_id );
			$adjustment->setQuantity( $quantity );
			$adjustment->setOccurredAt( $occurred_at );

			// Build InventoryChange object
			$change = new \Square\Models\InventoryChange();
			$change->setType( 'ADJUSTMENT' );
			$change->setAdjustment( $adjustment );

			// Build BatchChangeInventoryRequest
			$idempotency_key = $inventory_data['idempotency_key'] ?? fkwcsq_idempotency_key( 'fkwcsq_inventory_sync_' );
			$batch_change    = new \Square\Models\BatchChangeInventoryRequest( $idempotency_key );
			$batch_change->setChanges( array( $change ) );

			// Set ignore_unchanged_counts flag properly
			$ignore_unchanged_counts = $inventory_data['ignore_unchanged_counts'] ?? false;
			$batch_change->setIgnoreUnchangedCounts( (bool) $ignore_unchanged_counts );
			// Call API to update inventory
			$api_response = $inventory_api->batchChangeInventory( $batch_change );

			if ( ! $api_response->isSuccess() ) {
				$errors = $api_response->getErrors();
				throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to change inventory' );
			}

			return $api_response->getResult();

		} catch ( Exception $e ) {
			Logger::error( 'Failed to change inventory: ' . $e->getMessage() );

			return new WP_Error( 'square_api_error', $e->getMessage() );
		}
	}

	/**
	 * Retrieves the site ID from WordPress options.
	 *
	 * Gets the stored site ID value from the 'fkwcsq_site_id' option in the
	 * WordPress options table. This is typically used to identify the current
	 * site in multi-site configurations or external integrations.
	 *
	 * @return mixed The site ID if set, false if the option doesn't exist, or the default value.
	 * @since 1.0.0
	 */
	public function get_site_id() {
		$site_id = get_option( 'fkwcsq_site_id' );

		return $site_id;
	}

	/**
	 * Get all products from Square catalog.
	 *
	 * @return array List of Square products or WP_Error on failure.
	 * @throws \Exception If the Square API call returns an error.
	 */
	public function get_all_square_products() {
		try {
			Logger::info( 'Starting get_all_square_products API call.' );
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$catalog_api = $this->client->getCatalogApi();
			$types       = 'ITEM,ITEM_VARIATION';
			$cursor      = null;
			$all_objects = array();

			do {
				$response = $catalog_api->listCatalog( $cursor, $types );
				if ( ! $response->isSuccess() ) {
					$errors = $response->getErrors();
					Logger::debug( 'Failed to list catalog' . $errors[0]->getDetail() );
					throw new Exception( isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to list catalog' );
				}
				$result  = $response->getResult();
				$objects = $result->getObjects();
				if ( is_array( $objects ) ) {
					$all_objects = array_merge( $all_objects, $objects );
				}
				$cursor = $result->getCursor();
			} while ( $cursor );

			return $all_objects;
		} catch ( \Exception $e ) {
			Logger::error( 'Exception in get_all_square_products: ' . $e->getMessage() );
			throw new Exception( esc_attr( $e->getMessage() ) );
		}
	}

	/**
	 * Get image data from Square
	 *
	 * @param string $image_id Square image ID
	 *
	 * @return array|WP_Error Image data or WP_Error on failure
	 */
	public function get_image( $image_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				return new WP_Error( 'square_client_error', 'Failed to initialize Square client' );
			}

			$catalog_api = $this->client->getCatalogApi();
			$response    = $catalog_api->retrieveCatalogObject( $image_id );

			if ( ! $response->isSuccess() ) {
				$errors        = $response->getErrors();
				$error_message = ! empty( $errors ) ? $errors[0]->getDetail() : 'Unknown error';
				$this->log_error( 'Failed to retrieve image from Square: ' . $error_message );

				return new WP_Error( 'square_api_error', $error_message );
			}

			$object = $response->getResult()->getObject();
			if ( ! $object || $object->getType() !== 'IMAGE' ) {
				$this->log_error( 'Invalid image object returned from Square' );

				return new WP_Error( 'square_api_error', 'Invalid image object returned from Square' );
			}

			$image_data = $object->getImageData();
			if ( ! $image_data ) {
				$this->log_error( 'No image data found' );

				return new WP_Error( 'square_api_error', 'No image data found' );
			}

			return array(
				'url' => $image_data->getUrl(),
			);

		} catch ( ApiException $e ) {
			$this->log_error( 'Square API exception while retrieving image: ' . $e->getMessage() );

			return new WP_Error( 'square_api_exception', $e->getMessage() );
		} catch ( \Exception | \Error $e ) {
			$this->log_error( 'Exception while retrieving image from Square: ' . $e->getMessage() );

			return new WP_Error( 'square_error', $e->getMessage() );
		}
	}

	/**
	 * Retrieve inventory count for a catalog object
	 *
	 * @param string $catalog_object_id Square catalog object ID
	 *
	 * @return array|WP_Error Inventory count data or WP_Error on failure
	 */
	public function retrieve_inventory_count( $catalog_object_id ) {
		try {
			if ( ! $this->maybe_init_client() ) {
				return new WP_Error( 'client_error', 'Failed to initialize Square client' );
			}

			$inventory_api = $this->client->getInventoryApi();
			$response      = $inventory_api->retrieveInventoryCount( $catalog_object_id );

			if ( ! $response->isSuccess() ) {
				$errors        = $response->getErrors();
				$error_message = isset( $errors[0] ) ? $errors[0]->getDetail() : 'Unknown error';
				Logger::error( sprintf( 'Failed to retrieve inventory count: %s', $error_message ) );

				return new WP_Error( 'api_error', $error_message );
			}

			$result = $response->getResult();

			return array(
				'counts' => $result->getCounts(),
				'cursor' => $result->getCursor(),
			);

		} catch ( ApiException $e ) {
			Logger::error( sprintf( 'Square API exception while retrieving inventory count: %s', $e->getMessage() ) );

			return new \WP_Error( 'api_exception', $e->getMessage() );
		} catch ( \Exception | \Error $e ) {
			Logger::error( sprintf( 'Exception while retrieving inventory count: %s', $e->getMessage() ) );

			return new WP_Error( 'exception', $e->getMessage() );
		}
	}

	/**
	 * Get all categories from Square catalog.
	 *
	 * @return WP_Error List of categories with id, name, version, image_ids, or WP_Error on failure.
	 * @throws \Exception When syncing with Square API fails
	 */
	public function get_all_square_categories() {
		try {
			Logger::info( 'Starting get_all_square_categories API call.' );
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}

			$catalog_api    = $this->client->getCatalogApi();
			$types          = 'CATEGORY';
			$cursor         = null;
			$all_categories = array();

			do {
				$response = $catalog_api->listCatalog( $cursor, $types );
				if ( ! $response->isSuccess() ) {
					$errors = $response->getErrors();
					Logger::error( 'Failed to list catalog categories: ' . $errors[0]->getDetail() );

					return new \WP_Error( 'square_api_error', isset( $errors[0] ) ? $errors[0]->getDetail() : 'Failed to list catalog categories' );
				}
				$result  = $response->getResult();
				$objects = $result->getObjects();
				if ( is_array( $objects ) ) {
					foreach ( $objects as $object ) {
						if ( $object->getType() === 'CATEGORY' ) {
							$category_data    = $object->getCategoryData();
							$category         = array(
								'id'        => $object->getId(),
								'name'      => $category_data ? $category_data->getName() : '',
								'version'   => $object->getVersion(),
								'image_ids' => $category_data && method_exists( $category_data, 'getImageIds' ) ? $category_data->getImageIds() : array(),
							);
							$all_categories[] = $category;
						}
					}
				}
				$cursor = $result->getCursor();
			} while ( $cursor );

			return $all_categories;
		} catch ( \Exception $e ) {
			Logger::error( 'Exception in get_all_square_categories: ' . $e->getMessage() );

			return new \WP_Error( 'square_api_exception', $e->getMessage() );
		}
	}

	/**
	 * Retrieve a Square order by ID.
	 *
	 * @param string $order_id The Square order ID.
	 *
	 * @return array|false|WP_Error The order data as an array, false if not found, or WP_Error on error.
	 */
	public function get_square_order( $order_id ) {
		try {
			Logger::debug( 'Fetching Square order: ' . $order_id );
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}
			$orders_api = $this->client->getOrdersApi();
			$response   = $orders_api->retrieveOrder( $order_id );
			if ( method_exists( $response, 'isSuccess' ) && $response->isSuccess() ) {
				$result = $response->getResult();
				// Handle both array and object result
				if ( is_array( $result ) && isset( $result['order'] ) ) {
					return $result['order'];
				} elseif ( is_object( $result ) && method_exists( $result, 'getOrder' ) ) {
					return $result->getOrder();
				}

				return false;
			} else {
				$error_message = method_exists( $response, 'getErrors' ) ? wp_json_encode( $response->getErrors(), true ) : 'Unknown error';
				Logger::error( 'Error fetching Square order: ' . $error_message );

				return new \WP_Error( 'square_order_error', $error_message );
			}
		} catch ( Exception $e ) {
			Logger::error( 'Exception fetching Square order: ' . $e->getMessage() );

			return new \WP_Error( 'square_order_exception', $e->getMessage() );
		}
	}

	/**
	 * Retrieve a Square customer by ID.
	 *
	 * @param string $customer_id The Square customer ID.
	 *
	 * @return array|false|WP_Error The customer data as an array, false if not found, or WP_Error on error.
	 */
	public function get_square_customer( $customer_id ) {
		try {
			Logger::debug( 'Fetching Square customer: ' . $customer_id );
			if ( ! $this->maybe_init_client() ) {
				throw new Exception( 'Square client not initialized' );
			}
			$customers_api = $this->client->getCustomersApi();
			$response      = $customers_api->retrieveCustomer( $customer_id );
			if ( method_exists( $response, 'isSuccess' ) && $response->isSuccess() ) {
				$result = $response->getResult();
				if ( is_array( $result ) && isset( $result['customer'] ) ) {
					return $result['customer'];
				} elseif ( is_object( $result ) && method_exists( $result, 'getCustomer' ) ) {
					$customer_obj = $result->getCustomer();

					return json_decode( wp_json_encode( $customer_obj ), true );
				}

				return false;
			} else {
				$error_message = method_exists( $response, 'getErrors' ) ? wp_json_encode( $response->getErrors(), true ) : 'Unknown error';
				Logger::error( 'Error fetching Square customer: ' . $error_message );

				return new \WP_Error( 'square_customer_error', $error_message );
			}
		} catch ( Exception $e ) {
			Logger::error( 'Exception fetching Square customer: ' . $e->getMessage() );

			return new \WP_Error( 'square_customer_exception', $e->getMessage() );
		}
	}

	public static function is_min_suffix() {
		if ( ! defined( 'FKWCSQ_IS_DEV' ) ) {
			return '.min';
		}

		return '';
	}

	/**
	 * Mark an order as on-hold
	 *
	 * @param WC_Order $order
	 * @param string   $message
	 */
	public function mark_order_hold( $order, $message ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return;
		}
		if ( ! $order->has_status( 'on-hold' ) ) {
			$order->update_status( 'on-hold', $message );
		} else {
			$order->add_order_note( $message );
		}
		do_action( 'fkwcsq_order_hold', $order, $message );
	}
}
