<?php
/**
 * REST API Controller for FoodOrd Electron App Connection
 *
 * @package FoodOrd
 */

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

/**
 * Class Foodord_Rest_Api
 */
class Foodord_Rest_Api {

	/**
	 * Time after which on-the-way orders are completed by the orders endpoint.
	 *
	 * @var int
	 */
	private const ONTHEWAY_AUTO_COMPLETE_DELAY = 300;

	/**
	 * Action Scheduler hook used for on-the-way order completion.
	 *
	 * @var string
	 */
	private const ONTHEWAY_AUTO_COMPLETE_HOOK = 'foodord_auto_complete_ontheway_order';

	/**
	 * Action Scheduler group for FoodOrd order jobs.
	 *
	 * @var string
	 */
	private const ONTHEWAY_AUTO_COMPLETE_GROUP = 'foodord-orders';

	/**
	 * API namespace
	 *
	 * @var string
	 */
	private $namespace = 'foodord/v1';

	/**
	 * Initialize REST API
	 */
	public function init() {
		add_action( 'rest_api_init', array( $this, 'register_routes' ) );
		add_filter( 'rest_post_dispatch', array( $this, 'force_no_cache_headers' ), 50, 3 );
		add_action( self::ONTHEWAY_AUTO_COMPLETE_HOOK, array( $this, 'handle_ontheway_auto_completion' ), 10, 1 );
	}

	/**
	 * Ensure FoodOrd routes emit cache-busting headers.
	 *
	 * @param WP_REST_Response|mixed $response Response object.
	 * @param WP_REST_Server         $server   REST server instance.
	 * @param WP_REST_Request        $request  Request instance.
	 * @return mixed
	 */
	public function force_no_cache_headers( $response, $server, $request ) {
		unset( $server );

		if ( ! ( $response instanceof WP_REST_Response ) || ! ( $request instanceof WP_REST_Request ) ) {
			return $response;
		}

		$route_prefix = '/' . trim( $this->namespace, '/' ) . '/';
		$route        = (string) $request->get_route();

		if ( '' === $route || 0 !== strpos( $route, $route_prefix ) ) {
			return $response;
		}

		nocache_headers();
		$response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0' );
		$response->header( 'Pragma', 'no-cache' );
		$response->header( 'Expires', 'Wed, 11 Jan 1984 05:00:00 GMT' );
		$response->header( 'X-WR-Bypass-Cache', '1' );
		$response->header( 'Surrogate-Control', 'no-store' );
		$response->header( 'Cache-Tag', 'foodord_mode' );

		if ( function_exists( 'cachify_flush_cache' ) ) {
			cachify_flush_cache();
		}

		if ( function_exists( 'rocket_clean_domain' ) ) {
			rocket_clean_domain();
		}

		if ( function_exists( 'wpfc_clear_all_cache' ) ) {
			wpfc_clear_all_cache( true );
		}

		if ( function_exists( 'w3tc_flush_all' ) ) {
			w3tc_flush_all();
		}

		if ( function_exists( 'wp_cache_clear_cache' ) ) {
			wp_cache_clear_cache();
		}

		return $response;
	}

	/**
	 * Register REST API routes
	 */
	public function register_routes() {
		// Connection test endpoint.
		register_rest_route(
			$this->namespace,
			'/connection-test',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_connection_test' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// License details endpoint.
		register_rest_route(
			$this->namespace,
			'/license',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_license' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// Delivery pricing endpoints (city/postal & radius).
		register_rest_route(
			$this->namespace,
			'/delivery/pricing',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_delivery_pricing' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/delivery/pricing',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_delivery_pricing' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// Business hours endpoints.
		register_rest_route(
			$this->namespace,
			'/business-hours',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_business_hours' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/business-hours',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_business_hours' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// Orders endpoint.
		register_rest_route(
			$this->namespace,
			'/orders',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_orders' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// Single order endpoint.
		register_rest_route(
			$this->namespace,
			'/orders/(?P<id>\d+)',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_order' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
				),
			)
		);

		// Update order status endpoint.
		register_rest_route(
			$this->namespace,
			'/orders/(?P<id>\d+)/status',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_order_status' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id'     => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
					'status' => array(
						'required'          => false,
						'sanitize_callback' => function ( $value ) {
							return sanitize_text_field( $value );
						},
					),
				),
			),
		);

			register_rest_route(
				$this->namespace,
				'/orders/(?P<id>\d+)/stage',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_order_stage' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
				),
				),
			);

			register_rest_route(
				$this->namespace,
				'/orders/(?P<id>\d+)/seen',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'handle_mark_order_seen' ),
					'permission_callback' => array( $this, 'check_authentication' ),
					'args'                => array(
						'id' => array(
							'required'          => true,
							'validate_callback' => function ( $param ) {
								return is_numeric( $param );
							},
						),
					),
				),
			);

			register_rest_route(
				$this->namespace,
				'/orders/(?P<id>\d+)/confirmation',
				array(
					'methods'             => 'POST',
					'callback'            => array( $this, 'handle_confirm_order' ),
					'permission_callback' => array( $this, 'check_authentication' ),
					'args'                => array(
						'id' => array(
							'required'          => true,
							'validate_callback' => function ( $param ) {
								return is_numeric( $param );
							},
						),
					),
				)
			);

		// Browser-based Live Orders endpoints use the current WordPress session.
		register_rest_route(
			$this->namespace,
			'/admin/live-orders',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_live_orders_admin' ),
				'permission_callback' => array( $this, 'check_live_orders_admin_permission' ),
			)
		);

		register_rest_route(
			$this->namespace,
			'/admin/live-orders/(?P<id>\d+)',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_order' ),
				'permission_callback' => array( $this, 'check_live_orders_admin_permission' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => static function ( $param ) {
							return is_numeric( $param ) && (int) $param > 0;
						},
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/admin/live-orders/(?P<id>\d+)/stage',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_order_stage' ),
				'permission_callback' => array( $this, 'check_live_orders_admin_permission' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => static function ( $param ) {
							return is_numeric( $param ) && (int) $param > 0;
						},
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/admin/live-orders/(?P<id>\d+)/seen',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_mark_order_seen' ),
				'permission_callback' => array( $this, 'check_live_orders_admin_permission' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => static function ( $param ) {
							return is_numeric( $param ) && (int) $param > 0;
						},
					),
				),
			)
		);

		register_rest_route(
			$this->namespace,
			'/admin/live-orders/(?P<id>\d+)/confirmation',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_confirm_order' ),
				'permission_callback' => array( $this, 'check_live_orders_admin_permission' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => static function ( $param ) {
							return is_numeric( $param ) && (int) $param > 0;
						},
					),
				),
			)
		);

		// Products listing endpoint.
		register_rest_route(
			$this->namespace,
			'/products',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_products' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'page'     => array(
						'required'          => false,
						'default'           => 1,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param ) && (int) $param >= 1;
						},
					),
					'per_page' => array(
						'required'          => false,
						'default'           => 24,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param ) && (int) $param >= 1 && (int) $param <= 100;
						},
					),
					'search'   => array(
						'required'          => false,
						'sanitize_callback' => function ( $value ) {
							return sanitize_text_field( $value );
						},
					),
				),
			)
		);

		// POS products listing endpoint.
		register_rest_route(
			$this->namespace,
			'/pos/products',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_pos_products' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'page'     => array(
						'required'          => false,
						'default'           => 1,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param ) && (int) $param >= 1;
						},
					),
					'per_page' => array(
						'required'          => false,
						'default'           => 24,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param ) && (int) $param >= 1 && (int) $param <= 100;
						},
					),
					'search'   => array(
						'required'          => false,
						'sanitize_callback' => function ( $value ) {
							return sanitize_text_field( $value );
						},
					),
					'category' => array(
						'required'          => false,
						'sanitize_callback' => function ( $value ) {
							return sanitize_title( $value );
						},
					),
				),
			)
		);

		// Product availability endpoint.
		register_rest_route(
			$this->namespace,
			'/products/(?P<id>\d+)/availability',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_product_availability' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id'        => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
					'available' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							if ( is_bool( $param ) ) {
								return true;
							}
							$normalized = strtolower( trim( (string) $param ) );
							return in_array( $normalized, array( '0', '1', 'true', 'false', 'yes', 'no', 'on', 'off' ), true );
						},
					),
				),
			)
		);

		// Product pricing endpoint.
		register_rest_route(
			$this->namespace,
			'/products/(?P<id>\d+)/pricing',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_product_price' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
				),
			)
		);

		// Product topping pricing endpoint.
		register_rest_route(
			$this->namespace,
			'/products/(?P<id>\d+)/toppings/pricing',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_product_topping_prices' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'id' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return is_numeric( $param );
						},
					),
				),
			)
		);

		// Store mode status endpoint (fetch).
		register_rest_route(
			$this->namespace,
			'/store/modes',
			array(
				'methods'             => 'GET',
				'callback'            => array( $this, 'handle_get_store_modes' ),
				'permission_callback' => array( $this, 'check_authentication' ),
			)
		);

		// Store mode status endpoint (update).
		register_rest_route(
			$this->namespace,
			'/store/modes/(?P<mode>delivery|pickup)',
			array(
				'methods'             => 'POST',
				'callback'            => array( $this, 'handle_update_store_mode' ),
				'permission_callback' => array( $this, 'check_authentication' ),
				'args'                => array(
					'mode' => array(
						'required'          => true,
						'validate_callback' => function ( $param ) {
							return in_array( $param, array( 'delivery', 'pickup' ), true );
						},
					),
				),
			)
		);
	}

	/**
	 * Authentication check for REST API requests
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return bool|WP_Error
	 */
	public function check_authentication( $request ) {
		// Get license key from header.
		$license_key = $request->get_header( 'X-FoodOrd-License' );

		if ( empty( $license_key ) ) {
			return new WP_Error(
				'missing_license',
				__( 'License key is required', 'foodord' ),
				array( 'status' => 401 )
			);
		}

		// Validate license key.
		if ( ! $this->validate_license_key( $license_key ) ) {
			return new WP_Error(
				'invalid_license',
				__( 'Invalid or expired license key', 'foodord' ),
				array( 'status' => 403 )
			);
		}

		// Check Basic Auth credentials.
		$auth_header = $request->get_header( 'Authorization' );

		if ( empty( $auth_header ) || ! str_starts_with( $auth_header, 'Basic ' ) ) {
			return new WP_Error(
				'missing_auth',
				__( 'Authentication required', 'foodord' ),
				array( 'status' => 401 )
			);
		}

		// Decode Basic Auth.
		$auth_string = substr( $auth_header, 6 );
		$credentials = $this->decode_basic_auth( $auth_string );

		if ( false === $credentials ) {
			return new WP_Error(
				'invalid_auth',
				__( 'Invalid authentication encoding', 'foodord' ),
				array( 'status' => 401 )
			);
		}
		if ( ! str_contains( $credentials, ':' ) ) {
			return new WP_Error(
				'invalid_auth',
				__( 'Invalid authentication format', 'foodord' ),
				array( 'status' => 401 )
			);
		}

		list( $username, $password ) = explode( ':', $credentials, 2 );

		// Authenticate user.
		$user = wp_authenticate( $username, $password );

		if ( is_wp_error( $user ) ) {
			return new WP_Error(
				'auth_failed',
				__( 'Authentication failed', 'foodord' ),
				array( 'status' => 401 )
			);
		}

		// Check if user has required capabilities (manage_options for admins or shop_manager role).
		$has_admin_cap   = user_can( $user, 'manage_options' );
		$is_shop_manager = in_array( 'shop_manager', (array) $user->roles, true );

		if ( ! $has_admin_cap && ! $is_shop_manager ) {
			return new WP_Error(
				'insufficient_permissions',
				__( 'User does not have required permissions', 'foodord' ),
				array( 'status' => 403 )
			);
		}

		// Set current user for the request.
		wp_set_current_user( $user->ID );

		return true;
	}

	/**
	 * Authorize browser-based Live Orders requests with the WordPress session.
	 *
	 * @return true|WP_Error
	 */
	public function check_live_orders_admin_permission() {
		if ( ! is_user_logged_in() ) {
			return new WP_Error(
				'foodord_live_orders_authentication_required',
				__( 'Authentication required', 'foodord' ),
				array( 'status' => 401 )
			);
		}

		if ( ! current_user_can( 'manage_woocommerce' ) && ! current_user_can( 'edit_shop_orders' ) ) {
			return new WP_Error(
				'foodord_live_orders_permission_denied',
				__( 'You do not have permission to manage live orders.', 'foodord' ),
				array( 'status' => 403 )
			);
		}

		return true;
	}

	/**
	 * Decode Basic Auth header
	 *
	 * @param string $auth_string Base64 encoded credentials string.
	 * @return string|false Decoded credentials or false on failure.
	 */
	private function decode_basic_auth( $auth_string ) {
		if ( empty( $auth_string ) ) {
			return false;
		}

		// Validate base64 format.
		if ( ! preg_match( '/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $auth_string ) ) {
			return false;
		}

		/*
		 * Decode for Basic Auth (RFC 7617 - HTTP Basic Authentication).
		 * This is a legitimate and required use of Base64 for authentication.
		 *
		 * @see https://tools.ietf.org/html/rfc7617
		 */
		$auth_string = str_replace( array( "\r", "\n" ), '', $auth_string );

		if ( function_exists( 'sodium_base642bin' ) ) {
			try {
				$decoded = sodium_base642bin( $auth_string, SODIUM_BASE64_VARIANT_ORIGINAL, '' );
			} catch ( SodiumException $exception ) {
				return false;
			}
		} else {
			$decoded = call_user_func( 'base64_decode', $auth_string, true );
		}

		if ( false === $decoded || '' === $decoded ) {
			return false;
		}

		return $decoded;
	}

	/**
	 * Validate license key
	 *
	 * @param string $license_key License key to validate.
	 * @return bool
	 */
	private function validate_license_key( $license_key ) {
		if ( empty( $license_key ) ) {
			return false;
		}

		// Get stored license key.
		$stored_license = get_option( 'foodord_license_key', '' );

		if ( empty( $stored_license ) ) {
			return false;
		}

		// Compare license keys.
		if ( $license_key !== $stored_license ) {
			return false;
		}

		if ( function_exists( 'foodord_sync_license_status' ) ) {
			foodord_sync_license_status();
		}

		// Check license status (if you have license activation system).
		$license_status = get_option( 'foodord_license_status', 'inactive' );

		if ( 'active' !== $license_status ) {
			return false;
		}

		return true;
	}

	/**
	 * Handle connection test request
	 *
	 * @param WP_REST_Request $request Request object (unused but required by REST API).
	 * @return WP_REST_Response
	 */
	public function handle_connection_test( $request ) {
		unset( $request ); // Unused but required for REST API callback signature.
		$current_user = wp_get_current_user();

		return new WP_REST_Response(
			array(
				'success'   => true,
				'message'   => __( 'Connection successful', 'foodord' ),
				'site_name' => get_bloginfo( 'name' ),
				'site_url'  => get_site_url(),
				'user'      => array(
					'id'    => $current_user->ID,
					'login' => $current_user->user_login,
					'email' => $current_user->user_email,
					'name'  => $current_user->display_name,
				),
				'foodord'   => array(
					'version' => FOODORD_VERSION,
					'license' => get_option( 'foodord_license_status', 'inactive' ),
				),
			),
			200
		);
	}

	/**
	 * Handle license details request
	 *
	 * @param WP_REST_Request $request Request object (unused but required by REST API).
	 * @return WP_REST_Response
	 */
	public function handle_get_license( $request ) {
		unset( $request ); // Unused but required for REST API callback signature.

		$license_key     = get_option( 'foodord_license_key', '' );
		$license_status  = get_option( 'foodord_license_status', 'inactive' );
		$license_details = get_option( 'foodord_license_details', array() );

		if ( ! is_array( $license_details ) ) {
			$license_details = array();
		}

		return new WP_REST_Response(
			array(
				'success' => true,
				'license' => $license_key,
				'status'  => $license_status,
				'details' => $license_details,
			),
			200
		);
	}

	/**
	 * Handle the lightweight browser Live Orders feed.
	 *
	 * Full line-item data is intentionally excluded from polling responses and
	 * loaded only when an operator opens an order.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function handle_get_live_orders_admin( $request ) {
		unset( $request );

		if ( ! function_exists( 'wc_get_orders' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		$orders = wc_get_orders(
			array(
				'limit'   => -1,
				'orderby' => 'date',
				'order'   => 'ASC',
				'status'  => array( 'processing' ),
			)
		);
		$quota  = class_exists( 'Foodord_Live_Orders_Quota' ) ? Foodord_Live_Orders_Quota::get_status() : array();
		if ( class_exists( 'Foodord_Live_Orders_Quota' ) ) {
			$orders = Foodord_Live_Orders_Quota::filter_feed_orders( $orders, $quota );
		}
		$feed = array();

		foreach ( $orders as $order ) {
			if (
				! $order instanceof WC_Order
				|| foodord_is_excluded_order( $order )
				|| Foodord_Table_Reservation_Deposits::is_reservation_payment_order( $order )
				|| ! $this->is_order_ready_for_app( $order )
			) {
				continue;
			}

			$shipping_lines = array();
			foreach ( $order->get_items( 'shipping' ) as $shipping_item ) {
				$shipping_lines[] = array(
					'method_id'    => $shipping_item->get_method_id(),
					'method_title' => $shipping_item->get_name(),
				);
			}

			$mode_meta = strtolower( (string) $order->get_meta( 'foodord_order_mode', true ) );
			$mode      = in_array( $mode_meta, array( 'delivery', 'pickup' ), true ) ? $mode_meta : '';

			if ( '' === $mode ) {
				foreach ( $shipping_lines as $shipping_line ) {
					if ( ! empty( $shipping_line['method_id'] ) && str_contains( $shipping_line['method_id'], 'pickup' ) ) {
						$mode = 'pickup';
						break;
					}
				}
			}

			if ( '' === $mode && str_contains( strtolower( (string) $order->get_shipping_method() ), 'pickup' ) ) {
				$mode = 'pickup';
			}
			if ( '' === $mode ) {
				$mode = 'delivery';
			}

			$delivery_time = $this->get_order_meta_value(
				$order,
				array( 'foodord_delivery_time', '_foodord_delivery_time' )
			);
			$pickup_time   = $this->get_order_meta_value(
				$order,
				array( 'foodord_pickup_time', '_foodord_pickup_time' )
			);
			$schedule      = $this->build_order_schedule_payload( $order, $mode, $delivery_time, $pickup_time );
			$date_created  = $order->get_date_created();
			$seen_payload  = $this->build_order_seen_payload( $order );
			$confirmation  = self::build_order_confirmation_payload( $order );

			$feed[] = array(
				'id'                           => $order->get_id(),
				'number'                       => $order->get_order_number(),
				'status'                       => $order->get_status(),
				'total'                        => $order->get_total(),
				'currency'                     => $order->get_currency(),
				'date'                         => $date_created ? $date_created->date( 'Y-m-d H:i:s' ) : null,
				'date_display'                 => $this->format_order_datetime( $date_created ),
				'customer'                     => array(
					'name' => $order->get_formatted_billing_full_name(),
				),
				'items_count'                  => $order->get_item_count(),
				'fulfillment_type'             => $mode,
				'pickup_time'                  => $pickup_time,
				'delivery_time'                => $delivery_time,
				'requested_date'               => $schedule['date'],
				'requested_time'               => $schedule['time'],
				'requested_datetime'           => $schedule['datetime'],
				'requested_clock_display'      => $schedule['time_display'],
				'requested_time_display'       => $schedule['display'],
				'billing'                      => array(
					'address_1' => $order->get_billing_address_1(),
					'address_2' => $order->get_billing_address_2(),
					'city'      => $order->get_billing_city(),
					'postcode'  => $order->get_billing_postcode(),
				),
				'shipping'                     => array(
					'address_1' => $order->get_shipping_address_1(),
					'address_2' => $order->get_shipping_address_2(),
					'city'      => $order->get_shipping_city(),
					'postcode'  => $order->get_shipping_postcode(),
				),
				'confirmation'                 => $confirmation,
				'restaurant_confirmed_display' => $confirmation['confirmed_display'],
				'progress'                     => self::build_order_stage_payload( $order ),
				'seen'                         => $seen_payload['seen'],
				'seen_at'                      => $seen_payload['seen_at'],
				'is_new'                       => $seen_payload['is_new'],
			);
		}

		$remaining     = isset( $quota['remaining'] ) ? absint( $quota['remaining'] ) : null;
		$quota_payload = array(
			'known'     => ! empty( $quota['known'] ),
			'licensed'  => ! empty( $quota['licensed'] ),
			'limit'     => absint( $quota['limit'] ?? 0 ),
			'used'      => absint( $quota['used'] ?? 0 ),
			'remaining' => $remaining,
			'message'   => null !== $remaining ? Foodord_Live_Orders_Quota::get_remaining_message( $remaining ) : '',
		);

		return new WP_REST_Response(
			array(
				'success' => true,
				'orders'  => $feed,
				'total'   => count( $feed ),
				'quota'   => $quota_payload,
			),
			200
		);
	}

	/**
	 * Handle get orders request
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function handle_get_orders( $request ) {
		if ( ! function_exists( 'wc_get_orders' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		// Get query parameters.
		$page     = $request->get_param( 'page' ) ?? 1;
		$per_page = $request->get_param( 'per_page' ) ?? 10;

		$query_args = array(
			'limit'   => (int) $per_page,
			'page'    => (int) $page,
			'orderby' => 'date',
			'order'   => 'DESC',
			'status'  => array( 'processing', 'completed' ),
		);

		// Get orders.
		$orders = wc_get_orders( $query_args );

		$formatted_orders = array();

		foreach ( $orders as $order ) {
			if (
				foodord_is_excluded_order( $order )
				|| Foodord_Table_Reservation_Deposits::is_reservation_payment_order( $order )
			) {
				continue;
			}
			$payment_ready = $this->is_order_ready_for_app( $order );
			if ( ! $payment_ready ) {
				continue;
			}
			$items = array();
			foreach ( $order->get_items() as $item ) {
				$toppings               = $this->build_order_item_toppings( $item );
				$meta_entries           = $this->build_order_item_meta_entries( $item, 6, ! empty( $toppings ) );
				$item_note              = $this->get_order_item_note( $item );
				$line_total_incl_tax    = $this->round_order_amount( (float) $item->get_total() + (float) $item->get_total_tax() );
				$line_subtotal_incl_tax = $this->round_order_amount( (float) $item->get_subtotal() + (float) $item->get_subtotal_tax() );
				$item_data              = array(
					'id'                     => $item->get_id(),
					'name'                   => $item->get_name(),
					'product_id'             => $item->get_product_id(),
					'variation_id'           => $item->get_variation_id(),
					'quantity'               => $item->get_quantity(),
					'line_total'             => $item->get_total(),
					'total'                  => $item->get_total(),
					'subtotal'               => $item->get_subtotal(),
					'line_total_incl_tax'    => $line_total_incl_tax,
					'total_incl_tax'         => $line_total_incl_tax,
					'line_subtotal_incl_tax' => $line_subtotal_incl_tax,
					'subtotal_incl_tax'      => $line_subtotal_incl_tax,
					'total_tax'              => (float) $item->get_total_tax(),
					'subtotal_tax'           => (float) $item->get_subtotal_tax(),
					'currency'               => $order->get_currency(),
					'meta_data'              => $meta_entries,
					'toppings'               => $toppings,
					'toppings_extra'         => (float) $item->get_meta( '_foodord_toppings_extra', true ),
				);

				$item_data['note']         = $item_note;
				$item_data['foodord_note'] = $item_note;

				$items[] = $item_data;
			}
			$shipping_lines = array();
			foreach ( $order->get_items( 'shipping' ) as $shipping_item ) {
				$shipping_lines[] = array(
					'id'           => $shipping_item->get_id(),
					'instance_id'  => $shipping_item->get_instance_id(),
					'method_id'    => $shipping_item->get_method_id(),
					'method_title' => $shipping_item->get_name(),
					'total'        => (float) $shipping_item->get_total(),
					'total_tax'    => (float) $shipping_item->get_total_tax(),
				);
			}

			$delivery_total = $this->calculate_delivery_total( $order );
			$fee_lines      = $this->build_order_fee_payload( $order );
			$tax_lines      = $this->build_order_tax_payload( $order );
			$tip_data       = $this->build_order_tip_payload( $order );

			$billing_address = array(
				'first_name' => $order->get_billing_first_name(),
				'last_name'  => $order->get_billing_last_name(),
				'company'    => $order->get_billing_company(),
				'address_1'  => $order->get_billing_address_1(),
				'address_2'  => $order->get_billing_address_2(),
				'city'       => $order->get_billing_city(),
				'state'      => $order->get_billing_state(),
				'postcode'   => $order->get_billing_postcode(),
				'country'    => $order->get_billing_country(),
				'email'      => $order->get_billing_email(),
				'phone'      => $order->get_billing_phone(),
			);

			$shipping_address = array(
				'first_name' => $order->get_shipping_first_name(),
				'last_name'  => $order->get_shipping_last_name(),
				'company'    => $order->get_shipping_company(),
				'address_1'  => $order->get_shipping_address_1(),
				'address_2'  => $order->get_shipping_address_2(),
				'city'       => $order->get_shipping_city(),
				'state'      => $order->get_shipping_state(),
				'postcode'   => $order->get_shipping_postcode(),
				'country'    => $order->get_shipping_country(),
				'email'      => $order->get_billing_email(),
				'phone'      => $order->get_billing_phone(),
			);

			$mode_meta = strtolower( (string) $order->get_meta( 'foodord_order_mode', true ) );
			$mode      = in_array( $mode_meta, array( 'delivery', 'pickup' ), true ) ? $mode_meta : '';

			$delivery_time = $this->get_order_meta_value(
				$order,
				array( 'foodord_delivery_time', '_foodord_delivery_time' )
			);
			$pickup_time   = $this->get_order_meta_value(
				$order,
				array( 'foodord_pickup_time', '_foodord_pickup_time' )
			);

			if ( '' === $mode ) {
				foreach ( $shipping_lines as $shipping_line ) {
					if ( ! empty( $shipping_line['method_id'] ) && str_contains( $shipping_line['method_id'], 'pickup' ) ) {
						$mode = 'pickup';
						break;
					}
				}
			}

			if ( '' === $mode ) {
				$shipping_method_name = strtolower( (string) $order->get_shipping_method() );
				if ( str_contains( $shipping_method_name, 'pickup' ) ) {
					$mode = 'pickup';
				}
			}

			if ( '' === $mode && $order->get_shipping_total() > 0 ) {
				$mode = 'delivery';
			}

			if ( '' === $mode ) {
				$mode = 'delivery';
			}

			$date_created = $order->get_date_created();
			$schedule     = $this->build_order_schedule_payload( $order, $mode, $delivery_time, $pickup_time );
			$seen_payload = $this->build_order_seen_payload( $order );
			$confirmation = self::build_order_confirmation_payload( $order );
			$paid_date    = $order->get_date_paid();

			$formatted_orders[] = array(
				'id'                           => $order->get_id(),
				'number'                       => $order->get_order_number(),
				'status'                       => $order->get_status(),
				'total'                        => $order->get_total(),
				'subtotal'                     => $order->get_subtotal(),
				'subtotal_incl_tax'            => $this->get_order_subtotal_including_tax( $order ),
				'currency'                     => $order->get_currency(),
				'date'                         => $date_created ? $date_created->date( 'Y-m-d H:i:s' ) : null,
				'date_display'                 => $this->format_order_datetime( $date_created ),
				'paid_at'                      => $paid_date ? $paid_date->date( 'Y-m-d H:i:s' ) : null,
				'is_paid'                      => (bool) $order->is_paid(),
				'payment_verified'             => $payment_ready,
				'customer'                     => array(
					'name'  => $order->get_formatted_billing_full_name(),
					'email' => $order->get_billing_email(),
					'phone' => $order->get_billing_phone(),
				),
				'items_count'                  => $order->get_item_count(),
				'shipping_total'               => $delivery_total,
				'delivery_fee'                 => $delivery_total,
				'shipping_method'              => $order->get_shipping_method(),
				'shipping_lines'               => $shipping_lines,
				'fees'                         => $fee_lines,
				'fee_lines'                    => $fee_lines,
				'feeLines'                     => $fee_lines,
				'tax_lines'                    => $tax_lines,
				'taxLines'                     => $tax_lines,
				'fulfillment_type'             => $mode,
				'payment_method'               => $order->get_payment_method(),
				'payment_method_title'         => $order->get_payment_method_title(),
				'transaction_id'               => $order->get_transaction_id(),
				'customer_note'                => $order->get_customer_note(),
				'note'                         => $order->get_customer_note(),
				'pickup_time'                  => $pickup_time,
				'delivery_time'                => $delivery_time,
				'requested_date'               => $schedule['date'],
				'requested_time'               => $schedule['time'],
				'requested_datetime'           => $schedule['datetime'],
				'requested_clock_display'      => $schedule['time_display'],
				'requested_time_display'       => $schedule['display'],
				'total_tax'                    => $order->get_total_tax(),
				'discount_total'               => $order->get_total_discount(),
				'billing'                      => $billing_address,
				'shipping'                     => $shipping_address,
				'meta_data'                    => $this->build_order_meta_payload( $order ),
				'items'                        => $items,
				'line_items'                   => $items,
				'tip_amount'                   => $tip_data['amount'],
				'tip_total'                    => $tip_data['amount'],
				'tip'                          => $tip_data['amount'],
				'gratuity'                     => $tip_data['amount'],
				'gratuity_amount'              => $tip_data['amount'],
				'tip_currency'                 => $tip_data['currency'],
				'tip_type'                     => $tip_data['type'],
				'tip_value'                    => $tip_data['value'],
				'tip_source'                   => $tip_data['source'],
				'confirmation'                 => $confirmation,
				'restaurant_confirmed_time'    => $confirmation['confirmed_slot'] ? $confirmation['confirmed_slot'] : $confirmation['confirmed_at'],
				'restaurant_confirmed_display' => $confirmation['confirmed_display'],
				'restaurant_confirmed_minutes' => $confirmation['confirmed_minutes'],
				'progress'                     => self::build_order_stage_payload( $order ),
				'seen'                         => $seen_payload['seen'],
				'seen_at'                      => $seen_payload['seen_at'],
				'is_new'                       => $seen_payload['is_new'],
			);
		}

		return new WP_REST_Response(
			array(
				'success' => true,
				'orders'  => $formatted_orders,
				'total'   => count( $formatted_orders ),
				'page'    => $page,
			),
			200
		);
	}

	/**
	 * Handle get single order request
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function handle_get_order( $request ) {
		if ( ! function_exists( 'wc_get_order' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		$order_id = $request->get_param( 'id' );
		$order    = wc_get_order( $order_id );

		if ( ! $order ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		if ( foodord_is_excluded_order( $order ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		// Format order items.
		$items = array();
		foreach ( $order->get_items() as $item ) {
			$toppings     = $this->build_order_item_toppings( $item );
			$meta_entries = $this->build_order_item_meta_entries( $item, 0, ! empty( $toppings ) );

			$item_note              = $this->get_order_item_note( $item );
			$line_total_incl_tax    = $this->round_order_amount( (float) $item->get_total() + (float) $item->get_total_tax() );
			$line_subtotal_incl_tax = $this->round_order_amount( (float) $item->get_subtotal() + (float) $item->get_subtotal_tax() );
			$item_data              = array(
				'id'                     => $item->get_id(),
				'name'                   => $item->get_name(),
				'product_id'             => $item->get_product_id(),
				'variation_id'           => $item->get_variation_id(),
				'quantity'               => $item->get_quantity(),
				'line_total'             => $item->get_total(),
				'total'                  => $item->get_total(),
				'subtotal'               => $item->get_subtotal(),
				'line_total_incl_tax'    => $line_total_incl_tax,
				'total_incl_tax'         => $line_total_incl_tax,
				'line_subtotal_incl_tax' => $line_subtotal_incl_tax,
				'subtotal_incl_tax'      => $line_subtotal_incl_tax,
				'total_tax'              => (float) $item->get_total_tax(),
				'subtotal_tax'           => (float) $item->get_subtotal_tax(),
				'currency'               => $order->get_currency(),
				'meta_data'              => $meta_entries,
				'toppings'               => $toppings,
				'toppings_extra'         => (float) $item->get_meta( '_foodord_toppings_extra', true ),
			);

			$item_data['note']         = $item_note;
			$item_data['foodord_note'] = $item_note;

			$items[] = $item_data;
		}

		$shipping_lines = array();
		foreach ( $order->get_items( 'shipping' ) as $shipping_item ) {
			$shipping_lines[] = array(
				'id'           => $shipping_item->get_id(),
				'instance_id'  => $shipping_item->get_instance_id(),
				'method_id'    => $shipping_item->get_method_id(),
				'method_title' => $shipping_item->get_name(),
				'total'        => (float) $shipping_item->get_total(),
				'total_tax'    => (float) $shipping_item->get_total_tax(),
			);
		}

		$delivery_total = $this->calculate_delivery_total( $order );
		$fee_lines      = $this->build_order_fee_payload( $order );
		$tax_lines      = $this->build_order_tax_payload( $order );
		$tip_data       = $this->build_order_tip_payload( $order );

		$billing_address = array(
			'first_name' => $order->get_billing_first_name(),
			'last_name'  => $order->get_billing_last_name(),
			'company'    => $order->get_billing_company(),
			'address_1'  => $order->get_billing_address_1(),
			'address_2'  => $order->get_billing_address_2(),
			'city'       => $order->get_billing_city(),
			'state'      => $order->get_billing_state(),
			'postcode'   => $order->get_billing_postcode(),
			'country'    => $order->get_billing_country(),
			'email'      => $order->get_billing_email(),
			'phone'      => $order->get_billing_phone(),
		);

		$shipping_address = array(
			'first_name' => $order->get_shipping_first_name(),
			'last_name'  => $order->get_shipping_last_name(),
			'company'    => $order->get_shipping_company(),
			'address_1'  => $order->get_shipping_address_1(),
			'address_2'  => $order->get_shipping_address_2(),
			'city'       => $order->get_shipping_city(),
			'state'      => $order->get_shipping_state(),
			'postcode'   => $order->get_shipping_postcode(),
			'country'    => $order->get_shipping_country(),
			'email'      => $order->get_billing_email(),
			'phone'      => $order->get_billing_phone(),
		);

		$mode_meta = strtolower( (string) $order->get_meta( 'foodord_order_mode', true ) );
		$mode      = in_array( $mode_meta, array( 'delivery', 'pickup' ), true ) ? $mode_meta : '';

		if ( '' === $mode ) {
			foreach ( $shipping_lines as $shipping_line ) {
				if ( ! empty( $shipping_line['method_id'] ) && str_contains( $shipping_line['method_id'], 'pickup' ) ) {
					$mode = 'pickup';
					break;
				}
			}
		}

		if ( '' === $mode ) {
			$shipping_method_name = strtolower( (string) $order->get_shipping_method() );
			if ( str_contains( $shipping_method_name, 'pickup' ) ) {
				$mode = 'pickup';
			}
		}

		if ( '' === $mode && $order->get_shipping_total() > 0 ) {
			$mode = 'delivery';
		}

		if ( '' === $mode ) {
			$mode = 'delivery';
		}

		$delivery_time = $this->get_order_meta_value(
			$order,
			array( 'foodord_delivery_time', '_foodord_delivery_time' )
		);
		$pickup_time   = $this->get_order_meta_value(
			$order,
			array( 'foodord_pickup_time', '_foodord_pickup_time' )
		);
		$schedule      = $this->build_order_schedule_payload( $order, $mode, $delivery_time, $pickup_time );
		$seen_payload  = $this->build_order_seen_payload( $order );
		$confirmation  = self::build_order_confirmation_payload( $order );
		$date_created  = $order->get_date_created();
		$paid_date     = $order->get_date_paid();
		$payment_ready = $this->is_order_ready_for_app( $order );

		return new WP_REST_Response(
			array(
				'success' => true,
				'order'   => array(
					'id'                           => $order->get_id(),
					'number'                       => $order->get_order_number(),
					'status'                       => $order->get_status(),
					'total'                        => $order->get_total(),
					'subtotal'                     => $order->get_subtotal(),
					'subtotal_incl_tax'            => $this->get_order_subtotal_including_tax( $order ),
					'total_tax'                    => $order->get_total_tax(),
					'tax_total'                    => $order->get_total_tax(),
					'shipping_total'               => $delivery_total,
					'delivery_fee'                 => $delivery_total,
					'discount_total'               => $order->get_total_discount(),
					'currency'                     => $order->get_currency(),
					'date'                         => $date_created ? $date_created->date( 'Y-m-d H:i:s' ) : null,
					'date_display'                 => $this->format_order_datetime( $date_created ),
					'paid_at'                      => $paid_date ? $paid_date->date( 'Y-m-d H:i:s' ) : null,
					'is_paid'                      => (bool) $order->is_paid(),
					'payment_verified'             => $payment_ready,
					'payment_method'               => $order->get_payment_method(),
					'payment_method_title'         => $order->get_payment_method_title(),
					'transaction_id'               => $order->get_transaction_id(),
					'customer_note'                => $order->get_customer_note(),
					'note'                         => $order->get_customer_note(),
					'shipping_method'              => $order->get_shipping_method(),
					'fulfillment_type'             => $mode,
					'customer'                     => array(
						'name'  => $order->get_formatted_billing_full_name(),
						'email' => $order->get_billing_email(),
						'phone' => $order->get_billing_phone(),
					),
					'billing'                      => $billing_address,
					'shipping'                     => $shipping_address,
					'shipping_lines'               => $shipping_lines,
					'fees'                         => $fee_lines,
					'fee_lines'                    => $fee_lines,
					'feeLines'                     => $fee_lines,
					'tax_lines'                    => $tax_lines,
					'taxLines'                     => $tax_lines,
					'pickup_time'                  => $pickup_time,
					'delivery_time'                => $delivery_time,
					'requested_date'               => $schedule['date'],
					'requested_time'               => $schedule['time'],
					'requested_datetime'           => $schedule['datetime'],
					'requested_clock_display'      => $schedule['time_display'],
					'requested_time_display'       => $schedule['display'],
					'meta_data'                    => $this->build_order_meta_payload( $order ),
					'items'                        => $items,
					'line_items'                   => $items,
					'tip_amount'                   => $tip_data['amount'],
					'tip_total'                    => $tip_data['amount'],
					'tip'                          => $tip_data['amount'],
					'gratuity'                     => $tip_data['amount'],
					'gratuity_amount'              => $tip_data['amount'],
					'tip_currency'                 => $tip_data['currency'],
					'tip_type'                     => $tip_data['type'],
					'tip_value'                    => $tip_data['value'],
					'tip_source'                   => $tip_data['source'],
					'confirmation'                 => $confirmation,
					'restaurant_confirmed_time'    => $confirmation['confirmed_slot'] ? $confirmation['confirmed_slot'] : $confirmation['confirmed_at'],
					'restaurant_confirmed_display' => $confirmation['confirmed_display'],
					'restaurant_confirmed_minutes' => $confirmation['confirmed_minutes'],
					'progress'                     => self::build_order_stage_payload( $order ),
					'seen'                         => $seen_payload['seen'],
					'seen_at'                      => $seen_payload['seen_at'],
					'is_new'                       => $seen_payload['is_new'],
				),
			),
			200
		);
	}

	/**
	 * Handle custom order stage update request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
		public function handle_update_order_stage( $request ) {
		if ( ! function_exists( 'wc_get_order' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		$order_id = absint( $request->get_param( 'id' ) );
		if ( $order_id <= 0 ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Invalid order identifier', 'foodord' ),
				),
				400
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		if ( function_exists( 'foodord_is_excluded_order' ) && foodord_is_excluded_order( $order ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = array();
		}

		$stage_param = '';
		if ( $request->has_param( 'stage' ) ) {
			$stage_param = $request->get_param( 'stage' );
		} elseif ( array_key_exists( 'stage', $params ) ) {
			$stage_param = $params['stage'];
		}

		$normalized_stage = self::normalize_progress_stage( $stage_param );
		if ( '' === $normalized_stage && '' !== trim( (string) $stage_param ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Invalid stage value.', 'foodord' ),
				),
				400
			);
		}

		if ( '' === $normalized_stage ) {
			$normalized_stage = 'new';
		}

		$timestamp = time();
		$iso_utc   = gmdate( 'c', $timestamp );
		$iso_local = function_exists( 'wp_date' ) ? wp_date( 'c', $timestamp ) : date_i18n( 'c', $timestamp );

		$order->update_meta_data( 'foodord_order_stage', $normalized_stage );
		$order->update_meta_data( 'foodord_order_stage_updated_at', $iso_utc );
		$order->update_meta_data( 'foodord_order_stage_updated_local', $iso_local );

		if ( 'new' === $normalized_stage ) {
			$order->delete_meta_data( 'foodord_stage_kitchen_at' );
			$order->delete_meta_data( 'foodord_stage_kitchen_at_local' );
		} elseif ( 'kitchen' === $normalized_stage ) {
			$order->update_meta_data( 'foodord_stage_kitchen_at', $iso_utc );
			$order->update_meta_data( 'foodord_stage_kitchen_at_local', $iso_local );
		} elseif ( in_array( $normalized_stage, array( 'ready', 'ontheway' ), true ) ) {
			$existing_kitchen = (string) $order->get_meta( 'foodord_stage_kitchen_at', true );
			if ( '' === trim( $existing_kitchen ) ) {
				$order->update_meta_data( 'foodord_stage_kitchen_at', $iso_utc );
				$order->update_meta_data( 'foodord_stage_kitchen_at_local', $iso_local );
			}
		}

		$order->save();
		self::sync_ontheway_auto_completion( $order_id, $normalized_stage, $timestamp );

		$progress = self::build_order_stage_payload( $order );

		return new WP_REST_Response(
			array(
				'success'  => true,
				'stage'    => $progress['stage'],
				'progress' => $progress,
			),
				200
			);
		}

		/**
		 * Handle order seen update request.
		 *
		 * @param WP_REST_Request $request Request object.
		 * @return WP_REST_Response
		 */
		public function handle_mark_order_seen( $request ) {
			if ( ! function_exists( 'wc_get_order' ) ) {
				return new WP_REST_Response(
					array(
						'success' => false,
						'message' => __( 'WooCommerce is not active', 'foodord' ),
					),
					500
				);
			}

			$order_id = absint( $request->get_param( 'id' ) );
			if ( $order_id <= 0 ) {
				return new WP_REST_Response(
					array(
						'success' => false,
						'message' => __( 'Invalid order identifier', 'foodord' ),
					),
					400
				);
			}

			$order = wc_get_order( $order_id );
			if ( ! $order ) {
				return new WP_REST_Response(
					array(
						'success' => false,
						'message' => __( 'Order not found', 'foodord' ),
					),
					404
				);
			}

			if ( function_exists( 'foodord_is_excluded_order' ) && foodord_is_excluded_order( $order ) ) {
				return new WP_REST_Response(
					array(
						'success' => false,
						'message' => __( 'Order not found', 'foodord' ),
					),
					404
				);
			}

			$this->mark_order_seen( $order );
			$seen_payload = $this->build_order_seen_payload( $order );

			return new WP_REST_Response(
				array(
					'success'  => true,
					'order_id' => $order->get_id(),
					'seen'     => $seen_payload['seen'],
					'seen_at'  => $seen_payload['seen_at'],
					'is_new'   => $seen_payload['is_new'],
				),
				200
			);
		}

		/**
		 * Handle order status update request
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function handle_update_order_status( $request ) {
		if ( ! function_exists( 'wc_get_order' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		$order_id = absint( $request->get_param( 'id' ) );
		if ( $order_id <= 0 ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Invalid order identifier', 'foodord' ),
				),
				400
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		$requested_status = $request->get_param( 'status' );
		if ( empty( $requested_status ) ) {
			$requested_status = 'completed';
		}

		$normalized_status = strtolower( (string) $requested_status );
		if ( 0 === strpos( $normalized_status, 'wc-' ) ) {
			$normalized_status = substr( $normalized_status, 3 );
		}
		$normalized_status = sanitize_key( $normalized_status );

		$available_statuses = function_exists( 'wc_get_order_statuses' ) ? wc_get_order_statuses() : array();
		$valid_statuses     = array();
		foreach ( $available_statuses as $slug => $label ) {
			unset( $label );
			$clean_slug                    = 0 === strpos( $slug, 'wc-' ) ? substr( $slug, 3 ) : $slug;
			$valid_statuses[ $clean_slug ] = true;
		}

		if ( empty( $valid_statuses[ $normalized_status ] ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Unsupported order status', 'foodord' ),
				),
				400
			);
		}

		try {
			$order->update_status(
				$normalized_status,
				__( 'Order auto-completed via FoodOrd App', 'foodord' ),
				true
			);
		} catch ( \Exception $exception ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => $exception->getMessage(),
				),
				500
			);
		}

		$this->mark_order_seen( $order );
		$seen_payload = $this->build_order_seen_payload( $order );

		$current_status = $order->get_status();
		$status_label   = function_exists( 'wc_get_order_status_name' ) ? wc_get_order_status_name( $current_status ) : $current_status;

		return new WP_REST_Response(
			array(
				'success'      => true,
				'status'       => $current_status,
				'status_label' => $status_label,
				'order_id'     => $order->get_id(),
				'seen'         => $seen_payload['seen'],
				'seen_at'      => $seen_payload['seen_at'],
				'is_new'       => $seen_payload['is_new'],
			),
			200
		);
	}

	/**
	 * Handle restaurant confirmation request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response
	 */
	public function handle_confirm_order( $request ) {
		if ( ! function_exists( 'wc_get_order' ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'WooCommerce is not active', 'foodord' ),
				),
				500
			);
		}

		$order_id = absint( $request->get_param( 'id' ) );
		if ( $order_id <= 0 ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Invalid order identifier', 'foodord' ),
				),
				400
			);
		}

		$order = wc_get_order( $order_id );
		if ( ! $order ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		if ( function_exists( 'foodord_is_excluded_order' ) && foodord_is_excluded_order( $order ) ) {
			return new WP_REST_Response(
				array(
					'success' => false,
					'message' => __( 'Order not found', 'foodord' ),
				),
				404
			);
		}

		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = array();
		}

		$confirmed_at_raw    = isset( $params['confirmed_at'] ) ? (string) $params['confirmed_at'] : '';
		$confirmed_timestamp = $confirmed_at_raw ? strtotime( $confirmed_at_raw ) : false;
		if ( false === $confirmed_timestamp ) {
			$confirmed_timestamp = time();
		}

		$iso_utc   = gmdate( 'c', $confirmed_timestamp );
		$iso_local = function_exists( 'wp_date' ) ? wp_date( 'c', $confirmed_timestamp ) : date_i18n( 'c', $confirmed_timestamp );

		$selection      = isset( $params['selection'] ) && is_array( $params['selection'] ) ? $params['selection'] : array();
		$selection_type = isset( $selection['type'] ) ? strtolower( (string) $selection['type'] ) : '';
		$selection_val  = $selection['value'] ?? null;

		$confirmed_minutes = null;
		$confirmed_slot    = '';
		$confirmation_type = '';

		if ( 'duration' === $selection_type ) {
			if ( is_numeric( $selection_val ) ) {
				$minutes = absint( $selection_val );
				if ( $minutes > 0 && $minutes <= 720 ) {
					$confirmed_minutes = $minutes;
					$confirmation_type = 'duration';
				}
			}
		} elseif ( 'scheduled' === $selection_type ) {
			if ( is_string( $selection_val ) ) {
				$candidate = trim( $selection_val );
				if ( '' !== $candidate ) {
					$slot_ts        = strtotime( $candidate );
					$confirmed_slot = false !== $slot_ts ? gmdate( 'c', $slot_ts ) : sanitize_text_field( $candidate );
				}
			} elseif ( is_numeric( $selection_val ) ) {
				$confirmed_slot = gmdate( 'c', (int) $selection_val );
			}
			if ( '' !== $confirmed_slot ) {
				$confirmation_type = 'scheduled';
			}
		}

		if ( '' === $confirmation_type ) {
			$confirmation_type = 'timestamp';
		}

		$order->update_meta_data( 'foodord_confirmed_at', $iso_utc );
		$order->update_meta_data( 'foodord_confirmed_at_local', $iso_local );
		if ( null !== $confirmed_minutes ) {
			$order->update_meta_data( 'foodord_confirmed_minutes', $confirmed_minutes );
		} else {
			$order->delete_meta_data( 'foodord_confirmed_minutes' );
		}
		if ( '' !== $confirmed_slot ) {
			$order->update_meta_data( 'foodord_confirmed_slot', $confirmed_slot );
		} else {
			$order->delete_meta_data( 'foodord_confirmed_slot' );
		}
		$order->update_meta_data( 'foodord_confirmation_type', $confirmation_type );
		$order->update_meta_data( 'foodord_confirmed_source', 'app' );

		$order->update_meta_data(
			'foodord_app_receipt_status',
			esc_html__( 'Receipt printed', 'foodord' )
		);
		$order->save();

		$this->mark_order_seen( $order );
		$confirmation = self::build_order_confirmation_payload( $order );
		$seen_payload = $this->build_order_seen_payload( $order );
		$progress     = self::build_order_stage_payload( $order );

		return new WP_REST_Response(
			array(
				'success'      => true,
				'confirmation' => $confirmation,
				'progress'     => $progress,
				'seen'         => $seen_payload['seen'],
				'seen_at'      => $seen_payload['seen_at'],
				'is_new'       => $seen_payload['is_new'],
			),
			200
		);
	}

	/**
	 * Handle products listing request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_get_products( $request ) {
		if ( ! function_exists( 'wc_get_products' ) ) {
			return new WP_Error(
				'woocommerce_inactive',
				__( 'WooCommerce is not active', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$page     = max( 1, (int) $request->get_param( 'page' ) );
		$per_page = max( 1, min( 100, (int) $request->get_param( 'per_page' ) ) );
		$search   = $request->get_param( 'search' );
		$search   = is_string( $search ) ? sanitize_text_field( $search ) : '';

		$query_args = array(
			'post_type'      => 'product',
			'post_status'    => array( 'publish' ),
			'posts_per_page' => $per_page,
			'paged'          => $page,
			'orderby'        => 'menu_order',
			'order'          => 'ASC',
			'fields'         => 'ids',
		);

		if ( '' !== $search ) {
			$query_args['s'] = $search;
		}

		$products_query = new WP_Query( $query_args );
		$product_ids    = is_array( $products_query->posts ) ? $products_query->posts : array();

		$data = array();
		foreach ( $product_ids as $product_id ) {
			$product = wc_get_product( (int) $product_id );
			if ( ! $product instanceof WC_Product ) {
				continue;
			}

			$data[] = Foodord_Product_Availability::build_rest_payload( $product );
		}

		$total       = (int) $products_query->found_posts;
		$total_pages = (int) $products_query->max_num_pages;

		if ( 0 === $total ) {
			$total = count( $data );
		}

		return new WP_REST_Response(
			array(
				'ok'   => true,
				'data' => $data,
				'meta' => array(
					'page'        => $page,
					'per_page'    => $per_page,
					'total'       => $total,
					'total_pages' => $total_pages,
					'search'      => $search,
				),
			),
			200
		);
	}

	/**
	 * Handle POS products listing request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_get_pos_products( $request ) {
		if ( ! function_exists( 'wc_get_products' ) ) {
			return new WP_Error(
				'woocommerce_inactive',
				__( 'WooCommerce is not active', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$page     = max( 1, (int) $request->get_param( 'page' ) );
		$per_page = max( 1, min( 100, (int) $request->get_param( 'per_page' ) ) );
		$search   = $request->get_param( 'search' );
		$search   = is_string( $search ) ? sanitize_text_field( $search ) : '';
		$category = $request->get_param( 'category' );
		$category = is_string( $category ) ? sanitize_title( $category ) : '';

		$query_args = array(
			'limit'        => $per_page,
			'page'         => $page,
			'orderby'      => 'menu_order',
			'order'        => 'ASC',
			'status'       => array( 'publish' ),
			'stock_status' => array( 'instock', 'onbackorder' ),
			'return'       => 'ids',
			'paginate'     => true,
		);

		$product_types = function_exists( 'wc_get_product_types' ) ? array_keys( (array) wc_get_product_types() ) : array();
		if ( ! empty( $product_types ) ) {
			$product_types = array_values( array_diff( $product_types, array( 'variation' ) ) );
			if ( ! empty( $product_types ) ) {
				$query_args['type'] = $product_types;
			}
		}

		if ( '' !== $search ) {
			$query_args['s'] = $search;
		}

		if ( '' !== $category && 'all' !== $category ) {
			$query_args['category'] = array( $category );
		}

		$products_query = wc_get_products( $query_args );
		$product_ids    = array();
		$total          = 0;
		$total_pages    = 0;

		if ( is_object( $products_query ) && isset( $products_query->products ) ) {
			$product_ids = is_array( $products_query->products ) ? $products_query->products : array();
			$total       = isset( $products_query->total ) ? (int) $products_query->total : 0;
			$total_pages = isset( $products_query->max_num_pages ) ? (int) $products_query->max_num_pages : 0;
		} elseif ( is_array( $products_query ) ) {
			$product_ids = $products_query;
		}

		$toppings_proxy = null;
		if ( class_exists( 'Foodord_Product_Modal' ) ) {
			$toppings_proxy = new Foodord_Product_Modal();
		}

		$data = array();
		foreach ( $product_ids as $product_id ) {
			$product_id = absint( $product_id );
			$product    = wc_get_product( $product_id );
			if ( ! $product instanceof WC_Product ) {
				continue;
			}

			if ( 'outofstock' === $product->get_stock_status() ) {
				continue;
			}

			$payload                 = Foodord_Product_Availability::build_rest_payload( $product );
			$payload['topping_sets'] = array();
			if ( $toppings_proxy && method_exists( $toppings_proxy, 'get_product_topping_sets_for_api' ) ) {
				$payload['topping_sets'] = $toppings_proxy->get_product_topping_sets_for_api( $product_id );
			}

			$payload = $this->maybe_apply_pos_pricing_rule_payload( $product, $payload );

			if ( ! empty( $payload['variations'] ) && is_array( $payload['variations'] ) ) {
				foreach ( $payload['variations'] as $variation_index => $variation_payload ) {
					if ( ! is_array( $variation_payload ) || empty( $variation_payload['variation_id'] ) ) {
						continue;
					}

					$variation = wc_get_product( absint( $variation_payload['variation_id'] ) );
					if ( ! $variation instanceof WC_Product_Variation ) {
						continue;
					}

					$payload['variations'][ $variation_index ] = $this->maybe_apply_pos_pricing_rule_payload( $variation, $variation_payload );
				}
			}

			$data[] = $payload;
		}

		if ( 0 === $total ) {
			$total = count( $data );
		}

		$tax_enabled = function_exists( 'wc_tax_enabled' ) ? wc_tax_enabled() : false;
		$base        = function_exists( 'wc_get_base_location' ) ? wc_get_base_location() : array();
		$base        = is_array( $base ) ? $base : array();

		$tax_settings = array(
			'enabled'            => (bool) $tax_enabled,
			'prices_include_tax' => function_exists( 'wc_prices_include_tax' ) ? (bool) wc_prices_include_tax() : false,
			'tax_based_on'       => sanitize_text_field( (string) get_option( 'woocommerce_tax_based_on', '' ) ),
			'display_shop'       => sanitize_text_field( (string) get_option( 'woocommerce_tax_display_shop', '' ) ),
			'display_cart'       => sanitize_text_field( (string) get_option( 'woocommerce_tax_display_cart', '' ) ),
			'round_at_subtotal'  => 'yes' === get_option( 'woocommerce_tax_round_at_subtotal', 'no' ),
		);

		$base_location = array(
			'country' => isset( $base['country'] ) ? sanitize_text_field( (string) $base['country'] ) : '',
			'state'   => isset( $base['state'] ) ? sanitize_text_field( (string) $base['state'] ) : '',
		);

		$currency = function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : '';

		$ordered_categories = array();
		$category_terms     = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'hide_empty' => false,
				'orderby'    => 'menu_order',
				'order'      => 'ASC',
			)
		);

		if ( ! is_wp_error( $category_terms ) ) {
			foreach ( (array) $category_terms as $index => $term ) {
				if ( ! ( $term instanceof WP_Term ) ) {
					continue;
				}

				$ordered_categories[] = array(
					'id'         => (int) $term->term_id,
					'name'       => sanitize_text_field( (string) $term->name ),
					'slug'       => sanitize_text_field( (string) $term->slug ),
					'parent'     => (int) $term->parent,
					'menu_order' => (int) $index,
				);
			}
		}

		return new WP_REST_Response(
			array(
				'ok'   => true,
				'data' => $data,
				'meta' => array(
					'page'          => $page,
					'per_page'      => $per_page,
					'total'         => $total,
					'total_pages'   => $total_pages,
					'search'        => $search,
					'currency'      => $currency,
					'tax_settings'  => $tax_settings,
					'base_location' => $base_location,
					'categories'    => $ordered_categories,
				),
			),
			200
		);
	}

	/**
	 * Apply FoodOrd Pro product pricing rules to POS payloads when available.
	 *
	 * @param WC_Product $product Product instance.
	 * @param array      $payload REST payload.
	 * @return array<string,mixed>
	 */
	private function maybe_apply_pos_pricing_rule_payload( WC_Product $product, array $payload ): array {
		$runtime = $this->get_pos_pricing_runtime();
		if ( ! $runtime ) {
			return $payload;
		}

		$pricing = $runtime->get_product_pricing_for_channel( $product, 'pos' );
		if ( empty( $pricing ) || ! is_array( $pricing ) ) {
			return $payload;
		}

		$payload['price']         = $pricing['price'];
		$payload['current_price'] = $pricing['current_price'];
		$payload['regular_price'] = $pricing['regular_price'];
		$payload['sale_price']    = $pricing['sale_price'];
		$payload['price_html']    = $pricing['price_html'];
		$payload['foodord_pos_pricing_rule_applied'] = true;

		if ( empty( $payload['prices'] ) || ! is_array( $payload['prices'] ) ) {
			$payload['prices'] = array();
		}

		$payload['prices']['price']         = $pricing['price'];
		$payload['prices']['regular_price'] = $pricing['regular_price'];
		$payload['prices']['sale_price']    = $pricing['sale_price'];
		$payload['prices']['price_html']    = $pricing['price_html'];

		return $payload;
	}

	/**
	 * Resolve FoodOrd Pro pricing runtime for POS product payload generation.
	 *
	 * @return Foodord_Pro_Pricing_Runtime|null
	 */
	private function get_pos_pricing_runtime() {
		static $runtime = null;
		static $initialized = false;

		if ( $initialized ) {
			return $runtime;
		}

		$initialized = true;

		if ( ! class_exists( 'Foodord_Pro_Pricing_Runtime' ) ) {
			return null;
		}

		$runtime = new Foodord_Pro_Pricing_Runtime();
		return $runtime;
	}

	/**
	 * Handle product availability toggle request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_product_availability( $request ) {
		if ( ! function_exists( 'wc_get_product' ) ) {
			return new WP_Error(
				'woocommerce_inactive',
				__( 'WooCommerce is not active', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$product_id = (int) $request->get_param( 'id' );
		if ( $product_id <= 0 ) {
			return new WP_Error(
				'invalid_product',
				__( 'Invalid product identifier', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$product = wc_get_product( $product_id );
		if ( ! $product instanceof WC_Product ) {
			return new WP_Error(
				'product_not_found',
				__( 'Product not found', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		$available_raw = $request->get_param( 'available' );
		$available     = Foodord_Product_Availability::normalize_flag( $available_raw );
		$this->log_debug_event(
			'product_availability_request',
			array(
				'product_id' => $product_id,
				'raw_param'  => $available_raw,
				'normalized' => $available,
			)
		);
		$updated = Foodord_Product_Availability::set_available( $product_id, $available );

		$fresh_product = wc_get_product( $product_id );
		if ( ! $fresh_product instanceof WC_Product ) {
			$fresh_product = $product;
		}

		$raw_meta = get_post_meta( $product_id, '_foodord_product_available', true );
		$this->log_debug_event(
			'product_availability_stored',
			array(
				'product_id'  => $product_id,
				'updated'     => $updated,
				'raw_meta'    => $raw_meta,
				'post_status' => $product->get_status(),
			)
		);

		return new WP_REST_Response(
			array(
				'ok'   => true,
				'data' => array(
					'id'           => $product_id,
					'available'    => $updated,
					'availability' => Foodord_Product_Availability::get_status( $product_id ),
					'product'      => Foodord_Product_Availability::build_rest_payload( $fresh_product ),
				),
			),
			200
		);
	}


	/**
	 * Handle product pricing update request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_product_price( $request ) {
		if ( ! function_exists( 'wc_get_product' ) ) {
			return new WP_Error(
				'woocommerce_inactive',
				__( 'WooCommerce is not active', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$product_id = (int) $request->get_param( 'id' );
		if ( $product_id <= 0 ) {
			return new WP_Error(
				'invalid_product',
				__( 'Invalid product identifier', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$product = wc_get_product( $product_id );
		if ( ! $product instanceof WC_Product ) {
			return new WP_Error(
				'product_not_found',
				__( 'Product not found', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		$json_params = $request->get_json_params();
		if ( ! is_array( $json_params ) ) {
			$json_params = array();
		}

		$payload = array();
		$fields  = array( 'price', 'regular_price', 'sale_price', 'currency', 'variation_id' );

		foreach ( $fields as $field ) {
			if ( array_key_exists( $field, $json_params ) ) {
				$payload[ $field ] = $json_params[ $field ];
				continue;
			}

			if ( $request->has_param( $field ) ) {
				$payload[ $field ] = $request->get_param( $field );
			}
		}

		if ( isset( $payload['currency'] ) ) {
			$payload['currency'] = strtoupper( sanitize_text_field( (string) $payload['currency'] ) );
		}

		$this->log_debug_event(
			'product_price_request',
			array(
				'product_id'   => $product_id,
				'variation_id' => isset( $payload['variation_id'] ) ? (int) $payload['variation_id'] : 0,
				'has_price'    => array_key_exists( 'price', $payload ),
				'has_regular'  => array_key_exists( 'regular_price', $payload ),
				'has_sale'     => array_key_exists( 'sale_price', $payload ),
			)
		);

		$updated = Foodord_Product_Pricing::update_prices( $product_id, $payload );
		if ( is_wp_error( $updated ) ) {
			return $updated;
		}

		$this->log_debug_event(
			'product_price_updated',
			array(
				'product_id'   => $product_id,
				'variation_id' => isset( $payload['variation_id'] ) ? (int) $payload['variation_id'] : 0,
			)
		);

		return rest_ensure_response(
			array(
				'ok'   => true,
				'data' => $updated,
			)
		);
	}

	/**
	 * Handle product topping pricing update request.
	 *
	 * @param WP_REST_Request $request Request object.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_product_topping_prices( $request ) {
		if ( ! function_exists( 'wc_get_product' ) ) {
			return new WP_Error(
				'woocommerce_inactive',
				__( 'WooCommerce is not active', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$product_id = (int) $request->get_param( 'id' );
		if ( $product_id <= 0 ) {
			return new WP_Error(
				'invalid_product',
				__( 'Invalid product identifier', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$product = wc_get_product( $product_id );
		if ( ! $product instanceof WC_Product ) {
			return new WP_Error(
				'product_not_found',
				__( 'Product not found', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		if ( ! class_exists( 'Foodord_Product_Modal' ) ) {
			return new WP_Error(
				'topping_module_missing',
				__( 'Topping module is not available.', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$toppings_proxy = new Foodord_Product_Modal();
		if ( ! method_exists( $toppings_proxy, 'get_product_topping_sets_for_api' ) ) {
			return new WP_Error(
				'topping_module_missing',
				__( 'Topping module is not available.', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$current_sets = $toppings_proxy->get_product_topping_sets_for_api( $product_id );
		$current_sets = is_array( $current_sets ) ? $current_sets : array();
		if ( empty( $current_sets ) ) {
			return new WP_Error(
				'topping_sets_not_found',
				__( 'No topping sets are assigned to this product.', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		$json_params = $request->get_json_params();
		if ( ! is_array( $json_params ) ) {
			$json_params = array();
		}

		$raw_updates = array();
		if ( isset( $json_params['updates'] ) && is_array( $json_params['updates'] ) ) {
			$raw_updates = $json_params['updates'];
		} elseif ( $request->has_param( 'updates' ) ) {
			$param_updates = $request->get_param( 'updates' );
			$raw_updates   = is_array( $param_updates ) ? $param_updates : array();
		}

		$normalized_updates = $this->normalize_topping_price_updates( $raw_updates, $current_sets );
		if ( is_wp_error( $normalized_updates ) ) {
			return $normalized_updates;
		}

		$stored_sets = get_option( 'foodord_topping_sets', array() );
		$stored_sets = is_array( $stored_sets ) ? array_values( $stored_sets ) : array();
		if ( empty( $stored_sets ) ) {
			return new WP_Error(
				'topping_sets_not_found',
				__( 'No topping sets were found.', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		$set_index_by_id = array();
		foreach ( $stored_sets as $set_index => $set ) {
			if ( ! is_array( $set ) ) {
				continue;
			}

			$set_id = isset( $set['id'] ) ? sanitize_text_field( (string) $set['id'] ) : '';
			if ( '' === $set_id ) {
				continue;
			}

			$set_index_by_id[ $set_id ] = (int) $set_index;
		}

		$updated_count = 0;
		foreach ( $normalized_updates as $update ) {
			$set_id  = $update['set_id'];
			$item_id = $update['item_id'];
			$price   = $update['price'];

			if ( ! isset( $set_index_by_id[ $set_id ] ) ) {
				continue;
			}

			$set_index = $set_index_by_id[ $set_id ];
			if ( ! isset( $stored_sets[ $set_index ]['items'] ) || ! is_array( $stored_sets[ $set_index ]['items'] ) ) {
				continue;
			}

			$item_index = $this->resolve_topping_item_index_from_id( $item_id );
			if ( null === $item_index ) {
				continue;
			}

			if ( ! isset( $stored_sets[ $set_index ]['items'][ $item_index ] ) || ! is_array( $stored_sets[ $set_index ]['items'][ $item_index ] ) ) {
				continue;
			}

			$stored_sets[ $set_index ]['items'][ $item_index ]['price'] = $price;
			$updated_count++;
		}

		if ( 0 === $updated_count ) {
			return new WP_Error(
				'topping_items_not_found',
				__( 'No topping items could be updated.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		update_option( 'foodord_topping_sets', $stored_sets, false );

		$updated_sets = $toppings_proxy->get_product_topping_sets_for_api( $product_id );
		$updated_sets = is_array( $updated_sets ) ? $updated_sets : array();

		return rest_ensure_response(
			array(
				'ok'   => true,
				'data' => array(
					'product_id'    => $product_id,
					'updated_count' => $updated_count,
					'topping_sets'  => $updated_sets,
				),
			)
		);
	}

	/**
	 * Normalize topping price update payload.
	 *
	 * @param array $raw_updates Raw update payload.
	 * @param array $current_sets Current product topping sets.
	 * @return array|WP_Error
	 */
	private function normalize_topping_price_updates( array $raw_updates, array $current_sets ) {
		if ( empty( $raw_updates ) ) {
			return new WP_Error(
				'missing_updates',
				__( 'At least one topping price update is required.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$set_lookup = array();
		foreach ( $current_sets as $set ) {
			if ( ! is_array( $set ) ) {
				continue;
			}

			$set_id = isset( $set['id'] ) ? sanitize_text_field( (string) $set['id'] ) : '';
			if ( '' === $set_id ) {
				continue;
			}

			$items      = isset( $set['items'] ) && is_array( $set['items'] ) ? $set['items'] : array();
			$item_index = array();
			foreach ( $items as $item ) {
				if ( ! is_array( $item ) ) {
					continue;
				}

				$item_id    = isset( $item['id'] ) ? sanitize_text_field( (string) $item['id'] ) : '';
				$item_label = isset( $item['label'] ) ? sanitize_text_field( (string) $item['label'] ) : '';
				if ( '' === $item_id ) {
					continue;
				}

				$item_index[ $item_id ] = $item_label;
			}

			$set_lookup[ $set_id ] = $item_index;
		}

		$normalized = array();
		foreach ( $raw_updates as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			$set_id = '';
			if ( isset( $entry['set_id'] ) ) {
				$set_id = sanitize_text_field( (string) $entry['set_id'] );
			} elseif ( isset( $entry['setId'] ) ) {
				$set_id = sanitize_text_field( (string) $entry['setId'] );
			}

			if ( '' === $set_id || ! isset( $set_lookup[ $set_id ] ) ) {
				continue;
			}

			$item_id = '';
			if ( isset( $entry['item_id'] ) ) {
				$item_id = sanitize_text_field( (string) $entry['item_id'] );
			} elseif ( isset( $entry['itemId'] ) ) {
				$item_id = sanitize_text_field( (string) $entry['itemId'] );
			}

			if ( '' !== $item_id && ! isset( $set_lookup[ $set_id ][ $item_id ] ) ) {
				$item_id = '';
			}

			if ( '' === $item_id ) {
				$item_label = '';
				if ( isset( $entry['item_label'] ) ) {
					$item_label = sanitize_text_field( (string) $entry['item_label'] );
				} elseif ( isset( $entry['itemLabel'] ) ) {
					$item_label = sanitize_text_field( (string) $entry['itemLabel'] );
				}

				if ( '' !== $item_label ) {
					foreach ( $set_lookup[ $set_id ] as $candidate_id => $candidate_label ) {
						if ( $candidate_label === $item_label ) {
							$item_id = $candidate_id;
							break;
						}
					}
				}
			}

			if ( '' === $item_id ) {
				continue;
			}

			if ( ! array_key_exists( 'price', $entry ) ) {
				continue;
			}

			$price = $this->normalize_topping_price_value( $entry['price'] );
			if ( is_wp_error( $price ) ) {
				return $price;
			}

			$normalized[] = array(
				'set_id'  => $set_id,
				'item_id' => $item_id,
				'price'   => $price,
			);
		}

		if ( empty( $normalized ) ) {
			return new WP_Error(
				'invalid_updates',
				__( 'No valid topping price updates were provided.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		return $normalized;
	}

	/**
	 * Normalize a topping item price input.
	 *
	 * @param mixed $value Raw value.
	 * @return float|WP_Error
	 */
	private function normalize_topping_price_value( $value ) {
		if ( is_bool( $value ) || is_array( $value ) || is_object( $value ) ) {
			return new WP_Error(
				'invalid_price',
				__( 'Invalid topping price value.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$scalar = is_string( $value ) ? trim( $value ) : (string) $value;
		$scalar = preg_replace( '/[^0-9,\.\-]/', '', $scalar );
		$scalar = str_replace( ',', '.', $scalar );

		if ( '' === $scalar || ! is_numeric( $scalar ) ) {
			return new WP_Error(
				'invalid_price',
				__( 'Invalid topping price value.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$decimal = wc_format_decimal( $scalar, wc_get_price_decimals() );
		if ( '' === $decimal ) {
			$decimal = '0';
		}

		$amount = (float) $decimal;
		if ( $amount < 0 ) {
			return new WP_Error(
				'invalid_price_range',
				__( 'Topping price cannot be negative.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		if ( $amount > 1000000 ) {
			return new WP_Error(
				'invalid_price_range',
				__( 'Topping price exceeds the allowed range.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		return $amount;
	}

	/**
	 * Resolve topping item index from API item identifier.
	 *
	 * @param string $item_id Item identifier.
	 * @return int|null
	 */
	private function resolve_topping_item_index_from_id( string $item_id ): ?int {
		if ( preg_match( '/_item_(\d+)$/', $item_id, $matches ) ) {
			return absint( $matches[1] );
		}

		return null;
	}

	/**
	 * Return current delivery/pickup availability payload.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_get_store_modes( $request ) {
		unset( $request );
		$payload = $this->build_store_modes_payload();

		return rest_ensure_response(
			array(
				'success' => true,
				'modes'   => $payload['modes'],
				'meta'    => $payload['meta'],
			)
		);
	}

	/**
	 * Update delivery/pickup manual lock state.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_store_mode( $request ) {
		if ( ! class_exists( 'Foodord_Business_Hours' ) ) {
			return new WP_Error(
				'business_hours_missing',
				__( 'Business hours module is not available.', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$mode = (string) $request->get_param( 'mode' );
		if ( ! in_array( $mode, array( 'delivery', 'pickup' ), true ) ) {
			$mode = 'delivery';
		}

		$json_params = $request->get_json_params();
		if ( ! is_array( $json_params ) ) {
			$json_params = array();
		}

		$enabled = null;
		if ( $request->has_param( 'enabled' ) ) {
			$enabled = $this->normalize_boolean_flag( $request->get_param( 'enabled' ) );
		}
		if ( null === $enabled && array_key_exists( 'enabled', $json_params ) ) {
			$enabled = $this->normalize_boolean_flag( $json_params['enabled'] );
		}
		if ( null === $enabled && ( $request->has_param( 'locked' ) || array_key_exists( 'locked', $json_params ) ) ) {
			$locked_param = $request->has_param( 'locked' ) ? $request->get_param( 'locked' ) : $json_params['locked'];
			$locked       = $this->normalize_boolean_flag( $locked_param );
			if ( null !== $locked ) {
				$enabled = ! $locked;
			}
		}

		if ( null === $enabled ) {
			return new WP_Error(
				'invalid_enabled',
				__( 'The "enabled" flag is required.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$locked = ! $enabled;
		$reason = '';
		if ( $locked ) {
			if ( $request->has_param( 'reason' ) ) {
				$reason = sanitize_text_field( (string) $request->get_param( 'reason' ) );
			} elseif ( array_key_exists( 'reason', $json_params ) ) {
				$reason = sanitize_text_field( (string) $json_params['reason'] );
			}
		}

		Foodord_Business_Hours::set_mode_lock( $mode, $locked, $reason );

		$payload  = $this->build_store_modes_payload();
		$response = rest_ensure_response(
			array(
				'success'      => true,
				'modes'        => $payload['modes'],
				'meta'         => $payload['meta'],
				'updated_mode' => $payload['modes'][ $mode ] ?? null,
			)
		);

		$this->log_debug_event(
			'store_mode_update',
			array(
				'mode'      => $mode,
				'locked'    => $locked,
				'reason'    => $reason,
				'payload'   => $payload['modes'][ $mode ] ?? array(),
				'overrides' => get_option( 'foodord_mode_overrides', array() ),
			)
		);

		return $response;
	}

	/**
	 * Return delivery and pickup business hours.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response
	 */
	public function handle_get_business_hours( $request ) {
		unset( $request );

		return rest_ensure_response(
			array(
				'success' => true,
				'data'    => $this->build_business_hours_payload(),
			)
		);
	}

	/**
	 * Update delivery and pickup business hours.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_business_hours( $request ) {
		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = array();
		}

		$updated_modes = array();

		if ( isset( $params['delivery'] ) && is_array( $params['delivery'] ) ) {
			$delivery_payload = isset( $params['delivery']['hours'] ) && is_array( $params['delivery']['hours'] ) ? $params['delivery']['hours'] : $params['delivery'];
			update_option( 'foodord_opening_hours', $this->sanitize_business_hours_mode( $delivery_payload ) );
			$updated_modes[] = 'delivery';
		}

		if ( isset( $params['pickup'] ) && is_array( $params['pickup'] ) ) {
			$pickup_payload = isset( $params['pickup']['hours'] ) && is_array( $params['pickup']['hours'] ) ? $params['pickup']['hours'] : $params['pickup'];
			update_option( 'foodord_pickup_opening_hours', $this->sanitize_business_hours_mode( $pickup_payload ) );
			$updated_modes[] = 'pickup';
		}

		if ( empty( $updated_modes ) ) {
			return new WP_Error(
				'invalid_business_hours_payload',
				__( 'Please provide delivery and/or pickup business hours.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		if ( class_exists( 'Foodord_Business_Hours' ) && method_exists( 'Foodord_Business_Hours', 'reset_cache' ) ) {
			Foodord_Business_Hours::reset_cache();
		}

		$payload = $this->build_business_hours_payload();

		$this->log_debug_event(
			'business_hours_update',
			array(
				'updated_modes' => $updated_modes,
				'timezone'      => $payload['timezone'] ?? '',
			)
		);

		return rest_ensure_response(
			array(
				'success' => true,
				'data'    => $payload,
			)
		);
	}

	/**
	 * Build renderer-facing payload for business hours.
	 *
	 * @return array{delivery: array{hours: array<string, array<string, mixed>>}, pickup: array{hours: array<string, array<string, mixed>>}, timezone: string}
	 */
	private function build_business_hours_payload(): array {
		$default_hours  = $this->get_default_business_hours();
		$delivery_hours = get_option( 'foodord_opening_hours', $default_hours );
		$pickup_hours   = get_option( 'foodord_pickup_opening_hours', $default_hours );

		if ( ! is_array( $delivery_hours ) ) {
			$delivery_hours = $default_hours;
		}

		if ( ! is_array( $pickup_hours ) ) {
			$pickup_hours = $default_hours;
		}

		return array(
			'delivery' => array(
				'hours' => $this->sanitize_business_hours_mode( $delivery_hours ),
			),
			'pickup'   => array(
				'hours' => $this->sanitize_business_hours_mode( $pickup_hours ),
			),
			'timezone' => wp_timezone()->getName(),
		);
	}

	/**
	 * Return canonical default hours payload.
	 *
	 * @return array<string, array<string, mixed>>
	 */
	private function get_default_business_hours(): array {
		return array(
			'monday'    => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'tuesday'   => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'wednesday' => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'thursday'  => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'friday'    => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'saturday'  => array(
				'closed'        => false,
				'morning_start' => '10:00',
				'morning_end'   => '14:00',
				'evening_start' => '16:00',
				'evening_end'   => '22:00',
			),
			'sunday'    => array(
				'closed'        => true,
				'morning_start' => '',
				'morning_end'   => '',
				'evening_start' => '',
				'evening_end'   => '',
			),
		);
	}

	/**
	 * Sanitize weekly business hours for a mode.
	 *
	 * @param array $raw_hours Raw hours payload.
	 * @return array<string, array<string, mixed>>
	 */
	private function sanitize_business_hours_mode( array $raw_hours ): array {
		$days      = array( 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday' );
		$sanitized = array();

		foreach ( $days as $day ) {
			$entry             = isset( $raw_hours[ $day ] ) && is_array( $raw_hours[ $day ] ) ? $raw_hours[ $day ] : array();
			$sanitized[ $day ] = $this->sanitize_business_hours_day( $entry );
		}

		return $sanitized;
	}

	/**
	 * Sanitize one day entry in business hours payload.
	 *
	 * @param array $entry Raw day payload.
	 * @return array<string, mixed>
	 */
	private function sanitize_business_hours_day( array $entry ): array {
		$closed_value = $entry['closed'] ?? false;
		$closed_flag  = $this->normalize_boolean_flag( $closed_value );
		$closed       = ( null === $closed_flag ) ? ! empty( $closed_value ) : $closed_flag;

		$morning_start = $closed ? '' : $this->sanitize_business_hours_time( $entry['morning_start'] ?? ( $entry['morningStart'] ?? '' ) );
		$morning_end   = $closed ? '' : $this->sanitize_business_hours_time( $entry['morning_end'] ?? ( $entry['morningEnd'] ?? '' ) );
		$evening_start = $closed ? '' : $this->sanitize_business_hours_time( $entry['evening_start'] ?? ( $entry['eveningStart'] ?? '' ) );
		$evening_end   = $closed ? '' : $this->sanitize_business_hours_time( $entry['evening_end'] ?? ( $entry['eveningEnd'] ?? '' ) );

		return array(
			'closed'        => $closed,
			'morning_start' => $morning_start,
			'morning_end'   => $morning_end,
			'evening_start' => $evening_start,
			'evening_end'   => $evening_end,
		);
	}

	/**
	 * Sanitize time value (HH:MM).
	 *
	 * @param mixed $value Raw time value.
	 * @return string
	 */
	private function sanitize_business_hours_time( $value ): string {
		if ( ! is_scalar( $value ) ) {
			return '';
		}

		$time = trim( (string) $value );
		if ( '' === $time || ! preg_match( '/^\d{2}:\d{2}$/', $time ) ) {
			return '';
		}

		$parts  = explode( ':', $time );
		$hour   = isset( $parts[0] ) ? (int) $parts[0] : -1;
		$minute = isset( $parts[1] ) ? (int) $parts[1] : -1;

		if ( $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 ) {
			return '';
		}

		return sprintf( '%02d:%02d', $hour, $minute );
	}

	/**
	 * Return delivery pricing payload for the active delivery system.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_get_delivery_pricing( $request ) {
		unset( $request );

		if ( ! class_exists( 'Foodord_Location_Service' ) ) {
			return new WP_Error(
				'location_service_missing',
				__( 'Delivery settings are unavailable on this site.', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$payload = $this->build_delivery_pricing_payload();

		return rest_ensure_response(
			array(
				'success'   => true,
				'mode'      => $payload['mode'],
				'label'     => $payload['label'],
				'locations' => $payload['locations'],
				'zones'     => $payload['zones'],
				'meta'      => $payload['meta'],
			)
		);
	}

	/**
	 * Update delivery pricing data for the currently active system.
	 *
	 * @param WP_REST_Request $request Request instance.
	 * @return WP_REST_Response|WP_Error
	 */
	public function handle_update_delivery_pricing( $request ) {
		if ( ! class_exists( 'Foodord_Location_Service' ) ) {
			return new WP_Error(
				'location_service_missing',
				__( 'Delivery settings are unavailable on this site.', 'foodord' ),
				array( 'status' => 500 )
			);
		}

		$params = $request->get_json_params();
		if ( ! is_array( $params ) ) {
			$params = array();
		}

		$current_mode   = Foodord_Location_Service::get_delivery_mode();
		$requested_mode = '';
		if ( $request->has_param( 'mode' ) ) {
			$requested_mode = sanitize_text_field( (string) $request->get_param( 'mode' ) );
		} elseif ( isset( $params['mode'] ) ) {
			$requested_mode = sanitize_text_field( (string) $params['mode'] );
		}

		if ( '' !== $requested_mode && $requested_mode !== $current_mode ) {
			return new WP_Error(
				'delivery_mode_mismatch',
				__( 'Delivery mode changed on the website. Refresh the data and try again.', 'foodord' ),
				array( 'status' => 409 )
			);
		}

		if ( 'radius' === $current_mode ) {
			$result = $this->process_radius_delivery_updates( $params );
		} else {
			$result = $this->process_postal_delivery_updates( $params );
		}

		if ( is_wp_error( $result ) ) {
			return $result;
		}

		$payload = $this->build_delivery_pricing_payload();

		$this->log_debug_event(
			'delivery_pricing_update',
			array(
				'mode'   => $payload['mode'],
				'impact' => $result,
				'meta'   => $payload['meta'],
			)
		);

		return rest_ensure_response(
			array(
				'success'   => true,
				'mode'      => $payload['mode'],
				'label'     => $payload['label'],
				'locations' => $payload['locations'],
				'zones'     => $payload['zones'],
				'meta'      => $payload['meta'],
			)
		);
	}

	/**
	 * Assemble renderer-facing payload for store modes.
	 *
	 * @return array{modes: array<string, array>, meta: array<string, string>}
	 */
	private function build_store_modes_payload(): array {
		$modes = array();
		$meta  = array(
			'reference_time' => '',
			'timezone'       => '',
		);

		if ( class_exists( 'Foodord_Business_Hours' ) ) {
			$status   = Foodord_Business_Hours::get_status_payload();
			$messages = Foodord_Business_Hours::get_mode_messages();

			$meta['reference_time'] = isset( $status['ref_time'] ) ? (string) $status['ref_time'] : '';
			$meta['timezone']       = isset( $status['timezone'] ) ? (string) $status['timezone'] : '';

			try {
				$reference_now = $meta['reference_time'] ? new DateTimeImmutable( (string) $meta['reference_time'] ) : new DateTimeImmutable( 'now', wp_timezone() );
			} catch ( Exception $e ) {
				unset( $e );
				$reference_now = new DateTimeImmutable( 'now', wp_timezone() );
			}

			foreach ( array( 'delivery', 'pickup' ) as $mode_key ) {
				$state       = isset( $status['modes'][ $mode_key ] ) && is_array( $status['modes'][ $mode_key ] ) ? $status['modes'][ $mode_key ] : array();
				$lock        = Foodord_Business_Hours::get_mode_lock( $mode_key );
				$is_locked   = ! empty( $lock['locked'] );
				$is_open     = ! empty( $state['is_open'] );
				$available   = ( ! $is_locked ) && $is_open;
				$status_key  = $is_locked ? 'paused' : ( $is_open ? 'open' : 'closed' );
				$label       = ( 'pickup' === $mode_key ) ? __( 'Pickup', 'foodord' ) : __( 'Delivery', 'foodord' );
				$reason_text = $is_locked && ! empty( $lock['reason'] ) ? $lock['reason'] : '';
				$message     = isset( $messages[ $mode_key ] ) ? (string) $messages[ $mode_key ] : '';
				if ( ! $message && ! $available ) {
					$message = __( 'Currently unavailable. Please try again later.', 'foodord' );
				}

				$slots_payload  = Foodord_Business_Hours::get_mode_slots( $mode_key, $reference_now );
				$slots_options  = isset( $slots_payload['options'] ) && is_array( $slots_payload['options'] ) ? array_values( $slots_payload['options'] ) : array();
				$next_slot      = isset( $slots_payload['next_slot'] ) ? (string) $slots_payload['next_slot'] : '';
				$slot_opening   = isset( $slots_payload['opening'] ) ? (string) $slots_payload['opening'] : '';
				$slot_closing   = isset( $slots_payload['closing'] ) ? (string) $slots_payload['closing'] : '';
				$slot_breakdown = isset( $slots_payload['breakdown'] ) ? (int) $slots_payload['breakdown'] : 0;
				$slot_average   = isset( $slots_payload['average'] ) ? (int) $slots_payload['average'] : 0;
				$slots_enabled  = isset( $slots_payload['enabled'] ) ? (bool) $slots_payload['enabled'] : false;

				$state['slots']          = $slots_options;
				$state['next_slot']      = $next_slot;
				$state['slot_opening']   = $slot_opening;
				$state['slot_closing']   = $slot_closing;
				$state['slot_breakdown'] = $slot_breakdown;
				$state['slot_average']   = $slot_average;
				$state['slots_enabled']  = $slots_enabled;

				$modes[ $mode_key ] = array(
					'label'          => $label,
					'locked'         => $is_locked,
					'manual_locked'  => $is_locked,
					'available'      => $available,
					'is_open'        => $is_open,
					'status'         => $status_key,
					'reason'         => $reason_text,
					'message'        => $message,
					'updated_at'     => $lock['updated_at'] ?? '',
					'next_slot'      => $next_slot,
					'slots'          => $slots_options,
					'slot_opening'   => $slot_opening,
					'slot_closing'   => $slot_closing,
					'slot_breakdown' => $slot_breakdown,
					'slot_average'   => $slot_average,
					'slots_enabled'  => $slots_enabled,
					'context'        => $state,
				);
			}
		} else {
			foreach ( array( 'delivery', 'pickup' ) as $mode_key ) {
				$label              = ( 'pickup' === $mode_key ) ? __( 'Pickup', 'foodord' ) : __( 'Delivery', 'foodord' );
				$modes[ $mode_key ] = array(
					'label'          => $label,
					'locked'         => false,
					'manual_locked'  => false,
					'available'      => true,
					'is_open'        => true,
					'status'         => 'open',
					'reason'         => '',
					'message'        => '',
					'updated_at'     => '',
					'next_slot'      => '',
					'slots'          => array(),
					'slot_opening'   => '',
					'slot_closing'   => '',
					'slot_breakdown' => 0,
					'slot_average'   => 0,
					'slots_enabled'  => false,
					'context'        => array(),
				);
			}
		}

		return array(
			'modes' => $modes,
			'meta'  => $meta,
		);
	}

	/**
	 * Build renderer-facing payload for delivery pricing controls.
	 *
	 * @return array{mode: string, label: string, locations: array<int, array>, zones: array<int, array>, meta: array<string, mixed>}
	 */
	private function build_delivery_pricing_payload(): array {
		$mode          = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
		$rules_version = (string) get_option( 'foodord_delivery_rules_version', '' );

		$payload = array(
			'mode'      => $mode,
			'label'     => $this->get_delivery_mode_label( $mode ),
			'locations' => array(),
			'zones'     => array(),
			'meta'      => array(
				'mode'          => $mode,
				'rules_version' => $rules_version,
			),
		);

		if ( 'radius' === $mode && class_exists( 'Foodord_Location_Service' ) ) {
			$radius_settings = Foodord_Location_Service::get_radius_settings();
			$zones           = array();
			if ( isset( $radius_settings['zones'] ) && is_array( $radius_settings['zones'] ) ) {
				foreach ( array_values( $radius_settings['zones'] ) as $index => $zone ) {
					if ( ! is_array( $zone ) ) {
						continue;
					}
					$zones[] = array(
						'id'      => $this->build_radius_zone_key( $zone, (int) $index ),
						'label'   => isset( $zone['label'] ) ? (string) $zone['label'] : '',
						'radius'  => isset( $zone['radius'] ) ? (float) $zone['radius'] : 0.0,
						'fee'     => $this->format_amount_for_response( $zone['fee'] ?? 0 ),
						'minimum' => $this->format_amount_for_response( $zone['minimum'] ?? 0 ),
					);
				}
			}
			$payload['zones']                 = $zones;
			$payload['meta']['radius_center'] = array(
				'lat' => isset( $radius_settings['center_lat'] ) ? $radius_settings['center_lat'] : null,
				'lng' => isset( $radius_settings['center_lng'] ) ? $radius_settings['center_lng'] : null,
			);
			$payload['meta']['store_address'] = isset( $radius_settings['store_address'] ) ? (string) $radius_settings['store_address'] : '';
			return $payload;
		}

		$locations_option = get_option( 'foodord_delivery_locations', array() );
		if ( ! is_array( $locations_option ) ) {
			$locations_option = array();
		}

		$locations = array();
		foreach ( $locations_option as $location ) {
			if ( ! is_array( $location ) ) {
				continue;
			}
			$city        = isset( $location['city'] ) ? sanitize_text_field( (string) $location['city'] ) : '';
			$postal_code = isset( $location['postal_code'] ) ? sanitize_text_field( (string) $location['postal_code'] ) : '';
			if ( '' === $city ) {
				continue;
			}
			$locations[] = array(
				'id'          => $this->build_delivery_location_key( $city, $postal_code ),
				'city'        => $city,
				'postal_code' => $postal_code,
				'fee'         => $this->format_amount_for_response( $location['fee'] ?? 0 ),
				'minimum'     => $this->format_amount_for_response( $location['minimum'] ?? 0 ),
			);
		}

		$payload['locations'] = $locations;

		return $payload;
	}

	/**
	 * Build sanitized fee payload for API consumers.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array
	 */
	private function build_order_fee_payload( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return array();
		}

		$fees = array();
		foreach ( $order->get_fees() as $fee_item ) {
			$tax_entries = array();
			$tax_data    = $fee_item->get_taxes();
			if ( isset( $tax_data['total'] ) && is_array( $tax_data['total'] ) ) {
				foreach ( $tax_data['total'] as $tax_id => $tax_total ) {
					if ( '' === $tax_total || null === $tax_total ) {
						continue;
					}
					$tax_entries[] = array(
						'id'    => $tax_id,
						'total' => (float) $tax_total,
					);
				}
			}

			$fees[] = array(
				'id'        => $fee_item->get_id(),
				'name'      => $fee_item->get_name(),
				'title'     => $fee_item->get_name(),
				'amount'    => (float) $fee_item->get_total(),
				'value'     => (float) $fee_item->get_total(),
				'total'     => (float) $fee_item->get_total(),
				'total_tax' => (float) $fee_item->get_total_tax(),
				'tax_total' => (float) $fee_item->get_total_tax(),
				'taxes'     => $tax_entries,
			);
		}

		return $fees;
	}

	/**
	 * Calculate the customer-facing order subtotal including tax-aware toppings.
	 *
	 * WooCommerce stores tax-aware toppings as internal fee items so each topping
	 * can use its own tax class. Include only those internal fees here; delivery,
	 * tips, and other fees remain separate totals.
	 *
	 * @param WC_Order $order Order instance.
	 * @return float
	 */
	private function get_order_subtotal_including_tax( $order ): float {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return 0.0;
		}

		$subtotal = 0.0;
		foreach ( $order->get_items() as $item ) {
			$subtotal += (float) $item->get_subtotal() + (float) $item->get_subtotal_tax();
		}

		foreach ( $order->get_fees() as $fee_item ) {
			if ( '1' !== (string) $fee_item->get_meta( '_foodord_topping_tax_fee', true ) ) {
				continue;
			}

			$subtotal += (float) $fee_item->get_total() + (float) $fee_item->get_total_tax();
		}

		return $this->round_order_amount( $subtotal );
	}

	/**
	 * Round an API amount to the active WooCommerce currency precision.
	 *
	 * @param float $amount Raw amount.
	 * @return float
	 */
	private function round_order_amount( float $amount ): float {
		$decimals = function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2;

		return round( $amount, $decimals );
	}

	/**
	 * Build the WooCommerce VAT breakdown for desktop clients and receipts.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array
	 */
	private function build_order_tax_payload( $order ): array {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return array();
		}

		$tax_lines = array();
		foreach ( $order->get_items( 'tax' ) as $tax_item ) {
			if ( ! $tax_item instanceof WC_Order_Item_Tax ) {
				continue;
			}

			$item_tax     = $this->round_order_amount( (float) $tax_item->get_tax_total() );
			$shipping_tax = $this->round_order_amount( (float) $tax_item->get_shipping_tax_total() );
			$total        = $this->round_order_amount( (float) $tax_item->get_tax_total() + (float) $tax_item->get_shipping_tax_total() );
			if ( $total <= 0 ) {
				continue;
			}

			$tax_lines[] = array(
				'id'           => $tax_item->get_id(),
				'rate_id'      => $tax_item->get_rate_id(),
				'label'        => $tax_item->get_label(),
				'rate_percent' => (float) $tax_item->get_rate_percent(),
				'item_tax'     => $item_tax,
				'shipping_tax' => $shipping_tax,
				'total'        => $total,
			);
		}

		return $tax_lines;
	}

	/**
	 * Compile tip metadata for downstream clients.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array<string,mixed>
	 */
	private function build_order_tip_payload( $order ) {
		$default_currency = ( $order && is_a( $order, 'WC_Order' ) ) ? $order->get_currency() : '';

		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return array(
				'amount'   => null,
				'currency' => $default_currency,
				'type'     => '',
				'value'    => '',
				'source'   => 'none',
			);
		}

		$currency = $order->get_meta( '_foodord_tip_currency', true );
		if ( '' === $currency ) {
			$currency = $order->get_meta( 'foodord_tip_currency', true );
		}
		if ( '' === $currency ) {
			$currency = $default_currency;
		}

		$amount_sources = array(
			$order->get_meta( '_foodord_tip_amount', true ),
			$order->get_meta( 'foodord_tip_amount', true ),
			$order->get_meta( '_tip_amount', true ),
			$order->get_meta( 'tip_amount', true ),
			$order->get_meta( 'tip', true ),
			$order->get_meta( 'gratuity', true ),
			$order->get_meta( 'gratuity_amount', true ),
		);

		$amount = null;
		foreach ( $amount_sources as $candidate ) {
			$normalized = $this->normalize_money_value( $candidate );
			if ( null !== $normalized ) {
				$amount = $normalized;
				break;
			}
		}

		$source = 'meta';
		if ( null === $amount ) {
			$amount = $this->extract_tip_amount_from_fees( $order );
			$source = null === $amount ? 'none' : 'fees';
		}

		if ( null === $amount ) {
			return array(
				'amount'   => null,
				'currency' => $currency,
				'type'     => '',
				'value'    => '',
				'source'   => 'none',
			);
		}

		$type = $order->get_meta( '_foodord_tip_type', true );
		if ( '' === $type ) {
			$type = $order->get_meta( 'foodord_tip_type', true );
		}

		$value = $order->get_meta( '_foodord_tip_value', true );
		if ( '' === $value ) {
			$value = $order->get_meta( 'foodord_tip_value', true );
		}

		return array(
			'amount'   => (float) wc_format_decimal( $amount, wc_get_price_decimals() ),
			'currency' => (string) $currency,
			'type'     => (string) $type,
			'value'    => '' === $value ? '' : (string) $value,
			'source'   => $source,
		);
	}

	/**
	 * Attempt to hydrate tip totals from fee lines.
	 *
	 * @param WC_Order $order Order instance.
	 * @return float|null
	 */
	private function extract_tip_amount_from_fees( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return null;
		}

		$total   = 0.0;
		$matched = false;

		foreach ( $order->get_fees() as $fee_item ) {
			$label = strtolower( (string) $fee_item->get_name() );
			if ( '' === $label ) {
				continue;
			}

			if (
				str_contains( $label, 'tip' ) ||
				str_contains( $label, 'gratuity' ) ||
				str_contains( $label, 'bahşiş' )
			) {
				$total  += (float) $fee_item->get_total();
				$total  += (float) $fee_item->get_total_tax();
				$matched = true;
			}
		}

		if ( ! $matched || $total <= 0 ) {
			return null;
		}

		return (float) wc_format_decimal( $total, wc_get_price_decimals() );
	}

	/**
	 * Normalize arbitrary monetary input into a float value.
	 *
	 * @param mixed $value Raw value.
	 * @return float|null
	 */
	private function normalize_money_value( $value ) {
		if ( null === $value ) {
			return null;
		}

		if ( is_numeric( $value ) ) {
			return (float) $value;
		}

		if ( is_string( $value ) ) {
			$trimmed = trim( wp_strip_all_tags( $value ) );
			if ( '' === $trimmed ) {
				return null;
			}
			$filtered = preg_replace( '/[^0-9,\.\-]/', '', $trimmed );
			if ( '' === $filtered ) {
				return null;
			}
			if ( str_contains( $filtered, ',' ) && ! str_contains( $filtered, '.' ) ) {
				$filtered = str_replace( ',', '.', $filtered );
			} else {
				$filtered = str_replace( ',', '', $filtered );
			}
			if ( is_numeric( $filtered ) ) {
				return (float) $filtered;
			}
		}

		return null;
	}

	/**
	 * Resolve human readable title for delivery mode.
	 *
	 * @param string $mode Mode key.
	 * @return string
	 */
	private function get_delivery_mode_label( string $mode ): string {
		if ( 'radius' === $mode ) {
			return __( 'Google Maps Radius', 'foodord' );
		}
		return __( 'City & Postal Rules', 'foodord' );
	}

	/**
	 * Process updates for city/postal rules.
	 *
	 * @param array $params Request payload.
	 * @return array|WP_Error
	 */
	private function process_postal_delivery_updates( array $params ) {
		$entries = array();
		if ( isset( $params['locations'] ) && is_array( $params['locations'] ) ) {
			$entries = $params['locations'];
		} elseif ( isset( $params['rules']['locations'] ) && is_array( $params['rules']['locations'] ) ) {
			$entries = $params['rules']['locations'];
		}

		if ( empty( $entries ) ) {
			return new WP_Error(
				'delivery_locations_missing_payload',
				__( 'Provide at least one location to update.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$updates = array();
		foreach ( $entries as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}
			$city        = isset( $entry['city'] ) ? sanitize_text_field( (string) $entry['city'] ) : '';
			$postal_code = isset( $entry['postal_code'] ) ? sanitize_text_field( (string) $entry['postal_code'] ) : '';
			if ( '' === $city ) {
				continue;
			}
			$has_fee = array_key_exists( 'fee', $entry );
			$has_min = array_key_exists( 'minimum', $entry );
			if ( ! $has_fee && ! $has_min ) {
				continue;
			}

			$updates[ $this->build_delivery_location_key( $city, $postal_code ) ] = array(
				'fee'     => $has_fee ? $this->normalize_location_amount( $entry['fee'] ) : null,
				'minimum' => $has_min ? $this->normalize_location_amount( $entry['minimum'] ) : null,
			);
		}

		if ( empty( $updates ) ) {
			return new WP_Error(
				'delivery_locations_invalid_payload',
				__( 'No valid delivery locations were provided.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$stored_locations = get_option( 'foodord_delivery_locations', array() );
		if ( ! is_array( $stored_locations ) || empty( $stored_locations ) ) {
			return new WP_Error(
				'delivery_locations_not_configured',
				__( 'Delivery locations are not configured on the site.', 'foodord' ),
				array( 'status' => 412 )
			);
		}

		$match_count  = 0;
		$change_count = 0;
		foreach ( $stored_locations as &$location ) {
			if ( ! is_array( $location ) ) {
				continue;
			}
			$city        = isset( $location['city'] ) ? (string) $location['city'] : '';
			$postal_code = isset( $location['postal_code'] ) ? (string) $location['postal_code'] : '';
			if ( '' === $city ) {
				continue;
			}

			$key = $this->build_delivery_location_key( $city, $postal_code );
			if ( ! isset( $updates[ $key ] ) ) {
				continue;
			}

			$payload       = $updates[ $key ];
			$field_changed = false;
			++$match_count;

			$old_fee = isset( $location['fee'] ) ? (string) $location['fee'] : '0';
			if ( null !== $payload['fee'] && $payload['fee'] !== $old_fee ) {
				$location['fee'] = $payload['fee'];
				$field_changed   = true;
			}

			$old_min = isset( $location['minimum'] ) ? (string) $location['minimum'] : '0';
			if ( null !== $payload['minimum'] && $payload['minimum'] !== $old_min ) {
				$location['minimum'] = $payload['minimum'];
				$field_changed       = true;
			}

			if ( $field_changed ) {
				++$change_count;
			}
		}
		unset( $location );

		if ( 0 === $match_count ) {
			return new WP_Error(
				'delivery_locations_not_found',
				__( 'No matching delivery locations were found.', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		if ( $change_count > 0 ) {
			update_option( 'foodord_delivery_locations', $stored_locations );
			update_option( 'foodord_delivery_rules_version', time() );
		}

		return array(
			'mode'          => 'postal',
			'matches'       => $match_count,
			'updated'       => $change_count,
			'rules_version' => (string) get_option( 'foodord_delivery_rules_version', '' ),
		);
	}

	/**
	 * Process updates for radius zones.
	 *
	 * @param array $params Request payload.
	 * @return array|WP_Error
	 */
	private function process_radius_delivery_updates( array $params ) {
		$entries = array();
		if ( isset( $params['zones'] ) && is_array( $params['zones'] ) ) {
			$entries = $params['zones'];
		} elseif ( isset( $params['rules']['zones'] ) && is_array( $params['rules']['zones'] ) ) {
			$entries = $params['rules']['zones'];
		}

		if ( empty( $entries ) ) {
			return new WP_Error(
				'delivery_zones_missing_payload',
				__( 'Provide at least one radius zone to update.', 'foodord' ),
				array( 'status' => 400 )
			);
		}

		$stored_zones = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_radius_zones() : array();
		if ( empty( $stored_zones ) ) {
			return new WP_Error(
				'delivery_zones_not_configured',
				__( 'Radius delivery is not configured on the site.', 'foodord' ),
				array( 'status' => 412 )
			);
		}

		$zones_by_id = array();
		foreach ( $stored_zones as $index => $zone ) {
			$zones_by_id[ $this->build_radius_zone_key( $zone, (int) $index ) ] = (int) $index;
		}

		$match_count  = 0;
		$change_count = 0;
		foreach ( $entries as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}
			$has_fee = array_key_exists( 'fee', $entry );
			$has_min = array_key_exists( 'minimum', $entry );
			if ( ! $has_fee && ! $has_min ) {
				continue;
			}

			$entry_id     = isset( $entry['id'] ) ? sanitize_text_field( (string) $entry['id'] ) : '';
			$entry_label  = isset( $entry['label'] ) ? sanitize_text_field( (string) $entry['label'] ) : '';
			$entry_radius = isset( $entry['radius'] ) ? $this->parse_float_value( $entry['radius'] ) : null;
			$target_index = null;
			if ( '' !== $entry_id && isset( $zones_by_id[ $entry_id ] ) ) {
				$target_index = $zones_by_id[ $entry_id ];
			} elseif ( null !== $entry_radius ) {
				$target_index = $this->match_radius_zone_by_distance( $stored_zones, $entry_radius, $entry_label );
			}

			if ( null === $target_index ) {
				continue;
			}

			++$match_count;
			$field_changed = false;

			if ( $has_fee ) {
				$new_fee = $this->normalize_radius_amount( $entry['fee'] );
				if ( $new_fee !== $stored_zones[ $target_index ]['fee'] ) {
					$stored_zones[ $target_index ]['fee'] = $new_fee;
					$field_changed                        = true;
				}
			}

			if ( $has_min ) {
				$new_min = $this->normalize_radius_amount( $entry['minimum'] );
				if ( $new_min !== $stored_zones[ $target_index ]['minimum'] ) {
					$stored_zones[ $target_index ]['minimum'] = $new_min;
					$field_changed                            = true;
				}
			}

			if ( $field_changed ) {
				++$change_count;
			}
		}

		if ( 0 === $match_count ) {
			return new WP_Error(
				'delivery_zones_not_found',
				__( 'No matching radius zones were found.', 'foodord' ),
				array( 'status' => 404 )
			);
		}

		if ( $change_count > 0 ) {
			$ordered_zones = array_values( $stored_zones );
			update_option( 'foodord_delivery_radius_zones', $ordered_zones );

			$primary_zone    = $ordered_zones ? $ordered_zones[0] : array();
			$primary_radius  = isset( $primary_zone['radius'] ) ? $this->normalize_location_amount( $primary_zone['radius'] ) : '0';
			$primary_minimum = isset( $primary_zone['minimum'] ) ? $this->normalize_location_amount( $primary_zone['minimum'] ) : '0';
			$primary_fee     = isset( $primary_zone['fee'] ) ? $this->normalize_location_amount( $primary_zone['fee'] ) : '0';

			update_option( 'foodord_delivery_radius_distance', $primary_radius );
			update_option( 'foodord_delivery_radius_minimum', $primary_minimum );
			update_option( 'foodord_delivery_radius_fee', $primary_fee );
			update_option( 'foodord_delivery_rules_version', time() );
		}

		return array(
			'mode'          => 'radius',
			'matches'       => $match_count,
			'updated'       => $change_count,
			'rules_version' => (string) get_option( 'foodord_delivery_rules_version', '' ),
		);
	}

	/**
	 * Normalize location amount for storage (string with 2 decimals).
	 *
	 * @param mixed $value Raw value.
	 * @return string
	 */
	private function normalize_location_amount( $value ): string {
		$number = $this->format_amount_for_response( $value );
		if ( function_exists( 'wc_format_decimal' ) ) {
			return (string) wc_format_decimal( $number, 2 );
		}
		return number_format( $number, 2, '.', '' );
	}

	/**
	 * Normalize radius amount for storage (float).
	 *
	 * @param mixed $value Raw value.
	 * @return float
	 */
	private function normalize_radius_amount( $value ): float {
		return $this->format_amount_for_response( $value );
	}

	/**
	 * Format numeric value for response payloads.
	 *
	 * @param mixed $value Raw value.
	 * @return float
	 */
	private function format_amount_for_response( $value ): float {
		$number = $this->normalize_numeric_input( $value );
		return (float) number_format( $number, 2, '.', '' );
	}

	/**
	 * Normalize incoming numeric input.
	 *
	 * @param mixed $value Raw value.
	 * @return float
	 */
	private function normalize_numeric_input( $value ): float {
		if ( is_numeric( $value ) ) {
			$number = (float) $value;
		} elseif ( is_string( $value ) ) {
			$sanitized = trim( $value );
			$sanitized = str_replace( ',', '.', $sanitized );
			$sanitized = preg_replace( '/[^0-9\.\-]/', '', $sanitized );
			if ( '' === $sanitized ) {
				return 0.0;
			}
			$number = (float) $sanitized;
		} else {
			$number = 0.0;
		}

		if ( $number < 0 ) {
			$number = 0.0;
		}

		return $number;
	}

	/**
	 * Build deterministic identifier for location rows.
	 *
	 * @param string $city City name.
	 * @param string $postal Postal code.
	 * @return string
	 */
	private function build_delivery_location_key( string $city, string $postal ): string {
		$city_key   = strtolower( sanitize_text_field( $city ) );
		$postal_key = strtolower( sanitize_text_field( $postal ) );
		return md5( $city_key . '|' . $postal_key );
	}

	/**
	 * Build deterministic identifier for radius zones.
	 *
	 * @param array $zone  Zone payload.
	 * @param int   $index Zone index.
	 * @return string
	 */
	private function build_radius_zone_key( array $zone, int $index ): string {
		$radius = isset( $zone['radius'] ) ? (float) $zone['radius'] : 0.0;
		$label  = isset( $zone['label'] ) ? strtolower( (string) $zone['label'] ) : '';
		return md5( $index . '|' . $radius . '|' . $label );
	}

	/**
	 * Attempt to match a radius zone by distance/label.
	 *
	 * @param array  $zones  Configured zones.
	 * @param float  $radius Target radius.
	 * @param string $label  Target label.
	 * @return int|null
	 */
	private function match_radius_zone_by_distance( array $zones, float $radius, string $label ) {
		$normalized_label = strtolower( trim( $label ) );
		foreach ( $zones as $index => $zone ) {
			$zone_radius = isset( $zone['radius'] ) ? (float) $zone['radius'] : 0.0;
			$zone_label  = isset( $zone['label'] ) ? strtolower( (string) $zone['label'] ) : '';
			if ( abs( $zone_radius - $radius ) <= 0.001 ) {
				if ( '' === $normalized_label || $normalized_label === $zone_label ) {
					return (int) $index;
				}
			}
		}

		return null;
	}

	/**
	 * Parse numeric radius input that may arrive as string.
	 *
	 * @param mixed $value Raw value.
	 * @return float|null
	 */
	private function parse_float_value( $value ) {
		if ( null === $value ) {
			return null;
		}
		if ( is_numeric( $value ) ) {
			return (float) $value;
		}
		if ( is_string( $value ) ) {
			$normalized = $this->normalize_numeric_input( $value );
			return $normalized;
		}
		return null;
	}

	/**
	 * Normalize various truthy/falsey values into booleans.
	 *
	 * @param mixed $value Raw value.
	 * @return bool|null True/false for recognized values, null otherwise.
	 */
	private function normalize_boolean_flag( $value ): ?bool {
		if ( is_bool( $value ) ) {
			return $value;
		}

		if ( is_numeric( $value ) ) {
			if ( 1 === (int) $value ) {
				return true;
			}
			if ( 0 === (int) $value ) {
				return false;
			}
		}

		if ( is_string( $value ) ) {
			$normalized = strtolower( trim( $value ) );
			if ( in_array( $normalized, array( '1', 'true', 'yes', 'on', 'enabled' ), true ) ) {
				return true;
			}
			if ( in_array( $normalized, array( '0', 'false', 'no', 'off', 'disabled' ), true ) ) {
				return false;
			}
		}

		return null;
	}

	/**
	 * Retrieve sanitized note value for a given order item.
	 *
	 * @param WC_Order_Item_Product $item Order item instance.
	 * @return string
	 */
	private function get_order_item_note( $item ): string {
		if ( ! $item || ! method_exists( $item, 'get_meta' ) ) {
			return '';
		}

		$raw_note = '';
		foreach ( array( '_foodord_note', 'foodord_note', 'note' ) as $meta_key ) {
			$value            = $item->get_meta( $meta_key, true );
			$normalized_value = $this->normalize_note_candidate( $value );
			if ( '' !== $normalized_value ) {
				$raw_note = $normalized_value;
				break;
			}
		}

		if ( '' === $raw_note ) {
			foreach ( $item->get_meta_data() as $meta ) {
				if ( ! $meta || ! method_exists( $meta, 'get_data' ) ) {
					continue;
				}

				$data = $meta->get_data();
				if ( empty( $data['key'] ) ) {
					continue;
				}

				$key = strtolower( trim( (string) $data['key'] ) );
				if ( '' === $key || false === strpos( $key, 'note' ) ) {
					continue;
				}

				$value            = isset( $data['value'] ) ? $data['value'] : '';
				$normalized_value = $this->normalize_note_candidate( $value );
				if ( '' !== $normalized_value ) {
					$raw_note = $normalized_value;
					break;
				}
			}
		}

		if ( '' === $raw_note ) {
			return '';
		}

		$note = wp_strip_all_tags( $raw_note );
		$note = (string) preg_replace( '/\s+/u', ' ', $note );
		$note = trim( $note );

		if ( '' === $note ) {
			return '';
		}

		if ( function_exists( 'mb_substr' ) ) {
			return mb_substr( $note, 0, 160 );
		}

		return substr( $note, 0, 160 );
	}

	/**
	 * Normalize a note candidate value into a scalar string.
	 *
	 * @param mixed $value Candidate value.
	 * @return string
	 */
	private function normalize_note_candidate( $value ): string {
		if ( is_string( $value ) || is_numeric( $value ) ) {
			return trim( (string) $value );
		}

		if ( is_object( $value ) ) {
			$value = (array) $value;
		}

		if ( is_array( $value ) ) {
			$priority_keys = array( 'value', 'Value', 'display_value', 'displayValue', 'note', 'Note' );
			foreach ( $priority_keys as $priority_key ) {
				if ( array_key_exists( $priority_key, $value ) ) {
					$normalized = $this->normalize_note_candidate( $value[ $priority_key ] );
					if ( '' !== $normalized ) {
						return $normalized;
					}
				}
			}

			$parts = array();
			foreach ( $value as $part ) {
				$normalized = $this->normalize_note_candidate( $part );
				if ( '' !== $normalized ) {
					$parts[] = $normalized;
				}
			}

			if ( ! empty( $parts ) ) {
				return trim( implode( ' ', $parts ) );
			}
		}

		return '';
	}

	/**
	 * Build sanitized order item meta entries for API consumers.
	 * Filters out internal FoodOrd meta and (when structured toppings are present)
	 * hides the human-readable "Toppings" meta row to prevent duplicate printing.
	 *
	 * @param WC_Order_Item_Product $item                   Order item instance.
	 * @param int                   $limit                  Maximum number of meta entries to return. Use 0 for unlimited.
	 * @param bool                  $has_structured_toppings Whether structured toppings payload exists.
	 * @return array
	 */
	private function build_order_item_meta_entries( $item, $limit, $has_structured_toppings ) {
		if ( ! $item || ! method_exists( $item, 'get_formatted_meta_data' ) ) {
			return array();
		}

		$limit                   = absint( $limit );
		$has_structured_toppings = (bool) $has_structured_toppings;
		$toppings_label_key      = sanitize_key( __( 'Toppings', 'foodord' ) );

		$meta_entries = array();
		foreach ( $item->get_formatted_meta_data() as $meta ) {
			if ( ! is_object( $meta ) ) {
				continue;
			}

			$raw_key      = isset( $meta->key ) ? (string) $meta->key : '';
			$raw_key_norm = sanitize_key( $raw_key );
			if ( '' !== $raw_key_norm && 0 === strpos( $raw_key_norm, '_foodord_' ) ) {
				continue;
			}

			$display_key      = isset( $meta->display_key ) ? (string) $meta->display_key : '';
			$display_key_out  = wp_strip_all_tags( $display_key );
			$display_key_norm = sanitize_key( $display_key_out );

			if ( $has_structured_toppings ) {
				if ( '' !== $toppings_label_key && ( $display_key_norm === $toppings_label_key || $raw_key_norm === $toppings_label_key ) ) {
					continue;
				}
				if ( 'toppings' === $display_key_norm || 'toppings' === $raw_key_norm ) {
					continue;
				}
			}

			if ( '' === $display_key_out ) {
				continue;
			}

			$display_value = isset( $meta->display_value ) ? (string) $meta->display_value : '';
			$value_out     = wp_strip_all_tags( wp_kses_post( $display_value ) );

			$meta_entries[] = array(
				'key'   => $display_key_out,
				'value' => $value_out,
			);

			if ( $limit > 0 && count( $meta_entries ) >= $limit ) {
				break;
			}
		}

		return $meta_entries;
	}

	/**
	 * Build toppings payload for a given order item.
	 *
	 * @param WC_Order_Item_Product $item Order item instance.
	 * @return array
	 */
	private function build_order_item_toppings( $item ) {
		if ( ! $item || ! method_exists( $item, 'get_meta' ) ) {
			return array();
		}

		$raw_toppings = $item->get_meta( '_foodord_toppings', true );
		if ( empty( $raw_toppings ) || ! is_array( $raw_toppings ) ) {
			return array();
		}

		$toppings = array();

		$label_map      = array();
		$hide_map       = array();
		$group_map      = array();
		$available_sets = get_option( 'foodord_topping_sets', array() );
		if ( is_array( $available_sets ) ) {
			foreach ( $available_sets as $available_set ) {
				if ( ! is_array( $available_set ) || empty( $available_set['id'] ) ) {
					continue;
				}
				$set_id_key               = sanitize_text_field( (string) $available_set['id'] );
				$label_map[ $set_id_key ] = isset( $available_set['label'] ) ? sanitize_text_field( (string) $available_set['label'] ) : '';
				$hide_map[ $set_id_key ]  = ! empty( $available_set['hide_price'] );
				$group_map[ $set_id_key ] = isset( $available_set['group_type'] ) ? sanitize_key( (string) $available_set['group_type'] ) : '';
			}
		}

		foreach ( $raw_toppings as $set ) {
			if ( empty( $set['items'] ) || ! is_array( $set['items'] ) ) {
				continue;
			}

			$set_id    = isset( $set['set_id'] ) ? (string) $set['set_id'] : '';
			$raw_name  = isset( $set['set_name'] ) && '' !== $set['set_name'] ? $set['set_name'] : __( 'Toppings', 'foodord' );
			$raw_label = isset( $set['set_label'] ) && '' !== $set['set_label'] ? $set['set_label'] : '';
			if ( '' === $raw_label && '' !== $set_id && isset( $label_map[ $set_id ] ) && '' !== $label_map[ $set_id ] ) {
				$raw_label = $label_map[ $set_id ];
			}
			$display_label = '' !== $raw_label ? wp_strip_all_tags( $raw_label ) : wp_strip_all_tags( $raw_name );
			$group_type    = '';
			if ( isset( $set['group_type'] ) && '' !== $set['group_type'] ) {
				$group_type = sanitize_key( (string) $set['group_type'] );
			} elseif ( '' !== $set_id && isset( $group_map[ $set_id ] ) && '' !== $group_map[ $set_id ] ) {
				$group_type = $group_map[ $set_id ];
			}
			$hide_prices = false;
			if ( isset( $set['hide_price'] ) ) {
				$hide_prices = (bool) $set['hide_price'];
			} elseif ( '' !== $set_id && isset( $hide_map[ $set_id ] ) ) {
				$hide_prices = (bool) $hide_map[ $set_id ];
			}
			$items = array();

			foreach ( $set['items'] as $selection ) {
				$label = isset( $selection['label'] ) ? wp_strip_all_tags( $selection['label'] ) : '';
				if ( '' === $label ) {
					continue;
				}

				$price   = isset( $selection['price'] ) ? (float) $selection['price'] : 0.0;
				$items[] = array(
					'label' => $label,
					'price' => $price,
				);
			}

			if ( ! empty( $items ) ) {
				$toppings[] = array(
					'set_id'     => $set_id,
					'set_name'   => $display_label,
					'set_label'  => $display_label,
					'group_type' => $group_type,
					'hide_price' => $hide_prices ? 1 : 0,
					'items'      => $items,
				);
			}
		}

		return $toppings;
	}

	/**
	 * Retrieve the first non-empty meta value from provided keys.
	 *
	 * @param WC_Order $order Order instance.
	 * @param array    $keys  Meta keys to inspect.
	 * @return string
	 */
	private function get_order_meta_value( $order, $keys ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) || empty( $keys ) ) {
			return '';
		}

		foreach ( $keys as $key ) {
			if ( ! $key ) {
				continue;
			}
			$value = (string) $order->get_meta( $key, true );
			$value = trim( $value );
			if ( '' === $value ) {
				continue;
			}
			return wp_strip_all_tags( $value );
		}

		return '';
	}

	/**
	 * Build the customer-requested schedule using the order mode and site formats.
	 *
	 * @param WC_Order $order         Order instance.
	 * @param string   $mode          Fulfillment mode.
	 * @param string   $delivery_time Stored delivery time.
	 * @param string   $pickup_time   Stored pickup time.
	 * @return array
	 */
	private function build_order_schedule_payload( $order, $mode, $delivery_time, $pickup_time ) {
		$is_pickup = 'pickup' === $mode;
		$date      = $this->get_order_meta_value(
			$order,
			$is_pickup
				? array( 'foodord_pickup_date', '_foodord_pickup_date' )
				: array( 'foodord_delivery_date', '_foodord_delivery_date' )
		);
		$time      = trim( (string) ( $is_pickup ? $pickup_time : $delivery_time ) );
		$payload   = array(
			'date'         => $date,
			'time'         => $time,
			'datetime'     => '',
			'time_display' => $this->format_order_time( $time ),
			'display'      => $this->format_order_time( $time ),
		);

		if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ) {
			return $payload;
		}

		$timezone = wp_timezone();
		$date_obj = DateTimeImmutable::createFromFormat( '!Y-m-d', $date, $timezone );
		if ( false === $date_obj || $date_obj->format( 'Y-m-d' ) !== $date ) {
			$payload['date'] = '';
			return $payload;
		}

		$date_display = wp_date(
			get_option( 'date_format', 'Y-m-d' ),
			$date_obj->getTimestamp(),
			$timezone
		);

		$payload['display'] = $date_display;

		if ( ! preg_match( '/^(?:[01]\d|2[0-3]):[0-5]\d$/', $time ) ) {
			return $payload;
		}

		$schedule = DateTimeImmutable::createFromFormat( '!Y-m-d H:i', $date . ' ' . $time, $timezone );
		if ( false === $schedule || $schedule->format( 'Y-m-d H:i' ) !== $date . ' ' . $time ) {
			return $payload;
		}

		$payload['datetime'] = $schedule->format( 'c' );
		$payload['display']  = wp_date(
			get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' ),
			$schedule->getTimestamp(),
			$timezone
		);

		return $payload;
	}

	/**
	 * Format an order date with the WordPress site settings.
	 *
	 * @param WC_DateTime|null $date Order date.
	 * @return string|null
	 */
	private function format_order_datetime( $date ) {
		if ( ! $date || ! is_a( $date, 'WC_DateTime' ) ) {
			return null;
		}

		$format = get_option( 'date_format', 'Y-m-d' ) . ' ' . get_option( 'time_format', 'H:i' );

		return wp_date( $format, $date->getTimestamp(), wp_timezone() );
	}

	/**
	 * Format a stored order time with the WordPress site settings.
	 *
	 * Non-clock values such as ASAP remain unchanged.
	 *
	 * @param string $time Stored order time.
	 * @return string
	 */
	private function format_order_time( $time ) {
		$time = trim( (string) $time );
		if ( ! preg_match( '/^(?:[01]\d|2[0-3]):[0-5]\d$/', $time ) ) {
			return $time;
		}

		$date = DateTimeImmutable::createFromFormat( '!H:i', $time, wp_timezone() );
		if ( false === $date ) {
			return $time;
		}

		return wp_date( get_option( 'time_format', 'H:i' ), $date->getTimestamp(), wp_timezone() );
	}

	/**
	 * Mark the provided order as seen when confirm action is triggered.
	 *
	 * @param WC_Order $order Order instance.
	 * @return void
	 */
	private function mark_order_seen( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return;
		}

		$now_gmt   = time();
		$timestamp = gmdate( 'c', $now_gmt );
		$order->update_meta_data( 'foodord_seen', '1' );
		$order->update_meta_data( 'foodord_seen_at', $timestamp );
		$order->save();
	}

	/**
	 * Build seen payload details for API consumers.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array{
	 *     seen: bool,
	 *     seen_at: string,
	 *     is_new: bool,
	 * }
	 */
	private function build_order_seen_payload( $order ) {
		$seen    = false;
		$seen_at = null;
		$is_new  = true;

		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return array(
				'seen'    => false,
				'seen_at' => null,
				'is_new'  => true,
			);
		}

		$flag = strtolower( trim( (string) $order->get_meta( 'foodord_seen', true ) ) );
		if ( in_array( $flag, array( '1', 'true', 'yes', 'y', 'seen' ), true ) ) {
			$seen = true;
		}

		$raw_seen_at = (string) $order->get_meta( 'foodord_seen_at', true );
		if ( '' !== trim( $raw_seen_at ) ) {
			$timestamp = strtotime( $raw_seen_at );
			if ( false !== $timestamp ) {
				$seen_at = gmdate( 'c', $timestamp );
			} else {
				$seen_at = wp_strip_all_tags( $raw_seen_at );
			}
			$seen = true;
		}

		if ( $seen ) {
			$is_new = false;
		}

		return array(
			'seen'    => $seen,
			'seen_at' => $seen_at,
			'is_new'  => $is_new,
		);
	}

	/**
	 * Build whitelisted order meta payload for API consumers.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array
	 */
	public static function build_order_confirmation_payload( $order ) {
		$payload = array(
			'confirmed'            => false,
			'type'                 => '',
			'confirmed_at'         => '',
			'confirmed_at_local'   => '',
			'confirmed_minutes'    => null,
			'confirmed_slot'       => '',
			'confirmed_slot_local' => '',
			'confirmed_display'    => '',
			'source'               => '',
		);

		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return $payload;
		}

		$confirmation_type = (string) $order->get_meta( 'foodord_confirmation_type', true );
		if ( '' !== $confirmation_type ) {
			$payload['type'] = $confirmation_type;
		}

		$confirmed_at       = (string) $order->get_meta( 'foodord_confirmed_at', true );
		$confirmed_at_local = (string) $order->get_meta( 'foodord_confirmed_at_local', true );
		$confirmed_minutes  = $order->get_meta( 'foodord_confirmed_minutes', true );
		$confirmed_slot     = (string) $order->get_meta( 'foodord_confirmed_slot', true );
		$confirmed_source   = (string) $order->get_meta( 'foodord_confirmed_source', true );

		if ( '' !== trim( $confirmed_at ) ) {
			$payload['confirmed']    = true;
			$payload['confirmed_at'] = $confirmed_at;
		}

		if ( '' !== trim( $confirmed_at_local ) ) {
			$payload['confirmed_at_local'] = $confirmed_at_local;
		}

		if ( is_numeric( $confirmed_minutes ) ) {
			$minutes = absint( $confirmed_minutes );
			if ( $minutes > 0 ) {
				$payload['confirmed_minutes'] = $minutes;
				$payload['confirmed']         = true;
			}
		}

		if ( '' !== trim( $confirmed_slot ) ) {
			$payload['confirmed_slot'] = $confirmed_slot;
			$payload['confirmed']      = true;
		}

		if ( '' !== trim( $confirmed_source ) ) {
			$payload['source'] = $confirmed_source;
		}

		$time_format = get_option( 'time_format', 'H:i' );
		$date_format = get_option( 'date_format', 'Y-m-d' );
		$display     = '';

		if ( '' !== $payload['confirmed_slot'] ) {
			$slot_ts = strtotime( $payload['confirmed_slot'] );
			if ( false !== $slot_ts ) {
				$local_time                      = function_exists( 'wp_date' ) ? wp_date( $time_format, $slot_ts ) : date_i18n( $time_format, $slot_ts );
				$payload['confirmed_slot_local'] = $local_time;
				$display                         = $local_time;
			}
		}

		if ( '' === $display && null !== $payload['confirmed_minutes'] ) {
			$minutes = (int) $payload['confirmed_minutes'];
			if ( $minutes > 0 ) {
				/* translators: %d: number of minutes */
				$display = esc_html( sprintf( _n( '%d minute', '%d minutes', $minutes, 'foodord' ), $minutes ) );
			}
		}

		if ( '' === $display && '' !== $payload['confirmed_at_local'] ) {
			$at_ts = strtotime( $payload['confirmed_at_local'] );
			if ( false !== $at_ts ) {
				$display = function_exists( 'wp_date' ) ? wp_date( $time_format, $at_ts ) : date_i18n( $time_format, $at_ts );
			}
		}

		if ( '' === $display && '' !== $payload['confirmed_at'] ) {
			$at_ts = strtotime( $payload['confirmed_at'] );
			if ( false !== $at_ts ) {
				$display = function_exists( 'wp_date' ) ? wp_date( $time_format, $at_ts ) : date_i18n( $time_format, $at_ts );
			}
		}

		$payload['confirmed_display'] = $display;

		return $payload;
	}

	/**
	 * Normalize custom progress stage values into canonical keys.
	 *
	 * @param mixed $value Raw stage value.
	 * @return string
	 */
	private static function normalize_progress_stage( $value ): string {
		if ( is_string( $value ) ) {
			$normalized = strtolower( trim( $value ) );
		} elseif ( is_scalar( $value ) ) {
			$normalized = strtolower( trim( (string) $value ) );
		} else {
			$normalized = '';
		}

		$normalized = str_replace( array( '-', ' ' ), '_', $normalized );

		$map = array(
			''                 => '',
			'new'              => 'new',
			'pending'          => 'new',
			'received'         => 'new',
			'confirmed'        => 'new',
			'approval'         => 'new',
			'in_kitchen'       => 'kitchen',
			'kitchen'          => 'kitchen',
			'preparing'        => 'kitchen',
			'prep'             => 'kitchen',
			'ontheway'         => 'ontheway',
			'on_the_way'       => 'ontheway',
			'on_theway'        => 'ontheway',
			'out_for_delivery' => 'ontheway',
			'courier'          => 'ontheway',
			'courier_on_way'   => 'ontheway',
			'ready'            => 'ready',
			'ready_for_pickup' => 'ready',
			'readyforpickup'   => 'ready',
			'completed'        => 'ready',
			'done'             => 'ready',
		);

		return $map[ $normalized ] ?? '';
	}

	/**
	 * Build custom order progress payload for API consumers.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array
	 */
	public static function build_order_stage_payload( $order ) {
		$payload = array(
			'stage'            => '',
			'updated_at'       => '',
			'updated_at_local' => '',
			'kitchen_at'       => '',
			'kitchen_at_local' => '',
		);

		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return $payload;
		}

		$stage            = self::normalize_progress_stage( $order->get_meta( 'foodord_order_stage', true ) );
		$payload['stage'] = $stage;

		$updated_at = (string) $order->get_meta( 'foodord_order_stage_updated_at', true );
		if ( '' !== trim( $updated_at ) ) {
			$payload['updated_at'] = $updated_at;
			$updated_local         = (string) $order->get_meta( 'foodord_order_stage_updated_local', true );
			if ( '' !== trim( $updated_local ) ) {
				$payload['updated_at_local'] = $updated_local;
			} else {
				$timestamp = strtotime( $updated_at );
				if ( false !== $timestamp ) {
					$payload['updated_at_local'] = function_exists( 'wp_date' ) ? wp_date( 'c', $timestamp ) : date_i18n( 'c', $timestamp );
				}
			}
		}

		$kitchen_at = (string) $order->get_meta( 'foodord_stage_kitchen_at', true );
		if ( '' !== trim( $kitchen_at ) ) {
			$payload['kitchen_at'] = $kitchen_at;
			$kitchen_local         = (string) $order->get_meta( 'foodord_stage_kitchen_at_local', true );
			if ( '' !== trim( $kitchen_local ) ) {
				$payload['kitchen_at_local'] = $kitchen_local;
			} else {
				$kitchen_ts = strtotime( $kitchen_at );
				if ( false !== $kitchen_ts ) {
					$payload['kitchen_at_local'] = function_exists( 'wp_date' ) ? wp_date( 'c', $kitchen_ts ) : date_i18n( 'c', $kitchen_ts );
				}
			}
		}

		return $payload;
	}

	/**
	 * Keep one completion action synchronized with the current order stage.
	 *
	 * @param int    $order_id Order identifier.
	 * @param string $stage Current FoodOrd stage.
	 * @param int    $stage_timestamp Stage update timestamp.
	 * @return void
	 */
	private static function sync_ontheway_auto_completion( int $order_id, string $stage, int $stage_timestamp ): void {
		if ( ! function_exists( 'as_unschedule_all_actions' ) || ! function_exists( 'as_schedule_single_action' ) ) {
			return;
		}

		$arguments = array( $order_id );
		as_unschedule_all_actions( self::ONTHEWAY_AUTO_COMPLETE_HOOK, $arguments, self::ONTHEWAY_AUTO_COMPLETE_GROUP );

		if ( 'ontheway' !== $stage ) {
			return;
		}

		as_schedule_single_action(
			$stage_timestamp + self::ONTHEWAY_AUTO_COMPLETE_DELAY,
			self::ONTHEWAY_AUTO_COMPLETE_HOOK,
			$arguments,
			self::ONTHEWAY_AUTO_COMPLETE_GROUP,
			true
		);
	}

	/**
	 * Complete an on-the-way order after its persisted delay.
	 *
	 * @param int $order_id Order identifier.
	 * @return void
	 */
	public function handle_ontheway_auto_completion( $order_id ): void {
		if ( ! function_exists( 'wc_get_order' ) ) {
			return;
		}

		$order = wc_get_order( absint( $order_id ) );
		if ( ! $order || ! $order->has_status( 'processing' ) ) {
			return;
		}

		if ( function_exists( 'foodord_is_excluded_order' ) && foodord_is_excluded_order( $order ) ) {
			return;
		}

		$stage = self::normalize_progress_stage( $order->get_meta( 'foodord_order_stage', true ) );
		if ( 'ontheway' !== $stage ) {
			return;
		}

		$updated_at        = (string) $order->get_meta( 'foodord_order_stage_updated_at', true );
		$updated_timestamp = strtotime( $updated_at );
		if ( '' === trim( $updated_at ) || false === $updated_timestamp ) {
			return;
		}

		$complete_at = $updated_timestamp + self::ONTHEWAY_AUTO_COMPLETE_DELAY;
		if ( time() < $complete_at ) {
			self::sync_ontheway_auto_completion( (int) $order->get_id(), 'ontheway', $updated_timestamp );
			return;
		}

		$order->update_status(
			'completed',
			__( 'FoodOrd auto-completed after On the Way timeout.', 'foodord' ),
			false
		);
	}

	/**
	 * Check if an order is ready to be sent to the app.
	 *
	 * @param WC_Order $order Order instance.
	 * @return bool
	 */
	private function is_order_ready_for_app( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return false;
		}

		return $order->has_status( array( 'processing', 'completed' ) );
	}

	/**
	 * Build order meta payload.
	 *
	 * @param WC_Order $order Order instance.
	 * @return array
	 */
	private function build_order_meta_payload( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return array();
		}

		$whitelist = array(
			'foodord_order_mode',
			'_foodord_order_mode',
			'foodord_delivery_time',
			'_foodord_delivery_time',
			'foodord_delivery_date',
			'_foodord_delivery_date',
			'foodord_pickup_time',
			'_foodord_pickup_time',
			'foodord_pickup_date',
			'_foodord_pickup_date',
			'delivery_fee',
			'_delivery_fee',
			'foodord_tip_amount',
			'_foodord_tip_amount',
			'foodord_tip_currency',
			'_foodord_tip_currency',
			'foodord_tip_type',
			'_foodord_tip_type',
			'foodord_tip_value',
			'_foodord_tip_value',
			'foodord_tip_selection',
			'_foodord_tip_selection',
			'foodord_seen',
			'foodord_seen_at',
			'foodord_confirmed_at',
			'foodord_confirmed_at_local',
			'foodord_confirmed_minutes',
			'foodord_confirmed_slot',
			'foodord_confirmed_source',
			'foodord_confirmation_type',
			'foodord_app_receipt_status',
			'_foodord_pro_stampcard_reward_consumed',
			'_foodord_pro_stampcard_reward_stamps',
			'_foodord_pro_stampcard_reward_coupon',
		);

		$payload = array();
		foreach ( $whitelist as $key ) {
			$value = $order->get_meta( $key, true );
			if ( '' === $value || null === $value ) {
				continue;
			}
			$payload[] = array(
				'key'   => $key,
				'value' => is_scalar( $value ) ? wp_strip_all_tags( (string) $value ) : wp_json_encode( $value ),
			);
		}

		return $payload;
	}

	/**
	 * Write a structured entry to WooCommerce logger for troubleshooting.
	 *
	 * @param string $type    Event type identifier.
	 * @param array  $context Additional context.
	 * @return void
	 */
	private function log_debug_event( string $type, array $context = array() ): void {
		if ( function_exists( 'wc_get_logger' ) ) {
			$logger = wc_get_logger();
			$logger->info(
				wp_json_encode(
					array(
						'type'      => $type,
						'timestamp' => gmdate( 'c' ),
						'context'   => $context,
					)
				),
				array( 'source' => 'foodord-rest' )
			);
			return;
		}
	}


	/**
	 * Calculate delivery/shipping total composed of base, taxes and delivery fees.
	 *
	 * @param WC_Order $order Order instance.
	 * @return float
	 */
	private function calculate_delivery_total( $order ) {
		if ( ! $order || ! is_a( $order, 'WC_Order' ) ) {
			return 0.0;
		}

		$base_total = (float) $order->get_shipping_total();
		$tax_total  = (float) $order->get_shipping_tax();

		$lines_total = 0.0;
		foreach ( $order->get_items( 'shipping' ) as $shipping_item ) {
			$lines_total += (float) $shipping_item->get_total();
			$lines_total += (float) $shipping_item->get_total_tax();
		}

		$fee_total = 0.0;
		foreach ( $order->get_fees() as $fee_item ) {
			$label = strtolower( (string) $fee_item->get_name() );
			if ( '' === $label ) {
				continue;
			}

			if (
				str_contains( $label, 'delivery' ) ||
				str_contains( $label, 'shipping' ) ||
				str_contains( $label, 'courier' ) ||
				str_contains( $label, 'door' )
			) {
				$fee_total += (float) $fee_item->get_total();
				$fee_total += (float) $fee_item->get_total_tax();
			}
		}

		if ( $fee_total <= 0 ) {
			$meta_fee = (float) $order->get_meta( 'delivery_fee', true );
			if ( $meta_fee <= 0 ) {
				$meta_fee = (float) $order->get_meta( '_delivery_fee', true );
			}
			if ( $meta_fee > 0 ) {
				$fee_total += $meta_fee;
			}
		}

		$primary_total = max( $base_total + $tax_total, $lines_total );
		$calculated    = $primary_total + $fee_total;

		return (float) wc_format_decimal( $calculated, wc_get_price_decimals() );
	}
}
