<?php

namespace Ilabs\Inpost_International\Helpers;

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


use Exception;
use WC_Shipping;
use WC_Shipping_Zone;
use WC_Shipping_Zones;

/**
 * Helpful functions
 */
class Helper {

	/**
	 * Helper
	 *
	 * @var $instance
	 */
	protected static $instance;


	/**
	 * Cached shipping zones \WC_Shipping_Zones::get_zones()
	 *
	 * @var $cached_zones
	 */
	private $cached_zones = array();

	/**
	 * Return instance of class
	 *
	 * @return self
	 */
	public static function init(): Helper {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}


	/**
	 * Check if plugin "Flexible shipping" is activated
	 *
	 * @return boolean
	 */
	public function is_flexible_shipping_activated(): bool {

		$plugin_file    = 'flexible-shipping/flexible-shipping.php';
		$active_plugins = (array) get_option( 'active_plugins', array() );

		if ( is_multisite() ) {
			$active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
		}

		return in_array( $plugin_file, $active_plugins, true ) || array_key_exists( $plugin_file, $active_plugins );
	}


	/**
	 * Integration with plugin "Flexible shipping" (get shipping method linked in FS settings)
	 *
	 * @param mixed $chosen_shipping_methods $chosen_shipping_methods.
	 *
	 * @return string
	 */
	public function get_method_linked_to_fs( $chosen_shipping_methods ) {
		$method_linked_to_fs = '';
		if ( is_array( $chosen_shipping_methods ) ) {
			foreach ( $chosen_shipping_methods as $shipping_method ) {
				if ( 0 === strpos( $shipping_method, 'flexible_shipping_single' ) ) {
					$shipping_method_instance_id = $this->get_shipping_method_instance_id( $shipping_method );
					if ( $shipping_method_instance_id ) {
						$method_linked_to_fs = $this->get_method_linked_to_fs_by_instance_id( $shipping_method_instance_id );
					}
				}
			}
		}

		return $method_linked_to_fs;
	}


	/**
	 * Integration with plugin "Flexible shipping" (get shipping method linked in FS settings)
	 *
	 * @param string $instance_id $instance_id.
	 *
	 * @return string
	 */
	public function get_method_linked_to_fs_by_instance_id( $instance_id ) {
		$method_linked_to_fs      = '';
		$shipping_method_settings = get_option( 'woocommerce_flexible_shipping_single_' . $instance_id . '_settings' );

		if ( ! empty( $shipping_method_settings['fs_inpost_international_method'] ) ) {
			$method_linked_to_fs = $shipping_method_settings['fs_inpost_international_method'];
		}

		return $method_linked_to_fs;
	}


	public function get_shipping_method_instance_id( $shipping_method_name ) {
		if ( stripos( $shipping_method_name, ':' ) ) {
			return trim( explode( ':', $shipping_method_name )[1] );
		}
		return null;
	}



	/**
	 * Get configured shipping methods
	 *
	 * @return array
	 */
	public function get_shipping_methods(): array {

		$shipping_methods = array();
		if ( class_exists( 'WC_Shipping' ) ) {
			$shipping         = new WC_Shipping();
			$shipping_methods = $shipping->get_shipping_methods();
		}

		return $shipping_methods;
	}


	/**
	 * Get configured Inpost shipping methods
	 *
	 * @return array
	 */
	public function get_inpost_shipping_methods(): array {

		$configured_shipping_methods = array();

		$delivery_zones = $this->get_cached_zones();

		foreach ( (array) $delivery_zones as $key => $the_zone ) {
			if ( isset( $the_zone['shipping_methods'] ) ) {
				foreach ( $the_zone['shipping_methods'] as $configured_method ) {
					if ( 0 === strpos( $configured_method->id, 'inpost_international_' ) ) {
						$configured_shipping_methods[ $configured_method->instance_id ]['user_title']           = $configured_method->title;
						$configured_shipping_methods[ $configured_method->instance_id ]['method_title']         = $configured_method->id;
						$configured_shipping_methods[ $configured_method->instance_id ]['method_title_with_id'] = $configured_method->id . ':' . $configured_method->instance_id;
						$configured_shipping_methods[ $configured_method->instance_id ]['inpost_title']         = $configured_method->id;

						$logo_src = $this->get_shipping_method_logo_src( $configured_method->id, $configured_method->instance_id );
						$configured_shipping_methods[ $configured_method->instance_id ]['inpost_icon'] = ! empty( $logo_src ) ? $logo_src : false;
                        $configured_shipping_methods[ $configured_method->instance_id ]['delivery_terms'] = $this->get_shipping_method_delivery_terms( $configured_method->id, $configured_method->instance_id );

						if ( 0 === strpos( $configured_method->id, 'inpost_international_automated_locker_point' ) ) {
							$configured_shipping_methods[ $configured_method->instance_id ]['need_map'] = 1;
						}
					} elseif ( 0 === strpos( $configured_method->id, 'flexible_shipping' ) ) {
						// Integration with Flexible Shipping.
						$linked_method = $this->get_method_linked_to_fs_by_instance_id( $configured_method->instance_id );
						if ( 0 === strpos( $linked_method, 'inpost_international_' ) ) {
							$configured_shipping_methods[ $configured_method->instance_id ]['user_title']           = $configured_method->title;
							$configured_shipping_methods[ $configured_method->instance_id ]['method_title']         = $configured_method->id;
							$configured_shipping_methods[ $configured_method->instance_id ]['method_title_with_id'] = $configured_method->id . ':' . $configured_method->instance_id;
							$configured_shipping_methods[ $configured_method->instance_id ]['inpost_title']         = $linked_method;

							if ( 0 === strpos( $linked_method, 'inpost_international_automated_locker_point' ) ) {
								$configured_shipping_methods[ $configured_method->instance_id ]['need_map'] = 1;
							}

							$configured_shipping_methods[ $configured_method->instance_id ]['inpost_icon'] = inpost_intl_core()->get_plugin_images_url() . '/white.png';
						}
					}
				}
			}
		}

		return $configured_shipping_methods;
	}


	/**
	 * Retrieves the logo source URL for a specific shipping method.
	 *
	 * This function gets the logo URL for an InPost International shipping method based on its settings.
	 * It can return a default white logo, a yellow logo, a black logo, or a custom logo URL.
	 *
	 * @since 1.0.0
	 *
	 * @param string $shipping_method_id The ID of the shipping method.
	 * @param string $shipping_method_instance_id The instance ID of the shipping method.
	 *
	 * @uses get_option() To retrieve the shipping method settings.
	 * @uses inpost_intl_core()->get_plugin_images_url() To get the plugin's images URL.
	 *
	 * @return string The URL of the logo image for the shipping method.
	 */
	public function get_shipping_method_logo_src( $shipping_method_id, $shipping_method_instance_id ) {
        $shipping_method_settings_name = 'woocommerce_' . $shipping_method_id . '_' . $shipping_method_instance_id . '_settings';
        $shipping_method_settings      = get_option( $shipping_method_settings_name );

        $logo_src = '';
        if ( ! empty( $shipping_method_settings['logo_selected'] ) ) {
            $chosen_logo = $shipping_method_settings['logo_selected'];
            if ( 'white' === $chosen_logo ) {
                return inpost_intl_core()->get_plugin_images_url() . '/white.png';
            } elseif ( 'yellow' === $chosen_logo ) {
                return inpost_intl_core()->get_plugin_images_url() . '/yellow.png';

            } elseif ( 'black' === $chosen_logo ) {
                return inpost_intl_core()->get_plugin_images_url() . '/black.png';

            } elseif ( 'mondial' === $chosen_logo ) {
                return inpost_intl_core()->get_plugin_images_url() . '/mondial.png';

            } elseif ( 'custom' === $chosen_logo ) {
                if ( ! empty( $shipping_method_settings['logo_custom'] ) ) {
                    return esc_url( $shipping_method_settings['logo_custom'] );
                } else {
                    return '';
                }
            }
        }

        return $logo_src;
    }


	/**
	 * Clears the cached shipping zones.
	 *
	 * This function resets the cached_zones property to null, effectively clearing any
	 * previously stored shipping zone data. This is typically used to ensure that fresh
	 * zone data is fetched on the next request.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public function clear_zones_cache() {
		$this->cached_zones = null;
	}


	/**
	 * Retrieves and caches shipping zones.
	 *
	 * This private method checks if shipping zones are already cached. If not, it fetches
	 * the zones using WooCommerce's WC_Shipping_Zones class and stores them in the
	 * cached_zones property. This helps to minimize repeated calls to WC_Shipping_Zones::get_zones().
	 *
	 * @since 1.0.0
	 *
	 * @uses WC_Shipping_Zones::get_zones() To fetch shipping zones if not already cached.
	 *
	 * @return array An array of WooCommerce shipping zones, or an empty array if WC_Shipping_Zones class doesn't exist.
	 */
	private function get_cached_zones() {
		if ( empty( $this->cached_zones ) ) {
			if ( class_exists( 'WC_Shipping_Zones' ) ) {
				$this->cached_zones = \WC_Shipping_Zones::get_zones();
			}
		}
		return $this->cached_zones;
	}


	/**
	 * Get package sizes
	 *
	 * @return array
	 */
	public function get_package_sizes(): array {
		return array(
			'small'  => esc_html__( 'A 8 x 38 x 64 cm', 'inpost-international' ),
			'medium' => esc_html__( 'B 19 x 38 x 64 cm', 'inpost-international' ),
			'large'  => esc_html__( 'C 39 x 38 x 64 cm', 'inpost-international' ),
		);
	}


	/**
	 * Get possible places to output Map button on classic Checkout
	 *
	 * @return array
	 */
	public function get_checkout_hooks_for_map_button(): array {
		return array(
            'woocommerce_review_order_after_shipping'     => esc_html__( 'After describing the delivery methods', 'inpost-international' ),
			'woocommerce_review_order_before_submit'      => esc_html__( 'Before the "Place order" button', 'inpost-international' ),
			'woocommerce_after_order_notes'               => esc_html__( 'After the "Add note" field', 'inpost-international' ),
			'woocommerce_before_order_notes'              => esc_html__( 'Before the "Add note" field', 'inpost-international' ),
			'woocommerce_review_order_before_payment'     => esc_html__( 'Before information about payment methods', 'inpost-international' ),
			'woocommerce_review_order_before_order_total' => esc_html__( 'Before information about the total amount of the order', 'inpost-international' ),
			'woocommerce_review_order_after_order_total'  => esc_html__( 'After information about the total amount of the order', 'inpost-international' ),
			'woocommerce_review_order_before_shipping'    => esc_html__( 'Before describing the delivery methods', 'inpost-international' ),
			'woocommerce_after_checkout_shipping_form'    => esc_html__( 'After form with shipping address', 'inpost-international' ),
			'woocommerce_checkout_after_customer_details' => esc_html__( 'After customer details', 'inpost-international' ),
			'woocommerce_checkout_before_order_review'    => esc_html__( 'Before order details review', 'inpost-international' ),
			'woocommerce_review_order_after_submit'       => esc_html__( 'After the "Place order" button', 'inpost-international' ),
			'woocommerce_before_checkout_form'            => esc_html__( 'Before form with checkout details', 'inpost-international' ),
		);
	}

	/**
	 * Get template rates saved for shipping method
	 *
	 * @param string $id shipping method name.
	 * @param int    $instance_id shipping method instance id.
	 *
	 * @return array
	 */
	public function get_template_rates_saved_for_shipping_method( $id, $instance_id ): array {

		$rates = get_option( 'inpost_intl_' . $id . '_' . $instance_id . '_template_rates' );

		$sending_options = get_option( 'inpost_international_settings_sending' );

		$actual_templates   = array();
		$actual_templates[] = array(
			'name' => esc_html__( 'From product settings', 'inpost-international' ),
			'cost' => '',

		);

		if ( ! empty( $sending_options['templates'] ) ) {
			foreach ( $sending_options['templates'] as $template ) {
				if ( ! empty( $template['name'] ) ) {
					$actual_templates[] = array(
						'name' => $template['name'],
						'cost' => '',

					);
				}
			}
		}

		if ( is_array( $rates ) ) {
			foreach ( $actual_templates as $key => $template_from_general_settings ) {
				foreach ( $rates as $key2 => $previously_saved_template ) {
					if ( $template_from_general_settings['name'] === $previously_saved_template['name'] ) {
						$actual_templates[ $key ]['cost'] = $previously_saved_template['cost'];
					}
				}
			}
		}

		return $actual_templates;
	}


	/**
	 * Get rates saved for shipping method
	 *
	 * @param string $id shipping method name.
	 * @param int    $instance_id shipping method instance id.
	 *
	 * @return array
	 */
	public function get_rates_saved_for_shipping_method( $id, $instance_id ): array {

		$rates = get_option( 'inpost_intl_' . $id . '_' . $instance_id . '_rates' );

		return is_array( $rates ) ? $rates : array();
	}


	/**
	 * Check if at least one physical product exists in cart
	 *
	 * @param array $cart_contents $cart_contents.
	 *
	 * @return bool
	 */
	public function check_physical_goods_in_cart( $cart_contents ) {
		$res = false;

		if ( ! empty( $cart_contents ) ) {
			foreach ( $cart_contents as $cart_item_key => $cart_item ) {
				// if variation in cart.
				if ( $cart_item['variation_id'] ) {

					$variant = wc_get_product( $cart_item['variation_id'] );
					if ( ! $variant->is_virtual() ) {
						$res = true;
						break;
					}
				} else {
					// simple product.
					$_product = wc_get_product( $cart_item['product_id'] );
					if ( ! $_product->is_virtual() ) {
						$res = true;
						break;
					}
				}
			}
		}

		return $res;
	}



	/**
	 * Retrieves the package send method options.
	 *
	 * This function returns an array of available package send methods for InPost International.
	 * The options include APM (Automated Parcel Machines), PUDO (Pick Up Drop Off),
	 * HUB, and COURIER (address-to-point delivery).
	 *
	 * @since 1.0.0
	 *
	 * @return array An associative array of send method codes and their translated labels.
	 *               Keys are the method codes, and values are the translated labels.
	 *
	 * @uses esc_html__() For translating the method labels.
	 */
	public function get_package_send_method_options( $shipping_method, $country_code ) {

		$send_options = array(
			'address-to-point' => esc_html__( 'COURIER', 'inpost-international' ),
			'point-to-point'   => esc_html__( 'APM/PUDO/HUB', 'inpost-international' ),
		);

		if ( 'IT' === $country_code ) {
			if ( 'inpost_international_courier' === $shipping_method ) {
				$send_options = array(
					'address-to-address' => esc_html__( 'COURIER', 'inpost-international' ),
					'point-to-address'   => esc_html__( 'APM/PUDO', 'inpost-international' ),
				);

			} else {
				$send_options = array(
					'address-to-point' => esc_html__( 'COURIER', 'inpost-international' ),
					'point-to-point'   => esc_html__( 'APM/PUDO', 'inpost-international' ),
				);
			}
		}

		return $send_options;
	}


	/**
	 * Check if used any Sequential Order Numbers
	 *
	 * @param int $order_id $order_id.
	 * @return mixed
	 */
	public function get_maybe_custom_reference_number( $order_id ) {

		$reference_number = $order_id;

		if ( class_exists( 'Wt_Advanced_Order_Number' ) || class_exists( 'WC_Seq_Order_Number' ) ) {
			$order = wc_get_order( $order_id );
			if ( $order && ! is_wp_error( $order ) ) {
				if ( ! empty( $order->get_meta( '_order_number' ) ) ) {
					$reference_number = $order->get_meta( '_order_number' );
				}
			}
		}

		return $reference_number;
	}



	/**
	 * Get insurance amount
	 *
	 * @param int $order_id $order_id.
	 *
	 * @return false|float
	 */
	public function get_insurance_amount( $order_id ) {

		/**
		 * Priority to get insurance:
		 * 1) from shipping method settings if method linked to FS.
		 * 2) from shipping method settings.
		 */

		$insurance_amount = false;

		$order = wc_get_order( $order_id );

		if ( ! $order || is_wp_error( $order ) ) {
			return false;
		}

		$shipping_method_name = '';
		$method_instance_id   = '';

		foreach ( $order->get_shipping_methods() as $shipping_method ) {
			$method_instance_id   = $shipping_method->get_instance_id();
			$shipping_method_name = $shipping_method->get_method_id();
		}

		$shipping_method_settings_name = 'woocommerce_' . $shipping_method_name . '_' . $method_instance_id . '_settings';

		$shipping_method_settings = get_option( $shipping_method_settings_name );

		if ( isset( $shipping_method_settings['insurance_inpost_intl'] ) ) {
			if ( 'yes' === $shipping_method_settings['insurance_inpost_intl'] ) {
				$insurance_amount = $order->get_total();
			} else {
				$insurance_amount = ! empty( $shipping_method_settings['insurance_value_inpost_intl'] )
					? floatval( $shipping_method_settings['insurance_value_inpost_intl'] )
					: 0.00;
			}
		} elseif ( isset( $shipping_method_settings['fs_insurance_inpost_intl'] ) ) {
			if ( 'yes' === $shipping_method_settings['fs_insurance_inpost_intl'] ) {
				$insurance_amount = $order->get_total();
			} else {
				$insurance_amount = ! empty( $shipping_method_settings['fs_insurance_value_inpost_intl'] )
				? floatval( $shipping_method_settings['fs_insurance_value_inpost_intl'] )
				: 0.00;

			}
		}

		return $insurance_amount ? $insurance_amount : 0.00;
	}



	/**
	 * Retrieves the package send method options.
	 *
	 * This function returns an array of available package send methods for InPost International.
	 * The options include APM (Automated Parcel Machines), PUDO (Pick Up Drop Off),
	 * HUB, and COURIER (address-to-point delivery).
	 *
	 * @since 1.0.0
	 *
	 * @return array An associative array of send method codes and their translated labels.
	 *               Keys are the method codes, and values are the translated labels.
	 *
	 * @uses esc_html__() For translating the method labels.
	 */
	public function get_order_dimensions_based_on_order( $order_id ) {

		$order = wc_get_order( $order_id );

		if ( ! $order || is_wp_error( $order ) ) {
			return array(
				'length' => '',
				'width'  => '',
				'height' => '',
				'weight' => '',
			);
		}

		$items = $order->get_items();

		if ( is_array( $items ) ) {

			// Single product in order.
			if ( count( $items ) === 1 ) {

				$product = reset( $items );

				return $this->get_single_product_dimensions( $product->get_product_id() );

			} else {

				return $this->get_or_biggest_template_or_calculate_multiple_products_dimensions( $items );
			}
		}
	}


	/**
	 * Retrieves the dimensions and weight of a single product.
	 *
	 * This function fetches the length, width, height, and weight of a specified product.
	 * If the product cannot be found or an error occurs, it returns empty strings for all dimensions.
	 *
	 * @since 1.0.0
	 *
	 * @param int $product_id The ID of the product to retrieve dimensions for.
	 *
	 * @uses wc_get_product() To retrieve the WooCommerce product object.
	 *
	 * @return array An associative array containing:
	 *               - 'length': The length of the product (string)
	 *               - 'width': The width of the product (string)
	 *               - 'height': The height of the product (string)
	 *               - 'weight': The weight of the product (string)
	 *               Returns empty strings for each dimension if the product is invalid or not found.
	 */
	public function get_single_product_dimensions( $product_id ) {

		$product_template = get_post_meta( $product_id, 'inpost_international_parcel_template', true );

		if ( ! empty( $product_template ) && 'default' !== $product_template ) { // not "From  product settings".

			$dimensions = $this->get_dimensions_by_template_name( $product_template );

			return $dimensions;
		} else {

			// try to get dimensions from Product settings.
			$product = wc_get_product( $product_id );

			if ( ! $product || is_wp_error( $product ) ) {
				return array(
					'length' => '',
					'width'  => '',
					'height' => '',
					'weight' => '',
				);
			}

			$dimensions = array(
				'length' => $product->get_length(),
				'width'  => $product->get_width(),
				'height' => $product->get_height(),
				'weight' => $product->get_weight(),
			);
		}

		return $dimensions;
	}


	/**
	 * Calculates combined dimensions and weight for multiple products.
	 *
	 * This function computes the total weight and optimal dimensions for a collection of products,
	 * ensuring they fit within a standard box size of 64x41x38 cm. It scales dimensions if necessary
	 * and adjusts calculations to maintain the volume while respecting maximum dimensions.
	 *
	 * @since 1.0.0
	 *
	 * @param array $items An array of WC_Order_Item_Product objects representing the order items.
	 *
	 * @return array An associative array containing:
	 *               - 'length': Calculated length in cm (float, rounded to 1 decimal place)
	 *               - 'width': Calculated width in cm (float, rounded to 1 decimal place)
	 *               - 'height': Calculated height in cm (float, rounded to 1 decimal place)
	 *               - 'weight': Total weight in kg (float, rounded to 1 decimal place)
	 *
	 * @uses WC_Order_Item_Product::get_product() To get the product object for each item.
	 * @uses WC_Product::get_weight() To get the weight of each product.
	 * @uses WC_Product::get_length() To get the length of each product.
	 * @uses WC_Product::get_width() To get the width of each product.
	 * @uses WC_Product::get_height() To get the height of each product.
	 */
	public function calculate_multiple_products_dimensions( $items ) {

		$calculated_length = 0;
		$calculated_width  = 0;
		$calculated_height = 0;

		$total_weight = 0;
		$total_volume = 0;
		$max_length   = 0;
		$max_width    = 0;

		foreach ( $items as $item ) {
			$product = $item->get_product();
			if ( is_object( $product ) ) {
				$quantity = (int) $item->get_quantity();

				$total_weight += floatval( $product->get_weight() ) * $quantity;
				$length        = floatval( $product->get_length() );
				$width         = floatval( $product->get_width() );
				$height        = floatval( $product->get_height() );
				$item_volume   = $length * $width * $height * $quantity;
				$total_volume += $item_volume;

				$max_length = max( $max_length, $length );
				$max_width  = max( $max_width, $width );
			}
		}

		// Calculate dimensions to fit within 64x41x38 cm box.
		$box_volume = 64 * 39 * 38;
		if ( $total_volume > 0 ) {
			$scale_factor = min( 1, $box_volume / $total_volume );

			$calculated_length = min( 64, $max_length * $scale_factor );
			$calculated_width  = min( 41, $max_width * $scale_factor );

			if ( $calculated_length > 0 && $calculated_width > 0 ) {
				$calculated_height = $total_volume / ( $calculated_length * $calculated_width );

				// Ensure height doesn't exceed 38 cm.
				if ( $calculated_height > 38 ) {
					$calculated_height = 38;
					$calculated_width  = min( 39, sqrt( $total_volume / ( 38 * $calculated_length ) ) );
					$calculated_length = $total_volume / ( 38 * $calculated_width );
				}
			}
		}

		if ( $calculated_height > $calculated_width ) {
			$calculated_height_new = $calculated_width;

			$calculated_width  = $calculated_height;
			$calculated_height = $calculated_height_new;
		}

		return array(
			'length' => round( $calculated_length, 1 ),
			'width'  => round( $calculated_width, 1 ),
			'height' => round( $calculated_height, 1 ),
			'weight' => round( $total_weight, 1 ),
		);
	}


	/**
	 * Retrieves the selected shipment template from the InPost International settings.
	 *
	 * This function fetches the sending options from the WordPress options table and
	 * searches for the selected shipment template. If a selected template is found,
	 * it is returned. Otherwise, an empty array is returned.
	 *
	 * @since 1.0.0
	 *
	 * @uses get_option() To retrieve the InPost International sending settings.
	 *
	 * @return array The selected shipment template as an associative array, or an empty array if no template is selected.
	 *               The template array may include keys such as 'name' and other template-specific data.
	 */
	public function get_shipment_template() {
		$sending_options = get_option( 'inpost_international_settings_sending' );
		if ( ! empty( $sending_options['templates'] ) && is_array( $sending_options['templates'] ) ) {
			foreach ( $sending_options['templates'] as $key => $template ) {
				if ( ! empty( $template['name'] ) ) {
					$select_options[ $key ] = $template['name'];
					if ( ! empty( $template['selected'] ) ) {
						return $template;
					}
				}
			}
		}

		return array();
	}


	/**
	 * Checks if a parcel is being created in single mode via AJAX.
	 *
	 * This function verifies the AJAX request's nonce and checks if the action
	 * is specifically for creating an InPost International package in single mode.
	 *
	 * @since 1.0.0
	 *
	 * @uses check_ajax_referer() To verify the AJAX request's nonce.
	 * @uses sanitize_text_field() To sanitize the input.
	 * @uses wp_unslash() To remove slashes from the input.
	 *
	 * @return bool True if a parcel is being created in single mode, false otherwise.
	 *
	 * @throws WPDieException If the AJAX nonce verification fails.
	 */
	public function is_creating_parcel_in_single_mode() {
		check_ajax_referer( 'inpost_international_ajax', 'security' );
		if ( ! empty( $_POST['inpost_intl_action'] )
			&& 'inpost_international_create_package' === sanitize_text_field( wp_unslash( $_POST['inpost_intl_action'] ) ) ) {
			return true;
		}

		return false;
	}


	/**
	 * Retrieves the InPost International tracking number from an order's meta data.
	 *
	 * This function attempts to fetch the tracking number associated with an InPost International
	 * package from the order's meta data. It handles both order ID and WC_Order object inputs.
	 *
	 * @since 1.0.0
	 *
	 * @param int|WC_Order $order_id The order ID or WC_Order object.
	 *
	 * @uses wc_get_order() To retrieve the WooCommerce order object if an ID is provided.
	 * @uses WC_Order::get_meta() To fetch the InPost International package meta data.
	 *
	 * @return string The tracking number if found, otherwise an empty string.
	 *
	 * @throws Exception Internally when decoding JSON, but caught and handled.
	 */
	public function get_tracking_number_from_order_meta( $order_id ) {
		$tracking_number = '';

		if ( is_a( $order_id, 'WC_Order' ) ) {
			$order = $order_id;
		} else {
			$order = wc_get_order( $order_id );
		}

		if ( ! $order || is_wp_error( $order ) ) {
			return $tracking_number;
		}

		$package_data = array();
		$package_meta = $order->get_meta( 'inpost_international_package' );
		if ( ! empty( $package_meta ) ) {
			try {
				$package_data = json_decode( $package_meta, true );
				$package_data = ! empty( $package_data['api_response'] ) ? $package_data : array();
			} catch ( Exception $e ) {
				$package_data = array();
			}
		}

		if ( ! empty( $package_data['api_response']['trackingNumber'] ) ) {
			$tracking_number = $package_data['api_response']['trackingNumber'];
		}

		return $tracking_number;
	}



	/**
	 * Retrieves the InPost International package meta data from an order.
	 *
	 * This function fetches and decodes the InPost International package meta data
	 * associated with a given order. It can handle both order ID and WC_Order object inputs.
	 *
	 * @since 1.0.0
	 *
	 * @param int|WC_Order $order_id The order ID or WC_Order object.
	 *
	 * @uses wc_get_order() To retrieve the WooCommerce order object if an ID is provided.
	 * @uses WC_Order::get_meta() To fetch the InPost International package meta data.
	 *
	 * @return array The decoded package meta data as an associative array.
	 *               Returns an empty array if no data is found or if decoding fails.
	 *
	 * @throws Exception Internally when decoding JSON, but caught and handled.
	 */
	public function get_package_meta( $order_id ) {
		$package_data = array();

		if ( is_a( $order_id, 'WC_Order' ) ) {
			$order = $order_id;
		} else {
			$order = wc_get_order( $order_id );
		}

		if ( ! $order || is_wp_error( $order ) ) {
			return $package_data;
		}

		$package_meta = $order->get_meta( 'inpost_international_package' );
		if ( ! empty( $package_meta ) ) {
			try {
				$package_data = json_decode( $package_meta, true );
			} catch ( Exception $e ) {
				$package_data = array();
			}
		}

		return $package_data;
	}


	/**
	 * Retrieves the InPost International tracking url.
	 */
	public function get_tracking_url() {
		return 'https://inpost.pl/sledzenie-przesylek?number=';
	}


	/**
	 * Maps and formats validation errors into a human-readable string.
	 *
	 * This function takes an array of validation errors and converts them into a formatted
	 * string, translating common error messages and beautifying error keys for display.
	 *
	 * @since 1.0.0
	 *
	 * @param array $errors An array of validation errors, where keys are error fields
	 *                      and values are error messages or arrays of messages.
	 *
	 * @uses $this->beautify_error_key() To format error keys for display.
	 * @uses esc_html__() For translating error messages.
	 *
	 * @return string A formatted HTML string containing all error messages, or an empty
	 *                string if no valid errors are provided.
	 */
	public function map_validation_errors( $errors ) {

		$result = '';

		if ( ! is_array( $errors ) ) {
			return $result;
		}

		$possible_problems = array(
			'required',
			'invalid',
			'not found',
			'unknown',
		);

		foreach ( $errors as $key => $value ) {
			$parameter = $this->beautify_error_key( $key );
			$problem   = '';
			if ( is_array( $value ) ) {
				$problem = ! empty( $value[0] ) ? $value[0] : '';
			} else {
				$problem = $value;
			}

			$result .= $parameter;

			if ( in_array( $problem, $possible_problems ) ) {
				$result .= esc_html__( ' is ', 'inpost-international' );
			}

			if ( 'required' === $problem ) {
				$problem = esc_html__( ' required', 'inpost-international' );
			} elseif ( 'invalid' === $problem ) {
				$problem = esc_html__( ' invalid', 'inpost-international' );
			} elseif ( 'not_found' === $problem ) {
				$problem = esc_html__( ' not found', 'inpost-international' );
			} elseif ( 'unknown' === $problem ) {
				$problem = esc_html__( ' unknown', 'inpost-international' );
            } elseif ( 'unavailable' === $problem ) {
                $problem = esc_html__( ' unavailable', 'inpost-international' );
            } elseif ( 'Pickup time has to begin after 9 am.' === $problem ) {
				$problem = esc_html__( 'Pickup time has to begin after 9 am.', 'inpost-international' );
			} elseif ( stripos( $problem, 'minimal time window is 120 minutes' ) ) {
				$problem = esc_html__( 'Provided pickup time spans too short, minimal time window is 120 minutes.', 'inpost-international' );
			} elseif ( 'Pickup time ends less than two hours from now.' === $problem ) {
				$problem = esc_html__( 'Pickup time ends less than two hours from now.', 'inpost-international' );
			} elseif ( 'Pickup time starts in the past.' === $problem ) {
				$problem = esc_html__( 'Pickup time starts in the past.', 'inpost-international' );
			}

			$result .= $problem . '<br>';
		}

		return $result;
	}



	/**
	 * Maps and formats validation errors into a human-readable string.
	 *
	 * @param string $key $key.
	 *
	 * @return string
	 */
	public function beautify_error_key( $key ) {
		$result = $key;

		switch ( $key ) {
			case 'shipment.recipient.phone':
				return esc_html__( 'Recipient phone', 'inpost-international' );

			case 'shipment.sender.phone':
				return esc_html__( 'Sender phone', 'inpost-international' );

			case 'shipment.origin.countryCode':
				return esc_html__( 'Country code of origin', 'inpost-international' );

			case 'shipment.sender.firstName':
				return esc_html__( 'Sender first name', 'inpost-international' );

			case 'shipment.sender.lastName':
				return esc_html__( 'Sender last name', 'inpost-international' );

			case 'shipment.sender.email':
				return esc_html__( 'Sender  email', 'inpost-international' );

			case 'shipment.sender.phone.number':
				return esc_html__( 'Sender phone number', 'inpost-international' );

			case 'shipment.valueAddedServices.insurance.value':
				return esc_html__( 'Insurance amount', 'inpost-international' );

			case 'shipment.origin.shippingMethods':
				return esc_html__( 'Sending method', 'inpost-international' );

			case 'shipment.destination.countryCode':
				return esc_html__( 'Destination country code', 'inpost-international' );

			case 'shipment.origin.address.street':
				return esc_html__( 'Dispatch point street', 'inpost-international' );

			case 'shipment.origin.address.houseNumber':
				return esc_html__( 'Dispatch point house number', 'inpost-international' );

			case 'shipment.origin.address.city':
				return esc_html__( 'Dispatch point city', 'inpost-international' );

			case 'shipment.origin.address.postalCode':
				return esc_html__( 'Dispatch point postal code', 'inpost-international' );

			case 'shipment.parcel.label.barcode':
				return esc_html__( 'Label barcode', 'inpost-international' );

			case 'shipment.origin':
				return esc_html__( 'Shipment origin data', 'inpost-international' );

			case 'shipment.destination.pointName':
				return esc_html__( 'Locker point name', 'inpost-international' );

			case 'shipment.parcel.label.comment':
				return esc_html__( 'Label comment', 'inpost-international' );

			case 'shipment.recipient.phone.prefix':
				return esc_html__( 'Phone country code', 'inpost-international' );

			case 'validation.pickupFromIsAfterNineAm':
				return esc_html__( 'Time from: ', 'inpost-international' );
			case 'validation.tooShortPickupTime':
				return esc_html__( 'Too short time interval: ', 'inpost-international' );

			case 'validation.pickupTimeEndsTooSoon':
				return esc_html__( 'Pickup time ends too soon: ', 'inpost-international' );

			case 'validation.negativeFromTime':
				return esc_html__( 'Day in past chosen: ', 'inpost-international' );

            case 'shipment.valueAddedServices.insurance':
				return esc_html__( 'Insurance amount', 'inpost-international' );

			default:
				return $result;

		}
	}


	/**
	 * Retrieves the origin country code for the shop.
	 *
	 * This function fetches the default country setting from WooCommerce and extracts
	 * the country code. If no valid country code is found, it defaults to 'PL' (Poland).
	 *
	 * @since 1.0.0
	 *
	 * @uses get_option() To retrieve the WooCommerce default country setting.
	 *
	 * @return string A two-letter country code (ISO 3166-1 alpha-2).
	 *                Returns 'PL' if no other valid country code is found.
	 */
	public function get_shop_origin_country() {
		$country_code = 'PL';

		$shop_country = get_option( 'woocommerce_default_country' );
		if ( stripos( $shop_country, ':' ) ) {
			$arr = explode( ':', $shop_country );

			if ( ! empty( $arr[0] ) ) {
				$country_code = $arr[0];
			}
		}

		return $country_code;
	}


	/**
	 * Retrieves the current user's language code.
	 *
	 * This function attempts to get the current user's locale and extract
	 * the two-letter language code from it. If unsuccessful, it defaults to 'pl' (Polish).
	 *
	 * @since 1.0.0
	 *
	 * @uses get_user_locale() To retrieve the current user's locale if the function exists.
	 *
	 * @return string A two-letter language code (ISO 639-1).
	 *                Returns 'pl' if no other valid language code is found.
	 */
	public function get_current_user_language() {
		$lang_code = 'pl';

		if ( function_exists( 'get_user_locale' ) ) {
			$user_language = get_user_locale();

			if ( ! empty( $user_language ) ) {
				$lang_code = substr( $user_language, 0, 2 );
			}
		}

		return $lang_code;
	}


	/**
	 * Generates the URL for the WooCommerce logs section in the admin area.
	 *
	 * This function constructs the URL to the logs tab in the WooCommerce status page.
	 * It checks if WooCommerce is active before attempting to generate the URL.
	 *
	 * @since 1.0.0
	 *
	 * @uses admin_url() To get the base admin URL.
	 * @uses add_query_arg() To append query parameters to the URL.
	 *
	 * @return string|false The URL to the WooCommerce logs section if WooCommerce is active,
	 *                      or false if WooCommerce is not active.
	 */
	public function get_woocommerce_logs_section_url() {
		if ( ! function_exists( 'wc_get_page_permalink' ) ) {
			return false; // WooCommerce is not active.
		}

		$base_url   = admin_url( 'admin.php' );
		$query_args = array(
			'page' => 'wc-status',
			'tab'  => 'logs',
		);

		return add_query_arg( $query_args, $base_url );
	}


	/**
	 * Renders the HTML for the InPost International package status modal.
	 *
	 * This function outputs the HTML structure for a modal dialog that displays
	 * package status information for InPost International shipments. The modal
	 * includes the InPost logo, tracking number display, and a placeholder for
	 * status text.
	 *
	 * @since 1.0.0
	 *
	 * @uses inpost_intl_core()->get_plugin_images_url() To get the URL for plugin images.
	 * @uses esc_attr() To escape the image URL for safe output in an HTML attribute.
	 * @uses esc_html__() To translate and escape text for output.
	 *
	 * @return void Outputs HTML directly.
	 */
	public function package_status_modal() {

		$img = inpost_intl_core()->get_plugin_images_url() . '/yellow.png';
		?>

		<div id="inpost_intl_status_modal" class="inpost_intl_status_modal">
			<div class="modalCover"></div>
			<div class="modalContext">
				<div class="inpost_intl_status_modal_close"></div>
				<div class="inpost_intl_status_modal_text_wrapper">
					<img id="inpost_intl_logo" src="<?php echo esc_attr( $img ); ?>">
				</div>
				<div class="inpost_intl_status_modal_title">
					<h2><?php echo esc_html__( 'Tracking number:', 'inpost-international' ); ?></h2>
					<h3><span class="inpost_intl_status_modal_tracking"></span></h3>
				</div>
				<div class="inpost_intl_status_modal_text">
				</div>
				<div class="inpost_intl_status_modal_action">
					<button id="inpost_intl_status_modal_close_button">Ok</button>
				</div>
			</div>
		</div>

		<?php
	}


	/**
	 * Checks if an InPost International package has already been created for a given WooCommerce order.
	 *
	 * This function retrieves the WooCommerce order by its ID and checks for the presence of
	 * the 'inpost_international_package' meta key. If the meta key exists, it attempts to decode
	 * the associated JSON data to determine whether a package has already been created.
	 * If the order does not exist, is invalid, or an error occurs during processing, the function
	 * returns `false`.
	 *
	 * @param int $order_id The ID of the WooCommerce order to check.
	 *
	 * @return bool|array Returns `false` if no package has been created, the order is invalid,
	 *                    or an error occurs. Otherwise, returns an array containing the package
	 *                    data (specifically the `api_response` field) if a package has already
	 *                    been created.
	 *
	 * @throws Exception If the JSON decoding of the package metadata fails, the exception is caught
	 *                   internally, and the function returns `false`.
	 *
	 * @since 1.0.0
	 */
	public function wc_order_package_created( $order_id ) {
		$package_already_created = false;

		$order = wc_get_order( $order_id );

		if ( ! $order || is_wp_error( $order ) ) {
			return false;
		}

		$package_meta = $order->get_meta( 'inpost_international_package' );
		if ( ! empty( $package_meta ) ) {
			try {
				$package_data            = json_decode( $package_meta, true );
				$package_already_created = ! empty( $package_data['api_response'] ) ? $package_data : false;
			} catch ( Exception $e ) {
				return false;
			}
		}

		return $package_already_created;
	}


	/**
	 * Retrieves the available template options for InPost International settings.
	 *
	 * This function generates an array of template options for use in dropdowns or other UI elements.
	 * It starts with a default option labeled "From product settings" and dynamically adds additional
	 * options based on the `templates` data stored in the `inpost_international_settings_sending`
	 * option. Each valid template name from the stored data is added to the options array.
	 *
	 * @return array An associative array of template options where:
	 *               - The key is the template name.
	 *               - The value is the display name of the template.
	 *               If no templates are found, the array will only contain the default option.
	 *
	 * @since 1.0.0
	 */
	public function get_template_options() {

		$options['default'] = esc_html__( 'From product settings', 'inpost-international' );

		$sending_options = get_option( 'inpost_international_settings_sending' );

		if ( ! empty( $sending_options['templates'] ) ) {
			foreach ( $sending_options['templates'] as $key => $template ) {
				if ( ! empty( $template['name'] ) ) {
					$options[ $template['name'] ] = $template['name'];
				}
			}
		}

		return $options;
	}


	/**
	 * Retrieves the biggest template from a list of template names based on weight and volume.
	 *
	 * This function determines the "biggest" template from the provided list of template names
	 * by comparing their weight and volume. The process involves:
	 * 1. Filtering templates that match the provided names.
	 * 2. Sorting templates by weight in descending order.
	 * 3. If multiple templates have the same maximum weight, they are further sorted by volume
	 *    (calculated as length × width × height) in descending order.
	 * 4. The name of the largest template is returned. If no valid templates are found, a default
	 *    value ('default') is returned.
	 *
	 * @param array $template_names An array of template names to evaluate.
	 *
	 * @return string The name of the biggest template based on weight and volume. If no valid
	 *                templates are found or if input is invalid, the function returns 'default'.
	 *
	 * @since 1.0.0
	 */
	public function get_biggest_template( $template_names ) {

		$default = 'default';

		if ( is_array( $template_names ) && 1 === count( $template_names ) ) {
			return reset( $template_names );
		}

		$sending_options = get_option( 'inpost_international_settings_sending' );
		if ( empty( $sending_options['templates'] ) ) {
			return $default;
		}

		$default_keys            = array();
		$product_dimensions_temp = array();

		foreach ( $template_names as $key => $value ) {
			if ( 'default' === $value ) {
				$default_keys[] = $key;
			}
		}

		$matching_templates = array_filter(
			$sending_options['templates'],
			function ( $template ) use ( $template_names ) {
				return in_array( $template['name'], $template_names, true );
			}
		);

		if ( empty( $matching_templates ) ) {
			return $default;
		}

		usort(
			$matching_templates,
			function ( $a, $b ) {
				return $b['weight'] <=> $a['weight'];
			}
		);

		$max_weight = $matching_templates[0]['weight'];

		$max_weight_templates = array_filter(
			$matching_templates,
			function ( $template ) use ( $max_weight ) {
				return $template['weight'] == $max_weight;
			}
		);

		if ( count( $max_weight_templates ) === 1 ) {
			return reset( $max_weight_templates )['name'];
		}

		usort(
			$max_weight_templates,
			function ( $a, $b ) {

				$volume_a = $a['length'] * $a['width'] * $a['height'];
				$volume_b = $b['length'] * $b['width'] * $b['height'];

				return $volume_b <=> $volume_a;
			}
		);

		return reset( $max_weight_templates )['name'];
	}



	/**
	 * Retrieves the saved parcel template for a WooCommerce order based on product metadata.
	 *
	 * This function determines the appropriate parcel template for a given WooCommerce order
	 * by inspecting the metadata of the products in the order. The process involves:
	 * 1. Checking if the order contains a single product. If so, the template is retrieved
	 *    directly from the product's metadata.
	 * 2. If the order contains multiple products, the function retrieves the templates for
	 *    all products and determines the "biggest" template using the `get_biggest_template` method.
	 * 3. If no valid template is found or the order is invalid, the function defaults to 'default'.
	 *
	 * @param int $order_id The ID of the WooCommerce order to process.
	 *
	 * @return string The name of the saved parcel template. If no valid template is found
	 *                or the order is invalid, the function returns 'default'.
	 *
	 * @since 1.0.0
	 */
	public function get_saved_template_by_product_meta( $order_id ) {

		$template = 'default';

		$order = wc_get_order( $order_id );

		if ( ! $order || is_wp_error( $order ) ) {
			return $template;
		}

		$items = $order->get_items();

		if ( is_array( $items ) ) {
			if ( count( $items ) === 1 ) {
				$product            = reset( $items );
				$template_from_meta = get_post_meta( $product->get_product_id(), 'inpost_international_parcel_template', true );
				$template           = ! empty( $template_from_meta ) ? $template_from_meta : 'default';
			} else {
				$templates = array();
				foreach ( $items as $item ) {
					$id   = $item->get_product_id();
					$temp = get_post_meta( $id, 'inpost_international_parcel_template', true );
					if ( ! empty( $temp ) ) {
						$templates[ $id ] = $temp;
					}
				}

				$template = $this->get_biggest_template( $templates );
			}
		}

		return $template;
	}


	/**
	 * Get dispatch points.
	 *
	 * @return array
	 */
	public function get_dispatch_points() {
		$dpoints = array();

		$dpoints_settings = get_option( 'inpost_international_settings_dispatch_points' );

		if ( ! empty( $dpoints_settings['points'] ) && is_array( $dpoints_settings['points'] ) ) {
			$dpoints = $dpoints_settings['points'];
		}

		return $dpoints;
	}


	/**
	 * Retrieves the selected dispatch point details based on the provided index.
	 *
	 * This function fetches the dispatch point data for a given index from the list of
	 * available dispatch points. It ensures that all required fields are populated with
	 * default values if the corresponding data is missing. The returned array includes
	 * fields such as country code, city, postal code, street, house number, flat number,
	 * and location description.
	 *
	 * @param int $index The index of the dispatch point to retrieve from the list of
	 *                   available dispatch points.
	 *
	 * @return array An associative array containing the details of the selected dispatch point.
	 *               If no data is available for the specified index, default values are used:
	 *               - 'countryCode' defaults to 'PL'.
	 *               - All other fields default to empty strings.
	 *
	 * @since 1.0.0
	 */
	public function get_dispatch_point_selected( $index ) {

		$country_code   = '';
		$sender_options = get_option( 'inpost_international_settings_merchant' );
		if ( ! empty( $sender_options['country'] ) ) {
			$country_code = $sender_options['country'];
		} else {
			$country_code = 'PL';
		}

		$selected_dpoint = array(
			'countryCode'         => $country_code,
			'city'                => '',
			'postalCode'          => '',
			'street'              => '',
			'houseNumber'         => '',
			'flatNumber'          => '',
			'locationDescription' => '',
		);

		$point   = array();
		$dpoints = $this->get_dispatch_points();

		if ( ! empty( $dpoints[ $index ] ) ) {
			$point = $dpoints[ $index ];
		} else {
			$point = ! empty( $dpoints[0] ) ? $dpoints[0] : array();
		}

		$selected_dpoint['city']        = ! empty( $point['city'] ) ? $point['city'] : '';
		$selected_dpoint['postalCode']  = ! empty( $point['postal_code'] ) ? $point['postal_code'] : '';
		$selected_dpoint['street']      = ! empty( $point['street'] ) ? $point['street'] : '';
		$selected_dpoint['houseNumber'] = ! empty( $point['building_number'] ) ? $point['building_number'] : '';
		$selected_dpoint['flatNumber']  = ! empty( $point['flat_number'] ) ? $point['flat_number'] : '';

		return $selected_dpoint;
	}


	/**
	 * Returns the international phone code for a given country.
	 *
	 * This function takes a two-letter country code and returns the corresponding
	 * international phone dialing code. It supports all countries where InPost
	 * International shipping services are available.
	 *
	 * @since 1.0.0
	 *
	 * @param string $country_code A two-letter ISO country code (e.g., 'PL' for Poland).
	 *
	 * @return string The international phone dialing code without the plus sign (e.g., '48' for Poland),
	 *                or an empty string if the country code is not supported.
	 */
	public function get_phone_code( $country_code ) {
		switch ( strtoupper( $country_code ) ) {
			case 'PL':
				return '48';
			case 'BE':
				return '32';
			case 'ES':
				return '34';
			case 'FR':
				return '33';
			case 'IT':
				return '39';
			case 'LU':
				return '352';
			case 'NL':
				return '31';
			case 'PT':
				return '351';
			case 'AT':
				return '43';
			case 'HU':
				return '36';
			default:
				return '';
		}
	}



	/**
	 * Validates and formats a receiver's phone number.
	 *
	 * This function processes a phone number to ensure it's in the correct format
	 * for the InPost API. It removes whitespace, handles numbers with international
	 * prefixes (starting with '+'), and removes country code prefixes when they match
	 * the provided phone prefix.
	 *
	 * @since 1.0.0
	 *
	 * @param string $phone_prefix The country's phone prefix (e.g., '48' for Poland).
	 * @param string $phone_number The receiver's phone number to validate and format.
	 *
	 * @return string The formatted phone number without country code or '+' prefix.
	 */
	public function validate_receiver_phone( $phone_prefix, $phone_number ) {

		// Remove any whitespace from the phone number.
		$phone_number = preg_replace( '/\s+/', '', $phone_number );

		// Check if the phone number starts with '+'.
		if ( 0 === strpos( $phone_number, '+' ) ) {
			// Remove '+' and the next two digits.
			$phone_number = substr( $phone_number, 3 );
		}
		// Check if the phone number length is greater than 10.
		elseif ( strlen( $phone_number ) > 10 ) {
			// Get the first two digits.
			$first_two_digits = substr( $phone_number, 0, 2 );

			// Compare with phone_prefix.
			if ( $first_two_digits === $phone_prefix ) {
				// Remove the first two digits.
				$phone_number = substr( $phone_number, 2 );
			}
		}

		return $phone_number;
	}



	/**
	 * Returns the locale/language code for a given country.
	 *
	 * This function takes a two-letter country code and returns the corresponding
	 * locale code in the format of language_COUNTRY. This is used for localization
	 * purposes within the InPost International plugin.
	 *
	 * @since 1.0.0
	 *
	 * @param string $country_code A two-letter ISO country code (e.g., 'BE' for Belgium).
	 *
	 * @return string The locale code in language_COUNTRY format (e.g., 'nl_BE' for Belgium),
	 *                defaults to 'pl_PL' if the country code is not specifically supported.
	 */
	public function get_language_code( $country_code ) {
		switch ( strtoupper( $country_code ) ) {
			case 'BE':
				return 'nl_BE';
			case 'ES':
				return 'es_ES';
			case 'FR':
				return 'fr_FR';
			case 'IT':
				return 'it_IT';
			case 'LU':
				return 'fr_LU';
			case 'NL':
				return 'nl_NL';
			case 'AT':
				return 'de_AT';
			case 'HU':
				return 'hu_HU';
			default:
				return 'pl_PL';
		}
	}


	/**
	 * Check if buyer's phone is eligible
	 *
	 * @param string $phone_number $phone_number.
	 *
	 * @return boolean
	 */
	public function is_phone_number_valid( $phone_number ): bool {

		$clean_phone = preg_replace( '/[\s\-]/', '', $phone_number );

		// Country validation rules: [regex_pattern, min_digits, max_digits].
		$country_rules_full = array(
			'PL' => array( '/^\(\?((\+)|00)48\)?/', 9, 9 ),
			'FR' => array( '/^\(\?((\+)|00)33\)?/', 9, 9 ),
			'ES' => array( '/^\(\?((\+)|00)34\)?/', 9, 9 ),
			'BE' => array( '/^\(\?((\+)|00)32\)?/', 8, 9 ),
			// 'DE' => ['/^\(\?((\+)|00)49\)?/', 10, 11],
			'LU' => array( '/^\(\?((\+)|00)352\)?/', 9, 9 ),
			'PT' => array( '/^\(\?((\+)|00)351\)?/', 9, 9 ),
			'AT' => ['/^\(\?((\+)|00)43\)?/', 7, 13],
			// 'GB' => ['/^\(\?((\+)|00)44\)?/', 10, 10],
			'IT' => array( '/^\(\?((\+)|00)39\)?/', 9, 10 ),
			'NL' => array( '/^\(\?((\+)|00)31\)?/', 9, 10 ),
			// 'IE' => ['/^\(\?((\+)|00)353\)?/', 10, 10],
			// 'MC' => ['/^\(\?((\+)|00)377\)?/', 8, 8],
			// 'CH' => ['/^\(\?((\+)|00)41\)?/', 9, 9],
			// 'UA' => ['/^\(\?((\+)|00)380\)?/', 9, 9],
			// 'BY' => ['/^\(\?((\+)|00)375\)?/', 9, 9],
			// 'BG' => ['/^\(\?((\+)|00)359\)?/', 8, 9],
			// 'CY' => ['/^\(\?((\+)|00)357\)?/', 8, 8],
			// 'CZ' => ['/^\(\?((\+)|00)420\)?/', 9, 9],
			// 'DK' => ['/^\(\?((\+)|00)45\)?/', 8, 8],
			// 'EE' => ['/^\(\?((\+)|00)372\)?/', 7, 8],
			// 'FI' => ['/^\(\?((\+)|00)358\)?/', 8, 10],
			// 'GR' => ['/^\(\?((\+)|00)30\)?/', 10, 10],
			'HU' => ['/^\(\?((\+)|00)36\)?/', 9, 9],
			// 'LT' => ['/^\(\?((\+)|00)370\)?/', 8, 8],
			// 'LV' => ['/^\(\?((\+)|00)371\)?/', 8, 8],
			// 'MT' => ['/^\(\?((\+)|00)356\)?/', 8, 8],
			// 'SE' => ['/^\(\?((\+)|00)46\)?/', 9, 9],
			// 'SK' => ['/^\(\?((\+)|00)421\)?/', 9, 9]
		);

		$country_rules = array(
			'PL' => array( '48', 9, 9 ),
			'FR' => array( '33', 9, 9 ),
			'ES' => array( '34', 9, 9 ),
			'BE' => array( '32', 8, 9 ),
			// 'DE' => ['49', 10, 11],
			'LU' => array( '352', 9, 9 ),
			'PT' => array( '351', 9, 9 ),
			'AT' => ['43', 7, 13],
			// 'GB' => ['44', 10, 10],
			'IT' => array( '39', 9, 10 ),
			'NL' => array( '31', 9, 10 ),
			// 'IE' => ['353', 10, 10],
			// 'MC' => ['377', 8, 8],
			// 'CH' => ['41', 9, 9],
			// 'UA' => ['380', 9, 9],
			// 'BY' => ['375', 9, 9],
			// 'BG' => ['359', 8, 9],
			// 'CY' => ['357', 8, 8],
			// 'CZ' => ['420', 9, 9],
			// 'DK' => ['45', 8, 8],
			// 'EE' => ['372', 7, 8],
			// 'FI' => ['358', 8, 10],
			// 'GR' => ['30', 10, 10],
			'HU' => ['36', 9, 9],
			// 'LT' => ['370', 8, 8],
			// 'LV' => ['371', 8, 8],
			// 'MT' => ['356', 8, 8],
			// 'SE' => ['46', 9, 9],
			// 'SK' => ['421', 9, 9]
		);

		foreach ( $country_rules as $country => $rule ) {
			list($country_code, $min_digits, $max_digits) = $rule;

			if ( preg_match( '/^(\+?' . preg_quote( $country_code, '/' ) . ')(\d+)$/', $clean_phone, $matches ) ) {

				if ( strpos( $clean_phone, '00' ) === 0 ) {
					continue;
				}

				$number_part = $matches[2];

				$digit_count = strlen( $number_part );
				if ( $digit_count >= $min_digits && $digit_count <= $max_digits ) {
					return true;
				}
			}
		}

		return false;
	}



	/**
	 * Extract phone prefix and number without prefix from valid phone number
	 *
	 * @param string $phone_number $phone_number.
	 * @return array|false Returns array with phone_prefix and just_phone_without_prefix or false if invalid
	 */
	public function get_phone_prefix_from_phone_number( $phone_number ) {

		// Remove all spaces and dashes for validation.
		$clean_phone = preg_replace( '/[\s\-]/', '', $phone_number );

		// Only allowed countries.
		$allowed_countries = array(
			'PL' => array( '48', 9, 9 ),
			'FR' => array( '33', 9, 9 ),
			'ES' => array( '34', 9, 9 ),
			'BE' => array( '32', 8, 9 ),
			'LU' => array( '352', 9, 9 ),
			'PT' => array( '351', 9, 9 ),
			'IT' => array( '39', 9, 10 ),
			'NL' => array( '31', 9, 10 ),
			'AT' => array( '43', 7, 13 ),
			'HU' => array( '36', 9, 9 ),
		);

		foreach ( $allowed_countries as $country => $rule ) {
			list($country_code, $min_digits, $max_digits) = $rule;

			// Check if phone starts with + followed by country code OR just country code (no 00 prefix allowed).
			if ( preg_match( '/^(\+?' . preg_quote( $country_code, '/' ) . ')(\d+)$/', $clean_phone, $matches ) ) {
				// Make sure it doesn't start with 00.
				if ( strpos( $clean_phone, '00' ) === 0 ) {
					continue; // Skip if starts with 00.
				}

				// Extract the prefix and number part.
				$phone_prefix = $matches[1];
				$number_part  = $matches[2];

				// Check if digit count is within valid range.
				$digit_count = strlen( $number_part );
				if ( $digit_count >= $min_digits && $digit_count <= $max_digits ) {

					if ( 0 === strpos( $phone_prefix, '+' ) ) {
						// Remove '+'.
						$phone_prefix = substr( $phone_prefix, 1 );
					}

					return array(
						'prefix' => $phone_prefix,
						'phone'  => $number_part,
					);
				}
			}
		}

		return false;
	}



	/**
	 * Get localized error message for phone number validation based on country.
	 *
	 * Returns formatted error message in the appropriate language explaining phone number
	 * format requirements and supported countries. Supports localization for PL, BE, ES,
	 * FR, IT, LU, NL, PT with English as fallback.
	 *
	 * @param string $country_of_delivery Two-letter country code (e.g. 'PL', 'BE', 'ES').
	 * @return string Formatted HTML error message in appropriate language
	 */
	public function get_locale_error_for_phone_number( $country_of_delivery ) {
		switch ( $country_of_delivery ) {
			case 'PL':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Proszę wpisać numer telefonu w formacie kod kraju i numer telefonu.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Obsługiwane numery telefonów komórkowych dla następujących krajów: Polska, Włochy, Hiszpania, Portugalia, Francja, Holandia, Luksemburg, Belgia, Austria, Węgry.', 'inpost-international' );

			case 'BE':
			case 'FR':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Veuillez saisir le numéro de téléphone au format indicatif pays et numéro de téléphone.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Numéros de téléphone mobile pris en charge pour les pays Pologne, Italie, Espagne, Portugal, France, Pays-Bas, Luxembourg, Belgique, Autriche, Hongrie.', 'inpost-international' );

			case 'ES':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Ingrese el número de teléfono con el formato de código de país y número de teléfono.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Números de teléfono móviles admitidos para los países Polonia, Italia, España, Portugal, Francia, Países Bajos, Luxemburgo, Bélgica, Austria y Hungría.', 'inpost-international' );

			case 'IT':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Inserisci il numero di telefono nel formato prefisso internazionale e numero di telefono.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Numeri di cellulare supportati per i seguenti paesi: Polonia, Italia, Spagna, Portogallo, Francia, Paesi Bassi, Lussemburgo, Belgio, Austria, Ungheria.', 'inpost-international' );

			case 'LU':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Bitte geben Sie die Telefonnummer im Format „Landesvorwahl und Telefonnummer" ein.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Unterstützte Mobiltelefonnummern für die Länder Polen, Italien, Spanien, Portugal, Frankreich, Niederlande, Luxemburg, Belgien, Österreich, Ungarn.', 'inpost-international' );

			case 'NL':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Voer het telefoonnummer in de notatie landcode en telefoonnummer in.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Ondersteunde mobiele telefoonnummers voor landen: Polen, Italië, Spanje, Portugal, Frankrijk, Nederland, Luxemburg, België, Oostenrijk en Hongarije.', 'inpost-international' );

			case 'PT':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Introduza o número de telefone no formato indicativo do país e número de telefone.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Números de telemóvel suportados para os seguintes países: Polónia, Itália, Espanha, Portugal, França, Países Baixos, Luxemburgo, Bélgica, Áustria e Hungria.', 'inpost-international' );
						
			case 'AT':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Geben Sie die Telefonnummer im Landesvorwahl- und Telefonnummernformat ein.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Unterstützte Mobiltelefonnummern für die Länder Polen, Italien, Spanien, Portugal, Frankreich, Niederlande, Luxemburg, Belgien, Österreich, Ungarn.', 'inpost-international' );
						
			case 'HU':
				return 'InPost International:' . '<br>' .
						esc_html__( 'Kérjük, a telefonszámot országkód és telefonszám formátumban adja meg.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Támogatott mobiltelefonszámok a következő országokból: Lengyelország, Olaszország, Spanyolország, Portugália, Franciaország, Hollandia, Luxemburg, Belgium, Ausztria, Magyarország.', 'inpost-international' );

			default:
				return 'InPost International:' . '<br>' .
						esc_html__( 'Please enter the phone number in the format country code and phone number.', 'inpost-international' ) . '<br>' .
						esc_html__( 'Supported mobile phone numbers for countries Poland, Italy, Spain, Portugal, France, Netherlands, Luxembourg, Belgium, Austria, Hungary.', 'inpost-international' );
		}
	}


	/**
	 * Validates and corrects shipping method options for InPost international courier.
	 *
	 * Converts incompatible delivery options to supported alternatives when using
	 * InPost international courier shipping method.
	 *
	 * @param string $shipping_method The shipping method identifier.
	 * @param string $option The delivery option to validate.
	 * @return string The validated or corrected delivery option.
	 */
	public function validate_send_method_option( $shipping_method, $option ) {

		if ( 'inpost_international_courier' === $shipping_method ) {
			if ( 'address-to-point' === $option ) {
				$option = 'address-to-address';
			}
			if ( 'point-to-point' === $option ) {
				$option = 'point-to-address';
			}
		}

		return $option;
	}


	/**
	 * Gets package dimensions by template name from InPost settings.
	 *
	 * Searches through configured sending templates to find matching template
	 * by name and returns the associated length, width, height, and weight
	 * dimensions for package configuration.
	 *
	 * @param string $product_template The template name to search for.
	 * @return array Array with dimension values or empty array if not found.
	 */
	public function get_dimensions_by_template_name( $product_template ) {
		$dimensions = array();

		$sending_options = get_option( 'inpost_international_settings_sending' );
		if ( ! empty( $sending_options['templates'] ) && is_array( $sending_options['templates'] ) ) {
			foreach ( $sending_options['templates'] as $key => $template ) {
				if ( ! empty( $template['name'] ) ) {
					if ( $product_template === $template['name'] ) {
						$dimensions['length'] = $template['length'];
						$dimensions['width']  = $template['width'];
						$dimensions['height'] = $template['height'];
						$dimensions['weight'] = $template['weight'];
					}
				}
			}
		}

		return $dimensions;
	}


	/**
	 * Gets dimensions using templates or calculates from multiple products.
	 *
	 * Checks if products have dimension templates, selects the biggest template
	 * for multiple products, validates template existence, and falls back to
	 * calculating dimensions from product data when templates are unavailable.
	 *
	 * @param array $items Array of WC_Order_Item_Product objects.
	 * @return array Array with calculated or template-based dimensions.
	 */
	public function get_or_biggest_template_or_calculate_multiple_products_dimensions( $items ) {
		$dimensions = array();

		$templates = array();

		$is_product_has_dimension_template = false;

		foreach ( $items as $wc_order_item ) {
			$product_data = $wc_order_item->get_data();
			$product_id   = ! empty( $product_data['product_id'] ) ? $product_data['product_id'] : null;
			if ( $product_id ) {
				$product_template = get_post_meta( $product_id, 'inpost_international_parcel_template', true );

				if ( ! empty( $product_template ) && 'default' !== $product_template ) {
					$is_product_has_dimension_template = true;
					$templates[ $product_id ]          = $product_template;
				}
			}
		}

		if ( $is_product_has_dimension_template ) {

			if ( count( $templates ) > 1 ) {
				$biggest_template = $this->get_biggest_template( $templates );
			} else {
				$biggest_template = reset( $templates );
			}

			$is_template_exists = $this->check_is_template_exists( $biggest_template );

			if ( $is_template_exists ) {

				$dimensions = $this->get_dimensions_by_template_name( $biggest_template );

			} else {
				$dimensions = $this->calculate_multiple_products_dimensions( $items );
			}
		} else {
			$dimensions = $this->calculate_multiple_products_dimensions( $items );
		}

		return $dimensions;
	}



	/**
	 * Checks if a template exists in InPost sending settings.
	 *
	 * Searches through configured sending templates to verify if a template
	 * with the specified name exists in the settings configuration.
	 *
	 * @param string $template_name The template name to check for existence.
	 * @return bool True if template exists, false otherwise.
	 */
	public function check_is_template_exists( $template_name ) {

		$sending_options = get_option( 'inpost_international_settings_sending' );

		if ( ! empty( $sending_options['templates'] ) ) {
			foreach ( $sending_options['templates'] as $template ) {
				if ( ! empty( $template['name'] ) && $template_name === $template['name'] ) {
					return true;
				}
			}
		}

		return false;
	}


    /**
     * Retrieves the delivery terms for a specific shipping method.
     *
     * @param string $shipping_method_id The shipping method ID.
     * @param int $shipping_method_instance_id The shipping method instance ID.
     * @return string|bool The delivery terms text or false if not set.
     *
     * @since 1.0.7
     * @access public
     */
    public function get_shipping_method_delivery_terms( $shipping_method_id, $shipping_method_instance_id ) {
        $shipping_method_settings_name = 'woocommerce_' . $shipping_method_id . '_' . $shipping_method_instance_id . '_settings';
        $shipping_method_settings      = get_option( $shipping_method_settings_name );

        $delivery_terms = false;
        if ( ! empty( $shipping_method_settings['delivery_terms'] ) ) {
            $delivery_terms = esc_html( trim( $shipping_method_settings['delivery_terms'] ) );
        }

        return $delivery_terms;
    }
}
