<?php
/**
 * Frontend Location Modal and validation/fee integration.
 *
 * @package FoodOrd
 */

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

/**
 * Class Foodord_Location_Modal
 *
 * Handles:
 * - Rendering a modal to collect city + postal code
 * - AJAX validation against Delivery Settings (location based)
 * - Persisting selection in WooCommerce session
 * - Auto-filling WooCommerce checkout fields
 * - Enforcing address validation during checkout
 * - Applying location-based delivery fee when enabled
 */
class Foodord_Location_Modal {
	/**
	 * Determine if location modal should be enabled on this request.
	 * Only enable on pages that use [foodord_products] shortcode or
	 * the Foodord products/card blocks.
	 *
	 * @return bool
	 */
	private function should_enable_modal(): bool {
		if ( is_admin() ) {
			return false;
		}
		$has_flag = ! empty( $GLOBALS['foodord_has_card_block'] )
			|| ! empty( $GLOBALS['foodord_has_card_shortcode'] )
			|| ! empty( $GLOBALS['foodord_has_products_block'] )
			|| ! empty( $GLOBALS['foodord_has_products_shortcode'] );
		$post     = get_post();
		$has_sc   = false;
		$has_blk  = false;
		if ( $post instanceof WP_Post ) {
			if ( function_exists( 'has_shortcode' ) ) {
				$has_sc = has_shortcode( (string) $post->post_content, 'foodord_products' );
			}
			if ( function_exists( 'has_block' ) ) {
				$has_blk = has_block( 'foodord/products', $post ) || has_block( 'foodord/card', $post );
			}
		}
		return ( $has_flag || $has_sc || $has_blk );
	}

	/**
	 * Initialize hooks.
	 *
	 * @return void
	 */
	public function init(): void {
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
		// Ensure checkout autofill is available on checkout even if modal assets are not needed on that page.
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_checkout_autofill' ), 11 );
		add_action( 'wp_footer', array( $this, 'render_modal' ) );

		// AJAX handlers registered via register_ajax() to ensure availability in admin-ajax context as well.

		// Prefill checkout fields with session values.
		add_filter( 'woocommerce_checkout_get_value', array( $this, 'prefill_checkout_values' ), 10, 2 );

		// Validate during checkout.
		add_action( 'woocommerce_after_checkout_validation', array( $this, 'validate_checkout_address' ), 10, 2 );

		// Fee calculation handled centrally in foodord.php. No hook here.

		// Show notice on checkout if invalid.
		add_action( 'woocommerce_before_checkout_form', array( $this, 'maybe_notice_invalid_address' ), 5 );
	}

	/**
	 * Enqueue frontend assets.
	 *
	 * @return void
	 */
	public function enqueue_scripts(): void {
		if ( is_admin() ) {
			return;
		}
		// Only load on pages with FoodOrd products/card components.
		if ( ! $this->should_enable_modal() ) {
			return;
		}

		$delivery_mode   = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
		$radius_settings = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_radius_settings() : array(
			'distance'      => 0.0,
			'minimum'       => 0.0,
			'fee'           => 0.0,
			'maps_api_key'  => '',
			'center_lat'    => null,
			'center_lng'    => null,
			'store_address' => '',
			'zones'         => array(),
		);
		$session         = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array(
			'city'    => '',
			'postal'  => '',
			'address' => '',
			'lat'     => null,
			'lng'     => null,
			'allowed' => false,
			'fee'     => 0,
		);
		$allowed         = ! empty( $session['allowed'] );

		$city_out = $allowed ? (string) ( $session['city'] ?? '' ) : '';

		$postal_out = $allowed ? (string) ( $session['postal'] ?? '' ) : '';

		// Determine current order mode from session/cookie (delivery|pickup).
		$mode = $this->get_current_order_mode();

		wp_enqueue_script(
			'foodord-location-modal',
			FOODORD_URL . 'public/js/foodord-location-modal.js',
			array( 'jquery' ),
			FOODORD_VERSION,
			true
		);

		$minimum_amount = isset( $session['minimum'] ) ? (float) $session['minimum'] : 0.0;

		wp_localize_script(
			'foodord-location-modal',
			'foodord_location_vars',
			array(
				// Use relative admin-ajax to avoid http/https scheme mismatch in local/dev setups.
				'ajax_url'      => admin_url( 'admin-ajax.php', 'relative' ),
				'nonce'         => wp_create_nonce( 'foodord_location_nonce' ),
				'is_checkout'   => function_exists( 'is_checkout' ) ? is_checkout() : false,
				'mode'          => $mode,
				'delivery_mode' => $delivery_mode,
				'current'       => array(
					'city'        => $city_out,
					'postal'      => $postal_out,
					'allowed'     => $allowed,
					'fee'         => isset( $session['fee'] ) ? (float) $session['fee'] : 0.0,
					'address'     => $allowed ? (string) ( $session['address'] ?? '' ) : '',
					'lat'         => $allowed && isset( $session['lat'] ) ? (float) $session['lat'] : null,
					'lng'         => $allowed && isset( $session['lng'] ) ? (float) $session['lng'] : null,
					'minimum'     => $minimum_amount,
					'zone_radius' => $allowed && isset( $session['zone_radius'] ) ? (float) $session['zone_radius'] : 0.0,
				),
				'labels'        => array(
					'title'                 => __( 'Delivery Area', 'foodord' ),
					'city'                  => __( 'City', 'foodord' ),
					'postal'                => __( 'Postal Code', 'foodord' ),
					'save'                  => __( 'Save', 'foodord' ),
					'cancel'                => __( 'Cancel', 'foodord' ),
					'select_area'           => __( 'Please select your city to check delivery availability.', 'foodord' ),
					'success'               => __( 'Your address is within our delivery area.', 'foodord' ),
					'fail'                  => __( 'We do not deliver to this area.', 'foodord' ),
					'radius_select_address' => __( 'Please choose an address from the suggestions.', 'foodord' ),
					'radius_coords_error'   => __( 'Unable to determine coordinates for this address.', 'foodord' ),
				),
				'address_bar'   => array(
					'placeholder'        => __( 'Select address', 'foodord' ),
					'change'             => __( 'Change', 'foodord' ),
					'change_aria'        => __( 'Change delivery address', 'foodord' ),
					'delivery_fee'       => __( 'Delivery Fee', 'foodord' ),
					'free'               => __( 'Free', 'foodord' ),
					'currency_symbol'    => function_exists( 'get_woocommerce_currency_symbol' ) ? html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ) : '',
					'currency_pos'       => get_option( 'woocommerce_currency_pos', 'left' ),
					'price_decimals'     => function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2,
					'decimal_separator'  => function_exists( 'wc_get_price_decimal_separator' ) ? wc_get_price_decimal_separator() : '.',
					'thousand_separator' => function_exists( 'wc_get_price_thousand_separator' ) ? wc_get_price_thousand_separator() : ',',
				),
				// Do not auto-open on load to avoid aggressive prompting; open only on interactions.
				'open_on_load'  => false,
				'radius'        => array(
					'distance_km'   => isset( $radius_settings['distance'] ) ? (float) $radius_settings['distance'] : 0.0,
					'fee'           => isset( $radius_settings['fee'] ) ? (float) $radius_settings['fee'] : 0.0,
					'minimum'       => isset( $radius_settings['minimum'] ) ? (float) $radius_settings['minimum'] : 0.0,
					'center'        => array(
						'lat' => isset( $radius_settings['center_lat'] ) ? $radius_settings['center_lat'] : null,
						'lng' => isset( $radius_settings['center_lng'] ) ? $radius_settings['center_lng'] : null,
					),
					'store_address' => isset( $radius_settings['store_address'] ) ? $radius_settings['store_address'] : '',
					'zones'         => isset( $radius_settings['zones'] ) && is_array( $radius_settings['zones'] ) ? array_values( $radius_settings['zones'] ) : array(),
				),
			)
		);

		// Enqueue address bar (separate JS).
		wp_enqueue_script(
			'foodord-address-bar',
			FOODORD_URL . 'public/js/wrc-address-bar.js',
			array( 'jquery' ),
			FOODORD_VERSION,
			true
		);

		// If we are on checkout, enqueue a tiny helper to auto-fill fields on selection.
		if ( function_exists( 'is_checkout' ) && is_checkout() ) {
			wp_enqueue_script(
				'foodord-checkout-autofill',
				FOODORD_URL . 'public/js/foodord-checkout-autofill.js',
				array( 'jquery' ),
				FOODORD_VERSION,
				true
			);
		}

		if ( 'radius' === $delivery_mode ) {
			$api_key = isset( $radius_settings['maps_api_key'] ) ? (string) $radius_settings['maps_api_key'] : '';
			if ( '' !== $api_key ) {
				$language   = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_google_maps_language( 'frontend' ) : '';
				$script_url = 'https://maps.googleapis.com/maps/api/js?key=' . rawurlencode( $api_key ) . '&libraries=places&loading=async';
				if ( '' !== $language ) {
					$script_url .= '&language=' . rawurlencode( $language );
				}
				wp_enqueue_script(
					'foodord-google-maps',
					$script_url,
					array(),
					FOODORD_VERSION,
					true
				);
				if ( function_exists( 'wp_script_add_data' ) ) {
					wp_script_add_data( 'foodord-google-maps', 'strategy', 'async' );
				}
			}
		}
	}

	/**
	 * Enqueue only the checkout autofill helper on the checkout page.
	 * This is independent from modal gating so that checkout is always handled.
	 */
	public function enqueue_checkout_autofill(): void {
		if ( is_admin() ) {
			return;
		}
		if ( function_exists( 'is_checkout' ) && is_checkout() ) {
			// Prepare runtime vars from session for checkout context.
			$delivery_mode   = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
			$radius_settings = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_radius_settings() : array(
				'distance'      => 0.0,
				'minimum'       => 0.0,
				'fee'           => 0.0,
				'maps_api_key'  => '',
				'center_lat'    => null,
				'center_lng'    => null,
				'store_address' => '',
				'zones'         => array(),
			);
			$session         = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array(
				'city'    => '',
				'postal'  => '',
				'address' => '',
				'lat'     => null,
				'lng'     => null,
				'allowed' => false,
				'fee'     => 0,
			);
			$allowed         = ! empty( $session['allowed'] );
			$city_out        = $allowed ? (string) ( $session['city'] ?? '' ) : '';
			$postal_out      = $allowed ? (string) ( $session['postal'] ?? '' ) : '';
			$mode            = $this->get_current_order_mode();
			$minimum_amount  = isset( $session['minimum'] ) ? (float) $session['minimum'] : 0.0;

			wp_enqueue_script(
				'foodord-checkout-autofill',
				FOODORD_URL . 'public/js/foodord-checkout-autofill.js',
				array( 'jquery' ),
				FOODORD_VERSION,
				true
			);

			// Localize foodord_location_vars for checkout too.
			wp_localize_script(
				'foodord-checkout-autofill',
				'foodord_location_vars',
				array(
					'ajax_url'      => admin_url( 'admin-ajax.php', 'relative' ),
					'nonce'         => wp_create_nonce( 'foodord_location_nonce' ),
					'is_checkout'   => true,
					'mode'          => $mode,
					'delivery_mode' => $delivery_mode,
					'current'       => array(
						'city'        => $city_out,
						'postal'      => $postal_out,
						'address'     => $allowed ? (string) ( $session['address'] ?? '' ) : '',
						'lat'         => $allowed && isset( $session['lat'] ) ? (float) $session['lat'] : null,
						'lng'         => $allowed && isset( $session['lng'] ) ? (float) $session['lng'] : null,
						'allowed'     => $allowed,
						'fee'         => isset( $session['fee'] ) ? (float) $session['fee'] : 0.0,
						'minimum'     => $minimum_amount,
						'zone_radius' => $allowed && isset( $session['zone_radius'] ) ? (float) $session['zone_radius'] : 0.0,
					),
					'labels'        => array(
						'title'                 => __( 'Delivery Area', 'foodord' ),
						'city'                  => __( 'City', 'foodord' ),
						'postal'                => __( 'Postal Code', 'foodord' ),
						'save'                  => __( 'Save', 'foodord' ),
						'cancel'                => __( 'Cancel', 'foodord' ),
						'select_area'           => __( 'Please select your city to check delivery availability.', 'foodord' ),
						'success'               => __( 'Your address is within our delivery area.', 'foodord' ),
						'fail'                  => __( 'We do not deliver to this area.', 'foodord' ),
						'radius_select_address' => __( 'Please choose an address from the suggestions.', 'foodord' ),
						'radius_coords_error'   => __( 'Unable to determine coordinates for this address.', 'foodord' ),
					),
					'address_bar'   => array(
						'placeholder'        => __( 'Select address', 'foodord' ),
						'change'             => __( 'Change', 'foodord' ),
						'change_aria'        => __( 'Change delivery address', 'foodord' ),
						'delivery_fee'       => __( 'Delivery Fee', 'foodord' ),
						'free'               => __( 'Free', 'foodord' ),
						'currency_symbol'    => function_exists( 'get_woocommerce_currency_symbol' ) ? html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ) : '',
						'currency_pos'       => get_option( 'woocommerce_currency_pos', 'left' ),
						'price_decimals'     => function_exists( 'wc_get_price_decimals' ) ? wc_get_price_decimals() : 2,
						'decimal_separator'  => function_exists( 'wc_get_price_decimal_separator' ) ? wc_get_price_decimal_separator() : '.',
						'thousand_separator' => function_exists( 'wc_get_price_thousand_separator' ) ? wc_get_price_thousand_separator() : ',',
					),
					'radius'        => array(
						'distance_km'   => isset( $radius_settings['distance'] ) ? (float) $radius_settings['distance'] : 0.0,
						'fee'           => isset( $radius_settings['fee'] ) ? (float) $radius_settings['fee'] : 0.0,
						'minimum'       => isset( $radius_settings['minimum'] ) ? (float) $radius_settings['minimum'] : 0.0,
						'center'        => array(
							'lat' => isset( $radius_settings['center_lat'] ) ? $radius_settings['center_lat'] : null,
							'lng' => isset( $radius_settings['center_lng'] ) ? $radius_settings['center_lng'] : null,
						),
						'store_address' => isset( $radius_settings['store_address'] ) ? $radius_settings['store_address'] : '',
						'zones'         => isset( $radius_settings['zones'] ) && is_array( $radius_settings['zones'] ) ? array_values( $radius_settings['zones'] ) : array(),
					),
				)
			);
		}
	}

	/**
	 * Render the location modal markup in footer.
	 *
	 * @return void
	 */
	public function render_modal(): void {
		if ( is_admin() ) {
			return;
		}
		// Only render on pages with FoodOrd products/card components.
		if ( ! $this->should_enable_modal() ) {
			return;
		}

		$delivery_mode   = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
		$radius_settings = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_radius_settings() : array(
			'distance'      => 0.0,
			'minimum'       => 0.0,
			'fee'           => 0.0,
			'maps_api_key'  => '',
			'center_lat'    => null,
			'center_lng'    => null,
			'store_address' => '',
			'zones'         => array(),
		);
		$session         = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array(
			'city'    => '',
			'postal'  => '',
			'address' => '',
			'lat'     => null,
			'lng'     => null,
			'allowed' => false,
			'fee'     => 0,
		);
		$allowed         = ! empty( $session['allowed'] );

		$city_out             = $allowed ? (string) ( $session['city'] ?? '' ) : '';
		$postal_out           = $allowed ? (string) ( $session['postal'] ?? '' ) : '';
		$address_out          = $allowed ? (string) ( $session['address'] ?? '' ) : '';
		$lat_out              = $allowed && isset( $session['lat'] ) ? (float) $session['lat'] : '';
		$lng_out              = $allowed && isset( $session['lng'] ) ? (float) $session['lng'] : '';
		$radius_store_address = isset( $radius_settings['store_address'] ) ? (string) $radius_settings['store_address'] : '';
		$raw_locations        = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_locations() : array();
		$location_options     = array();
		if ( is_array( $raw_locations ) ) {
			foreach ( $raw_locations as $index => $location_entry ) {
				if ( ! is_array( $location_entry ) ) {
					continue;
				}
				$city_label = isset( $location_entry['city'] ) ? (string) $location_entry['city'] : '';
				if ( '' === $city_label ) {
					continue;
				}
				$postal_label = '';
				if ( isset( $location_entry['postal_code'] ) ) {
					$postal_label = (string) $location_entry['postal_code'];
				} elseif ( isset( $location_entry['postalCode'] ) ) {
					$postal_label = (string) $location_entry['postalCode'];
				}
				$option_id          = 'loc-' . $index . '-' . substr( md5( $city_label . '|' . $postal_label . '|' . $index ), 0, 8 );
				$location_options[] = array(
					'id'     => $option_id,
					'city'   => $city_label,
					'postal' => $postal_label,
				);
			}
		}
		$current_selection_id = '';
		if ( '' !== $city_out && ! empty( $location_options ) ) {
			foreach ( $location_options as $option ) {
				if ( '' !== $postal_out && '' !== $option['postal'] ) {
					if ( 0 === strcasecmp( $option['city'], $city_out ) && 0 === strcasecmp( $option['postal'], $postal_out ) ) {
						$current_selection_id = $option['id'];
						break;
					}
				}
			}
			if ( '' === $current_selection_id ) {
				foreach ( $location_options as $option ) {
					if ( 0 === strcasecmp( $option['city'], $city_out ) ) {
						$current_selection_id = $option['id'];
						break;
					}
				}
			}
		}
		$has_location_options = ! empty( $location_options );

		?>
		<div id="foodord-location-modal" class="foodord-modal" aria-hidden="true">
			<div class="foodord-modal__content" role="dialog" aria-modal="true" aria-labelledby="foodord-location-modal-title">
				<div class="foodord-modal__header">
					<h3 id="foodord-location-modal-title" class="foodord-modal__title"><?php echo esc_html__( 'Enter your location', 'foodord' ); ?></h3>
					<button type="button" class="foodord-modal__close" id="foodord-location-close" aria-label="<?php echo esc_attr__( 'Close', 'foodord' ); ?>">
						<span class="foodord-modal__close-icon" aria-hidden="true"></span>
					</button>
				</div>
				<div class="foodord-modal__body">
					<?php if ( 'radius' === $delivery_mode ) : ?>
					<div class="foodord-form-row">
						<label for="foodord-radius-address" class="foodord-form-label screen-reader-text"><?php echo esc_html__( 'Address', 'foodord' ); ?></label>
						<div class="foodord-input-with-clear">
							<input type="text" id="foodord-radius-address" class="foodord-form-input" value="<?php echo esc_attr( $address_out ); ?>" placeholder="<?php echo esc_attr__( 'Start typing your address…', 'foodord' ); ?>" autocomplete="off">
							<button type="button" id="foodord-radius-clear" class="foodord-input-clear is-hidden" aria-label="<?php echo esc_attr__( 'Clear address', 'foodord' ); ?>">
								<span aria-hidden="true">&times;</span>
							</button>
						</div>
					</div>
					<input type="hidden" id="foodord-radius-lat" value="<?php echo esc_attr( '' !== $lat_out ? (string) $lat_out : '' ); ?>">
					<input type="hidden" id="foodord-radius-lng" value="<?php echo esc_attr( '' !== $lng_out ? (string) $lng_out : '' ); ?>">
					<input type="hidden" id="foodord-radius-city" value="<?php echo esc_attr( $city_out ); ?>">
					<input type="hidden" id="foodord-radius-postal" value="<?php echo esc_attr( $postal_out ); ?>">
					<div id="foodord-radius-feedback" class="foodord-form-feedback" aria-live="polite"></div>
				<?php else : ?>
				<div class="foodord-form-row">
						<label for="foodord-location-choice" class="foodord-form-label"><?php echo esc_html__( 'City', 'foodord' ); ?></label>
						<select id="foodord-location-choice" class="foodord-form-input" <?php disabled( ! $has_location_options ); ?>>
							<option value=""><?php echo esc_html__( 'Select your city', 'foodord' ); ?></option>
							<?php foreach ( $location_options as $option ) : ?>
								<option value="<?php echo esc_attr( $option['id'] ); ?>" data-city="<?php echo esc_attr( $option['city'] ); ?>" data-postal="<?php echo esc_attr( $option['postal'] ); ?>" <?php selected( $option['id'], $current_selection_id ); ?>><?php echo esc_html( $option['city'] ); ?></option>
							<?php endforeach; ?>
						</select>
					</div>
						<?php if ( ! $has_location_options ) : ?>
					<p class="foodord-modal__desc"><?php echo esc_html__( 'No delivery locations are configured. Please add at least one in Theme Settings → Delivery.', 'foodord' ); ?></p>
						<?php endif; ?>
					<div id="foodord-location-feedback" class="foodord-form-feedback" aria-live="polite"></div>
					<?php endif; ?>
				</div>
				<div class="foodord-modal__actions">
					<button type="button" class="foodord-modal__button foodord-modal__button--confirm" id="foodord-location-save"><?php echo esc_html__( 'Save', 'foodord' ); ?></button>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * AJAX: Validate location and persist to session.
	 *
	 * @return void
	 */
	public function ajax_validate_location(): void {
		try {
			$delivery_mode = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
			// Accept nonce via multiple common keys to avoid conflicts with security plugins.
			$nonce = '';
			if ( isset( $_POST['nonce'] ) ) {
				$nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ) );
			} elseif ( isset( $_POST['security'] ) ) {
				$nonce = sanitize_text_field( wp_unslash( $_POST['security'] ) );
			} elseif ( isset( $_GET['nonce'] ) ) {
				$nonce = sanitize_text_field( wp_unslash( $_GET['nonce'] ) );
			} elseif ( isset( $_GET['security'] ) ) {
				$nonce = sanitize_text_field( wp_unslash( $_GET['security'] ) );
			}
			if ( ! wp_verify_nonce( $nonce, 'foodord_location_nonce' ) ) {
				wp_send_json_error( array( 'message' => 'Invalid nonce' ), 403 );
			}

			if ( 'radius' === $delivery_mode ) {
				$address = isset( $_POST['address'] ) ? sanitize_text_field( wp_unslash( $_POST['address'] ) ) : '';
				$city    = isset( $_POST['city'] ) ? sanitize_text_field( wp_unslash( $_POST['city'] ) ) : '';
				$postal  = isset( $_POST['postal_code'] ) ? sanitize_text_field( wp_unslash( $_POST['postal_code'] ) ) : '';
				$lat_raw = isset( $_POST['lat'] ) ? sanitize_text_field( wp_unslash( $_POST['lat'] ) ) : null;
				$lng_raw = isset( $_POST['lng'] ) ? sanitize_text_field( wp_unslash( $_POST['lng'] ) ) : null;
				$this->ajax_validate_radius( $address, $city, $postal, $lat_raw, $lng_raw );
				return;
			}

			$city   = isset( $_POST['city'] ) ? sanitize_text_field( wp_unslash( $_POST['city'] ) ) : '';
			$postal = isset( $_POST['postal_code'] ) ? sanitize_text_field( wp_unslash( $_POST['postal_code'] ) ) : '';

			if ( '' === $city ) {
				wp_send_json_error( array( 'message' => __( 'City selection is required.', 'foodord' ) ) );
			}

			$match = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::find_location( $city, $postal ) : null;
			if ( $match ) {
				$raw_fee = isset( $match['fee'] ) ? (string) $match['fee'] : '';
				$fee     = 0.0;
				if ( function_exists( 'wc_format_decimal' ) ) {
					$fee = (float) wc_format_decimal( $raw_fee );
				} else {
					$fee = (float) $raw_fee;
				}
				$fee = max( 0.0, $fee );

				$raw_minimum = isset( $match['minimum'] ) ? (string) $match['minimum'] : '';
				$minimum     = 0.0;
				if ( '' !== $raw_minimum ) {
					if ( function_exists( 'wc_format_decimal' ) ) {
						$minimum = (float) wc_format_decimal( $raw_minimum );
					} else {
						$minimum = (float) $raw_minimum;
					}
				}
				$minimum = max( 0.0, $minimum );

				$match_postal = '';
				if ( isset( $match['postal_code'] ) && '' !== $match['postal_code'] ) {
					$match_postal = (string) $match['postal_code'];
				} elseif ( isset( $match['postalCode'] ) && '' !== $match['postalCode'] ) {
					$match_postal = (string) $match['postalCode'];
				}
				$resolved_postal = '' !== $match_postal ? $match_postal : $postal;

				if ( class_exists( 'Foodord_Location_Service' ) ) {
					Foodord_Location_Service::persist_session( $city, $resolved_postal, true, $fee, $minimum );
				}
				// Also set WooCommerce customer address for immediate fee calculation stability.
				if ( function_exists( 'WC' ) && WC()->customer ) {
					try {
						WC()->customer->set_shipping_city( $city );
						if ( '' !== $resolved_postal ) {
							WC()->customer->set_shipping_postcode( $resolved_postal );
						}
						if ( ! WC()->customer->get_billing_city() ) {
							WC()->customer->set_billing_city( $city );
						}
						if ( '' !== $resolved_postal && ! WC()->customer->get_billing_postcode() ) {
							WC()->customer->set_billing_postcode( $resolved_postal );
						}
						WC()->customer->save();
					} catch ( Exception $e ) {
						// Surface the failure without blocking the user flow.
						do_action( 'foodord_location_customer_save_error', $e );
					}
				}
				wp_send_json_success(
					array(
						'allowed' => true,
						'fee'     => $fee,
						'city'    => $city,
						'postal'  => $resolved_postal,
						'minimum' => $minimum,
					)
				);
			}

			if ( class_exists( 'Foodord_Location_Service' ) ) {
				Foodord_Location_Service::persist_session( $city, $postal, false, 0.0, 0.0 );
			}
			wp_send_json_success(
				array(
					'allowed' => false,
					'fee'     => 0,
					'city'    => $city,
					'postal'  => $postal,
					'message' => __( 'We do not deliver to this area.', 'foodord' ),
				)
			);
		} catch ( Exception $e ) {
			wp_send_json_error( array( 'message' => 'PHP Error: ' . $e->getMessage() ) );
		}
	}

	/**
	 * Handle radius-based validation request.
	 *
	 * @param string      $address Address line.
	 * @param string      $city    City name.
	 * @param string      $postal  Postal code.
	 * @param string|null $lat_raw Latitude value.
	 * @param string|null $lng_raw Longitude value.
	 */
	private function ajax_validate_radius( string $address, string $city, string $postal, ?string $lat_raw, ?string $lng_raw ): void {

		if ( null === $lat_raw || null === $lng_raw || ! is_numeric( $lat_raw ) || ! is_numeric( $lng_raw ) ) {
			wp_send_json_error( array( 'message' => __( 'Address coordinates are missing. Please select an address from the suggestions.', 'foodord' ) ) );
		}

		$lat = (float) $lat_raw;
		$lng = (float) $lng_raw;

		$radius_settings = Foodord_Location_Service::get_radius_settings();
		$distance_limit  = isset( $radius_settings['distance'] ) ? (float) $radius_settings['distance'] : 0.0;
		$radius_zones    = isset( $radius_settings['zones'] ) && is_array( $radius_settings['zones'] ) ? $radius_settings['zones'] : array();

		if ( empty( $radius_zones ) ) {
			if ( $distance_limit <= 0 ) {
				wp_send_json_error( array( 'message' => __( 'Delivery radius is not configured. Please contact the restaurant.', 'foodord' ) ) );
			}
			$radius_zones = array(
				array(
					'radius'  => $distance_limit,
					'minimum' => isset( $radius_settings['minimum'] ) ? (float) $radius_settings['minimum'] : 0.0,
					'fee'     => isset( $radius_settings['fee'] ) ? (float) $radius_settings['fee'] : 0.0,
					'label'   => '',
				),
			);
		}

		usort(
			$radius_zones,
			static function ( $a, $b ) {
				$radius_a = isset( $a['radius'] ) ? (float) $a['radius'] : 0.0;
				$radius_b = isset( $b['radius'] ) ? (float) $b['radius'] : 0.0;
				return $radius_a <=> $radius_b;
			}
		);

		$center_lat = isset( $radius_settings['center_lat'] ) ? $radius_settings['center_lat'] : null;
		$center_lng = isset( $radius_settings['center_lng'] ) ? $radius_settings['center_lng'] : null;
		if ( null === $center_lat || null === $center_lng ) {
			wp_send_json_error( array( 'message' => __( 'Store location is not available. Please try again later.', 'foodord' ) ) );
		}

		$distance_km  = $this->calculate_distance_km( (float) $center_lat, (float) $center_lng, $lat, $lng );
		$matched_zone = null;
		foreach ( $radius_zones as $zone ) {
			$zone_radius = isset( $zone['radius'] ) ? (float) $zone['radius'] : 0.0;
			if ( $zone_radius <= 0 ) {
				continue;
			}
			if ( $distance_km <= $zone_radius ) {
				$matched_zone = $zone;
				break;
			}
		}

		if ( null !== $matched_zone ) {
			$fee         = isset( $matched_zone['fee'] ) ? (float) $matched_zone['fee'] : 0.0;
			$minimum     = isset( $matched_zone['minimum'] ) ? (float) $matched_zone['minimum'] : 0.0;
			$zone_radius = isset( $matched_zone['radius'] ) ? (float) $matched_zone['radius'] : 0.0;

			Foodord_Location_Service::persist_session(
				$city,
				$postal,
				true,
				$fee,
				$minimum,
				array(
					'address'     => $address,
					'lat'         => $lat,
					'lng'         => $lng,
					'zone_radius' => $zone_radius,
				)
			);

			if ( function_exists( 'WC' ) && WC()->customer ) {
				try {
					if ( '' !== $city ) {
						WC()->customer->set_shipping_city( $city );
					}
					if ( '' !== $postal ) {
						WC()->customer->set_shipping_postcode( $postal );
					}
					if ( '' !== $address ) {
						WC()->customer->set_shipping_address_1( $address );
					}
					if ( '' !== $city && ! WC()->customer->get_billing_city() ) {
						WC()->customer->set_billing_city( $city );
					}
					if ( '' !== $postal && ! WC()->customer->get_billing_postcode() ) {
						WC()->customer->set_billing_postcode( $postal );
					}
					if ( '' !== $address && ! WC()->customer->get_billing_address_1() ) {
						WC()->customer->set_billing_address_1( $address );
					}
					WC()->customer->save();
				} catch ( Exception $e ) {
					do_action( 'foodord_location_customer_save_error', $e );
				}
			}

			wp_send_json_success(
				array(
					'allowed'     => true,
					'fee'         => $fee,
					'city'        => $city,
					'postal'      => $postal,
					'address'     => $address,
					'lat'         => $lat,
					'lng'         => $lng,
					'minimum'     => $minimum,
					'distance'    => $distance_km,
					'zone_radius' => $zone_radius,
				)
			);
		}

		Foodord_Location_Service::persist_session( $city, $postal, false, 0.0, 0.0 );
		wp_send_json_success(
			array(
				'allowed'     => false,
				'fee'         => 0,
				'city'        => $city,
				'postal'      => $postal,
				'address'     => $address,
				'message'     => __( 'We do not deliver to this address.', 'foodord' ),
				'distance'    => $distance_km,
				'zone_radius' => 0,
			)
		);
	}

	/**
	 * Calculate distance between two coordinates in kilometers.
	 *
	 * @param float $lat1 Latitude 1.
	 * @param float $lng1 Longitude 1.
	 * @param float $lat2 Latitude 2.
	 * @param float $lng2 Longitude 2.
	 * @return float
	 */
	private function calculate_distance_km( float $lat1, float $lng1, float $lat2, float $lng2 ): float {
		$earth_radius = 6371.0;

		$lat_delta = deg2rad( $lat2 - $lat1 );
		$lng_delta = deg2rad( $lng2 - $lng1 );

		$a = sin( $lat_delta / 2 ) * sin( $lat_delta / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $lng_delta / 2 ) * sin( $lng_delta / 2 );
		$c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) );

		return $earth_radius * $c;
	}

	/**
	 * Prefill checkout billing/shipping fields from session.
	 *
	 * @param mixed  $value Existing value.
	 * @param string $input Input key.
	 * @return mixed
	 */
	public function prefill_checkout_values( $value, string $input ) {
		$session = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array(
			'city'    => '',
			'postal'  => '',
			'address' => '',
		);
		$city    = (string) ( $session['city'] ?? '' );
		$postal  = (string) ( $session['postal'] ?? '' );
		$address = (string) ( $session['address'] ?? '' );

		switch ( $input ) {
			case 'billing_city':
			case 'shipping_city':
				return '' !== $city ? $city : $value;
			case 'billing_postcode':
			case 'shipping_postcode':
				return '' !== $postal ? $postal : $value;
			case 'billing_address_1':
			case 'shipping_address_1':
				return '' !== $address ? $address : $value;
			default:
				return $value;
		}
	}

	/**
	 * Enforce location validation during checkout.
	 *
	 * @param array    $data Posted checkout data.
	 * @param WP_Error $errors Error collector.
	 * @return void
	 */
	public function validate_checkout_address( array $data, $errors ): void {
		if ( ! is_object( $errors ) || ! method_exists( $errors, 'add' ) ) {
			return;
		}

		$mode = $this->get_current_order_mode();
		if ( 'pickup' === $mode ) {
			return;
		}

		$delivery_mode = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_delivery_mode() : 'postal';
		if ( 'radius' === $delivery_mode ) {
			$session = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array();
			$allowed = ! empty( $session['allowed'] );
			if ( ! $allowed ) {
				$errors->add( 'foodord_location_error', __( 'We do not deliver to the selected address. Please update it before continuing.', 'foodord' ) );
			}
			return;
		}

		$city   = isset( $data['billing_city'] ) ? sanitize_text_field( (string) $data['billing_city'] ) : '';
		$postal = isset( $data['billing_postcode'] ) ? sanitize_text_field( (string) $data['billing_postcode'] ) : '';

		if ( '' === $city ) {
			return; // Let WooCommerce required-field errors handle empties.
		}

		if ( class_exists( 'Foodord_Location_Service' ) ) {
			Foodord_Location_Service::maybe_sync_from_checkout_post( $data );
		}

		$matched = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::find_location( $city, $postal ) : null;
		if ( ! $matched ) {
			$errors->add( 'foodord_location_error', __( 'We do not deliver to the entered address.', 'foodord' ) );
		}
	}

	/**
	 * Add a location-based delivery fee if enabled and allowed.
	 *
	 * @param WC_Cart $cart Cart instance.
	 * @return void
	 */
	public function maybe_add_location_fee( $cart ): void {
		if ( ! $cart || ! method_exists( $cart, 'get_cart_contents_count' ) ) {
			if ( function_exists( 'foodord_track_delivery_fee' ) ) {
				foodord_track_delivery_fee( 0.0 );
			}
			return;
		}
		if ( 0 === (int) $cart->get_cart_contents_count() ) {
			if ( function_exists( 'foodord_track_delivery_fee' ) ) {
				foodord_track_delivery_fee( 0.0 );
			}
			return;
		}

		// Respect pickup mode.
		$mode = $this->get_current_order_mode();
		if ( 'pickup' === $mode ) {
			if ( function_exists( 'foodord_track_delivery_fee' ) ) {
				foodord_track_delivery_fee( 0.0 );
			}
			return;
		}

		$type = get_option( 'foodord_delivery_fee_type', 'fixed' );
		if ( 'distance' !== $type ) {
			return;
		}

		$session = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array(
			'allowed' => false,
			'fee'     => 0,
		);
		$allowed = (bool) ( $session['allowed'] ?? false );
		$fee     = (float) ( $session['fee'] ?? 0 );
		$fee     = max( 0.0, $fee );

		if ( $allowed && $fee > 0 ) {
			if ( function_exists( 'foodord_track_delivery_fee' ) ) {
				foodord_track_delivery_fee( $fee );
			}
			$cart->add_fee( __( 'Delivery fee', 'foodord' ), $fee, false, '' );
		} elseif ( function_exists( 'foodord_track_delivery_fee' ) ) {
			foodord_track_delivery_fee( 0.0 );
		}
	}

	/**
	 * Determine current order mode with session/cookie fallback.
	 *
	 * @return string
	 */
	private function get_current_order_mode(): string {
		if ( function_exists( 'foodord_get_order_mode' ) ) {
			return foodord_get_order_mode();
		}
		if ( function_exists( 'WC' ) && WC()->session ) {
			$session_mode = (string) WC()->session->get( 'foodord_order_mode', 'delivery' );
			if ( in_array( $session_mode, array( 'delivery', 'pickup' ), true ) ) {
				return $session_mode;
			}
		}
		return 'delivery';
	}

	/**
	 * Register AJAX handlers in all contexts (admin and frontend).
	 *
	 * @return void
	 */
	public static function register_ajax(): void {
		$instance = new self();
		add_action( 'wp_ajax_foodord_validate_location', array( $instance, 'ajax_validate_location' ) );
		add_action( 'wp_ajax_nopriv_foodord_validate_location', array( $instance, 'ajax_validate_location' ) );
	}

	/**
	 * Show a notice on checkout when an address is selected but not allowed.
	 *
	 * @return void
	 */
	public function maybe_notice_invalid_address(): void {
		if ( ! function_exists( 'WC' ) ) {
			return;
		}
		$mode = $this->get_current_order_mode();
		if ( 'pickup' === $mode ) {
			return;
		}
		$session = class_exists( 'Foodord_Location_Service' ) ? Foodord_Location_Service::get_session() : array();
		$has_any = ! empty( $session['city'] ) || ! empty( $session['postal'] ) || ! empty( $session['address'] );
		$allowed = (bool) ( $session['allowed'] ?? false );
		if ( $has_any && ! $allowed ) {
			wc_print_notice( esc_html__( 'We do not deliver to the selected area. Please update your address.', 'foodord' ), 'error' );
		}
	}
}
