<?php

declare(strict_types=1);

namespace Oktagon\WooCommerce\XConnect;

/**
 * @SuppressWarnings(PHPMD.CamelCasePropertyName)
 */
class ShippingMethod extends \WC_Shipping_Method
{
    public $cost = 0.0;

    public $description = '';

    public $enabled = 'yes';

    public $fee_cost = 0.0;

    public $id = Meta::METHOD_ID;

    public $instance_id = 0;

    public $instance_form_fields = [];

    public $method_title = 'X-Connect';

    public $method_description = 'Shipping method with connection to a shipping partner.';

    public $supports = [
        'instance-settings',
        'shipping-zones',
    ];

    public $tax_status = 'taxable';

    public $title;

    private $rateMetaData = [];

    /**
     * phpcs:disable Generic.Files.LineLength.TooLong
     */
    public function __construct(
        int $instanceId = 0,
        ?array $settings = null
    ) {
        $this->method_description = esc_html__(
            'Shipping method with connection to a shipping partner.',
            'oktagon-x-connect-for-woocommerce'
        );
        $this->instance_id = absint($instanceId);
        $this->instance_form_fields = [
            'title' => [
                'title' => esc_html__('Title', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'text',
                'description' => esc_html__(
                    'Will be displayed to customer in checkout.',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'default' => (string) esc_html__('X-Connect Shipping Method', 'oktagon-x-connect-for-woocommerce'),
                'desc_tip' => false,
            ],
            'description' => [
                'title' => esc_html__('Description', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'text',
                'description' => esc_html__(
                    'Will be displayed to customer in checkout.',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'default' => '',
                'desc_tip' => false,
            ],
            'cost' => [
                'description' =>
                    esc_html__(
                        'Enter a cost (excl. tax) or sum, e.g. '
                        . '10.00 * [qty].',
                        'oktagon-x-connect-for-woocommerce'
                    )
                    . '<br/><br/>'
                    . esc_html__(
                        'Use [qty] for the number of items, '
                        . '<code>[cost]</code> for the total cost of items, '
                        . 'and [fee percent="10" min_fee="20" max_fee=""] '
                        . 'for percentage based fees.',
                        'oktagon-x-connect-for-woocommerce'
                    ),
                'desc_tip' => true,
                'title' => esc_html__('Cost', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'text',
            ],
            'tax_status' => [
                'title' => esc_html__('Tax status', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'select',
                'class' => 'wc-enhanced-select',
                'default' => 'taxable',
                'options' => [
                    'taxable' => esc_html__('Taxable', 'oktagon-x-connect-for-woocommerce'),
                    'none' => _x('None', 'Tax status', 'oktagon-x-connect-for-woocommerce'),
                ],
            ],
            'shipping_service' => [
                'default' => '',
                'title' => esc_html__('Shipping Service', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'hidden',
            ],
            'shipping_service_options' => [
                'default' => '',
                'title' => esc_html__('Additional options', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'hidden',
            ],
            'enabled_pick_up_point_selection' => [
                'default' => '',
                'title' => esc_html__('Enable service point selection', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'hidden',
            ],
            'logo' => [
                'description' => esc_html__(
                    'If you leave this field empty the default logotype below will be used.',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'default' => '',
                'title' => esc_html__('Custom URL to logotype', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'text',
            ],
            'predefined_logo' => [
                'class' => 'wc-enhanced-select',
                'description' => esc_html__(
                    'Select a logotype that is built-in in the plugin.',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'default' => '',
                'options' => [
                    '' => esc_html__('None', 'oktagon-x-connect-for-woocommerce'),
                    'airmee.svg' => 'Airmee',
                    'bring.jpg' => 'Bring',
                    'budbee.svg' => 'Budbee',
                    'dbschenker.jpg' => 'DBSchenker',
                    'dhl.svg' => 'DHL',
                    'dpd.jpg' => 'DPD',
                    'earlybird.jpg' => 'Early Bird',
                    'fedex.jpg' => 'FedEx',
                    'fiuge.jpg' => 'Fiuge',
                    'gls.jpg' => 'GLS',
                    'instabox.png' => 'Instabox',
                    'itella.jpg' => 'Itella',
                    'jakeluyhtio-suomi.png' => 'Jakeluyhtiö Suomi',
                    'jetpak.jpg' => 'Jetpak',
                    'matkahuolto.jpg' => 'Matkahuolto',
                    'netlux.jpg' => 'Netlux',
                    'omniva.jpg' => 'Omniva',
                    'posti.jpg' => 'Posti',
                    'postnord.svg' => 'PostNord',
                    'ups.jpg' => 'UPS',
                    'wolt.jpg' => 'Wolt',
                ],
                'title' => esc_html__('Default Logotype', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'select',
            ],
            'carrier' => [
                'default' => '',
                'title' => esc_html__('Carrier', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'hidden',
            ],
            'logic' => [
                'default' => '',
                'title' => esc_html__('Logic', 'oktagon-x-connect-for-woocommerce'),
                'type' => 'hidden',
            ],
            'logic_presentation' => [
                'description' => esc_html__(
                    'Use this to limit when a shipping method is available. Use $weight for cart weight and $subtotal for cart subtotal before discounts and $subtotalPostDiscounts for cart subtotal after discounts. Use $productCount for count of products in cart and $productQuantitySum for the total sum of product quantities. Allowed functions are: anyProductInFreightClass(\'bulky\'), allProductsInFreightClass(\'glass\'). Allowed operators are: &lt;, &lt;=, &gt;, &gt;=, =, &lt;&gt;, !=, !, &amp;, |, (, ). Example weight above 200: $weight &gt; 200. Example weight above or equal 100 or subtotal above 500: $weight &gt;= 100 | $subtotal &gt; 500. Example weight and subtotal after discounts is below 100: $weight &lt; 100 &amp; $subtotalPostDiscounts &lt; 100. Example any product in bulky freight class slug: anyProductInFreightClass(\'bulky\')',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'title' => esc_html__(
                    'Logic',
                    'oktagon-x-connect-for-woocommerce'
                ),
                'type' => 'textarea',
            ],
        ];
        parent::init_form_fields();
        parent::init_settings();

        $this->instance_settings = Meta::getInstanceOptions($this->instance_id);
        $formFields = $this->get_instance_form_fields();
        foreach ($formFields as $formFieldKey => $formFieldValue) {
            if (!isset($this->instance_settings[$formFieldKey])) {
                $this->instance_settings[$formFieldKey] =
                    $formFieldValue['default'] ?? '';
            }
        }

        $this->cost =
            $this->instance_settings['cost'];
        $this->title =
            $this->instance_settings['title'];
        $this->tax_status =
            $this->instance_settings['tax_status'];
        if (
            !empty($settings)
            && is_array($settings)
        ) {
            $this->enabled = $settings['enabled'];
        }
    }

    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
    /**
     * @param array $package
     * @return bool
     * @since Woocommerce 3.0.0
     * @SuppressWarnings(PHPMD.CamelCaseMethodName)
     */
    public function is_available($package)
    {
        Meta::log(
            sprintf(
                esc_html__(
                    '%s args: %s',
                    'oktagon-x-connect-for-woocommerce'
                ),
                __METHOD__,
                var_export(func_get_args(), true)
            )
        );
        $available = false;
        if ($this->enabled === 'yes') {
            $available = true;
        }

        $logic = $this->instance_settings['logic'];
        if (
            !empty($logic)
            && $available
        ) {
            $logic = str_replace('&amp;', '&', $logic);
            $logic = str_replace(
                [
                    '&gt;',
                    '&lt;',
                    '&amp;',
                ],
                [
                    '>',
                    '<',
                    '&',
                ],
                $logic
            );

            // Calculate arguments
            $freightClasses = [];
            $productCount = 0;
            $productQuantitySum = 0;
            $weight = 0.0;
            $subtotal = 0.0;
            $subtotalPostDiscounts = 0.0;
            if (!empty($package['contents'])) {
                foreach ($package['contents'] as $item) {
                    if (isset($item['line_subtotal'])) {
                        $subtotal += (float) $item['line_subtotal'];
                    }
                    if (isset($item['line_subtotal_tax'])) {
                        $subtotal += (float) $item['line_subtotal_tax'];
                    }
                    if (!empty($item['line_total'])) {
                        $subtotalPostDiscounts += (float) $item['line_total'];
                    }
                    if (!empty($item['line_tax'])) {
                        $subtotalPostDiscounts += (float) $item['line_tax'];
                    }
                    $weight +=
                        (float) $item['data']->get_weight() * (int) $item['quantity'];
                    $productCount++;
                    $productQuantitySum += (int) $item['quantity'];
                    $productShippingClassSlug =
                        $item['data']->get_shipping_class();
                    if (!empty($productShippingClassSlug)) {
                        if (!isset($freightClasses[$productShippingClassSlug])) {
                            $freightClasses[$productShippingClassSlug] = 0;
                        }
                        $freightClasses[$productShippingClassSlug]++;
                    }
                }
            }

            Meta::log(
                sprintf(
                    esc_html__(
                        'Cart logic args: %s',
                        'oktagon-x-connect-for-woocommerce'
                    ),
                    var_export(
                        [
                            'subtotal' => $subtotal,
                            'subtotalPostDiscounts' => $subtotalPostDiscounts,
                            'weight' => $weight,
                        ],
                        true
                    )
                )
            );

            $parser = new Parser(new Lexer());
            $available = $parser->evaluate(
                $logic,
                [
                    'productCount' => $productCount,
                    'productQuantitySum' => $productQuantitySum,
                    'subtotal' => $subtotal,
                    'subtotalPostDiscounts' => $subtotalPostDiscounts,
                    'weight' => $weight,
                ],
                [
                    'allProductsInFreightClass' => function ($freightClassSlug) use ($freightClasses, $productCount): bool {
                        return !empty($freightClasses[$freightClassSlug])
                            && $freightClasses[$freightClassSlug] === $productCount;
                    },
                    'anyProductInFreightClass' => function ($freightClassSlug) use ($freightClasses): bool {
                        return !empty($freightClasses[$freightClassSlug]);
                    },
                ],
                [
                    'allProductsInFreightClass' => 1,
                    'anyProductInFreightClass' => 1,
                ]
            );
        }

        $available = (bool) apply_filters(
            'woocommerce_shipping_' . $this->id . '_is_available',
            $available,
            $package
        );

        return $available;
    }
    // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps

    // phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
    /**
     * Based on WooCommerce Flat Rate shipping method.
     * @param array $package
     * @return void
     * @see \WC_Shipping_Flat_Rate->calculate_shipping()
     * @since WooCommerce 3.0.0, Wordpress 3.1.0
     * @see $package defined in \WC_Cart->get_shipping_packages()
     * @SuppressWarnings(PHPMD.CamelCaseMethodName)
     * @suppress PhanTypeArraySuspiciousNullable
     */
    public function calculate_shipping(
        $package = []
    ) {
        Meta::log(
            sprintf(
                esc_html__(
                    '%s args: %s',
                    'oktagon-x-connect-for-woocommerce'
                ),
                __METHOD__,
                var_export(func_get_args(), true)
            )
        );

        $packageHashKey = Meta::getPackageHashKey($package);
        Meta::log(
            sprintf(
                esc_html__(
                    'Package hash-key: %s',
                    'oktagon-x-connect-for-woocommerce'
                ),
                var_export($packageHashKey, true)
            )
        );
        if (!$packageHashKey) {
            return;
        }

        $enabledPickUpPointSelection = !empty($this->instance_settings)
            && !empty($this->instance_settings['enabled_pick_up_point_selection'])
            && $this->instance_settings['enabled_pick_up_point_selection'] !== 'no';

        $shippingService =
            (string) ($this->instance_settings['shipping_service'] ?? '');

        $this->rateMetaData = [];

        // Check if service supports calculated rates here and what attributes it has
        $serviceProvidesRates = false;
        $serviceProvidesRatesWithDescriptions = true;
        $serviceProvidesRatesWithPrices = true;
        if (
            !empty($shippingService)
            && stripos($shippingService, '.') === false
        ) {
            $enabledAndActivatedServices = Meta::getEnabledAndActivatedServices();
            if (
                !empty($enabledAndActivatedServices[$shippingService])
                && is_array($enabledAndActivatedServices[$shippingService])
                && !empty(
                    $enabledAndActivatedServices[$shippingService]['features']
                )
                && is_array(
                    $enabledAndActivatedServices[$shippingService]['features']
                )
                && !empty(
                    $enabledAndActivatedServices[$shippingService]
                    ['features']['GetShippingOptionsForSpecificRequest']
                )
                && is_bool(
                    $enabledAndActivatedServices[$shippingService]
                    ['features']['GetShippingOptionsForSpecificRequest']
                )
            ) {
                $serviceProvidesRates = true;
            }
            if (
                !empty($enabledAndActivatedServices[$shippingService])
                && is_array($enabledAndActivatedServices[$shippingService])
                && !empty(
                    $enabledAndActivatedServices[$shippingService]['attributes']
                )
                && is_array(
                    $enabledAndActivatedServices[$shippingService]['attributes']
                )
            ) {
                foreach ($enabledAndActivatedServices[$shippingService]['attributes'] as $attr) {
                    if ($attr === 'GetShippingOptionsForSpecificRequestExcludesExternalDescriptions') {
                        $serviceProvidesRatesWithDescriptions = false;
                    } elseif ($attr === 'GetShippingOptionsForSpecificRequestExcludesExternalPriceCalculation') {
                        $serviceProvidesRatesWithPrices = false;
                    }
                }
            }
        }

        // Try to decode additional options
        $additionalOptions = [];
        $additionalOptionsString = !empty($this->instance_settings['shipping_service_options'])
            && is_string($this->instance_settings['shipping_service_options'])
            ? $this->instance_settings['shipping_service_options']
            : '';
        if ($additionalOptionsString) {
            try {
                $additionalOptionsDecoded =
                    json_decode($additionalOptionsString, true);
                if ($additionalOptionsDecoded) {
                    $additionalOptions =
                        $additionalOptionsDecoded;
                }
            } catch (\Exception $e) {
                $additionalOptions = [];
            }
        }

        $pickUpPoints = [];
        $selectedPickUpPoint = [];
        if (
            $shippingService
            && $enabledPickUpPointSelection
        ) {
            $street = $package['destination']['address'];
            $zipCode = $package['destination']['postcode'];
            $country = $package['destination']['country'];

            if (
                !empty($country)
                && Session::getSelectedCustomZipCode()
            ) {
                $zipCode = Session::getSelectedCustomZipCode();
                Meta::log(
                    sprintf(
                        esc_html__(
                            'Setting destination postcode from session to: %s',
                            'oktagon-x-connect-for-woocommerce'
                        ),
                        var_export($zipCode, true)
                    )
                );
            }

            if (
                empty($zipCode)
                || empty($country)
            ) {
                return;
            }

            $pickUpPoints = Meta::getPickUpAgentsForSpecificRequest(
                $shippingService,
                $country,
                $street,
                $zipCode,
                $additionalOptions
            );
            Meta::log(
                sprintf(
                    esc_html__(
                        'Pick-up points: %s',
                        'oktagon-x-connect-for-woocommerce'
                    ),
                    var_export($pickUpPoints, true)
                )
            );

            if (!$pickUpPoints) {
                return;
            }

            // If no pick-up point is selected select first
            $firstPickUpPoint = reset($pickUpPoints);
            $sessionPickUpPoint = Session::getSelectedServicePoint(
                $packageHashKey,
                $this->get_rate_id()
            );

            $sessionPickUpPointExistsInPickUpPoints = false;
            if (!empty($sessionPickUpPoint['id'])) {
                foreach ($pickUpPoints as $pickUpPoint) {
                    if (
                        is_array($pickUpPoint)
                        && !empty($pickUpPoint['id'])
                        && $pickUpPoint['id'] === $sessionPickUpPoint['id']
                    ) {
                        $sessionPickUpPointExistsInPickUpPoints = true;
                        break;
                    }
                }
            }

            if (
                $sessionPickUpPoint
                && $sessionPickUpPointExistsInPickUpPoints
            ) {
                $selectedPickUpPoint = $sessionPickUpPoint;
            } else {
                $selectedPickUpPoint = [
                    'address' => $firstPickUpPoint['address'],
                    'id' => $firstPickUpPoint['id'],
                    'title' => $firstPickUpPoint['title'],
                ];
            }

            if ($pickUpPoints) {
                $this->applyAdaptiveQliroOneSupport(
                    $pickUpPoints
                );
            }
        } elseif ($shippingService && $serviceProvidesRates) {
            $shippingOptionsQuery =
                $this->getShippingOptionsQueryForPackage($package);
            $shippingOptions = Meta::getShippingOptionsForSpecificRequest(
                $shippingService,
                $shippingOptionsQuery
            );

            if (empty($shippingOptions)) {
                return;
            }

            $cartIsAvailable =
                \did_action('wp_loaded')
                && isset(\WC()->cart);
            $taxes = [];
            if ($cartIsAvailable) {
                $taxes = \WC_Tax::get_shipping_tax_rates();
            }

            $predefinedLogo = !empty($this->instance_settings['predefined_logo'])
                ? $this->instance_settings['predefined_logo']
                : '';
            if ($predefinedLogo) {
                $predefinedLogo = sprintf(
                    '%s/%s',
                    \plugins_url(
                        'includes/assets/frontend/img',
                        dirname(__FILE__)
                    ),
                    $predefinedLogo
                );
            }

            $subRateIndex = 0;
            $this->rateMetaData = [];
            foreach ($shippingOptions as $shippingOption) {
                $subRateId = sprintf(
                    '%s_%d',
                    $this->get_rate_id(),
                    $subRateIndex
                );

                $subRateEnabledPickUpPointSelection = false;
                $subRatePickUpPoints = [];
                $subRateSelectedPickUpPoint = [];
                if (
                    !empty($shippingOption['pickUpAgents'])
                    && is_array($shippingOption['pickUpAgents'])
                ) {
                    $subRateEnabledPickUpPointSelection = true;
                    $subRatePickUpPoints = $shippingOption['pickUpAgents'];

                    // If no pick-up point is selected select first
                    $firstPickUpPoint = reset($subRatePickUpPoints);
                    $sessionPickUpPoint = Session::getSelectedServicePoint(
                        $packageHashKey,
                        $subRateId
                    );

                    $sessionPickUpPointExistsInPickUpPoints = false;
                    if (!empty($sessionPickUpPoint['id'])) {
                        foreach ($subRatePickUpPoints as $pickUpPoint) {
                            if (
                                is_array($pickUpPoint)
                                && !empty($pickUpPoint['id'])
                                && $pickUpPoint['id'] === $sessionPickUpPoint['id']
                            ) {
                                $sessionPickUpPointExistsInPickUpPoints = true;
                                break;
                            }
                        }
                    }

                    if (
                        $sessionPickUpPoint
                        && $sessionPickUpPointExistsInPickUpPoints
                    ) {
                        $subRateSelectedPickUpPoint = $sessionPickUpPoint;
                    } else {
                        $subRateSelectedPickUpPoint = [
                            'address' => $firstPickUpPoint['address'],
                            'id' => $firstPickUpPoint['id'],
                            'title' => $firstPickUpPoint['title'],
                        ];
                    }
                }

                if (!empty($shippingOption['pickUpAgents'])) {
                    $this->applyAdaptiveQliroOneSupport(
                        $shippingOption['pickUpAgents']
                    );
                }

                $subRateTitle = (string) ($shippingOption['title'] ?? '');
                $subRateDescription = (string) ($shippingOption['description'] ?? '');
                $subRatePriceInt = (int) ($shippingOption['price'] ?? 0);
                $subRatePrice = (float) round($subRatePriceInt / 100, 4);
                $subRateShippingService = (string) ($shippingOption['id'] ?? '');

                // Convert price to ex. VAT
                if ($cartIsAvailable && $taxes) {
                    $taxAmount = array_sum(
                        \WC_Tax::calc_tax(
                            $subRatePrice,
                            $taxes,
                            true
                        )
                    );
                    $subRatePrice -= $taxAmount;
                    $subRatePriceInt = (int) (round($subRatePrice / 100));
                }

                $shippingRateMetaData = [
                    'carrier' => (string) ($this->instance_settings['carrier'] ?? ''),
                    'customLogo' => (string) ($this->instance_settings['customLogo'] ?? ''),
                    'description' => $serviceProvidesRatesWithDescriptions
                        ? $subRateDescription
                        : (string) ($this->instance_settings['description'] ?? ''),
                    'enabledPickUpPointSelection' => $subRateEnabledPickUpPointSelection,
                    'pickUpAgents' => $subRatePickUpPoints,
                    'predefinedLogo' => $predefinedLogo,
                    'selectedPickUpPoint' => $subRateSelectedPickUpPoint,
                    'shippingService' => $subRateShippingService,
                ];
                Session::setShippingRateMetaData(
                    $subRateId,
                    $shippingRateMetaData
                );
                $cost = (float) ($this->cost ?? 0.0);
                $rate = [
                    'cost' => (string) (
                        $serviceProvidesRatesWithPrices ? $cost + $subRatePrice : $cost
                    ),
                    'id' => $subRateId,
                    'label' => $serviceProvidesRatesWithDescriptions ? $subRateTitle : $this->title,
                    'meta_data' => [
                        'sub_rate_index' => $subRateIndex,
                    ],
                    'package' => $package,
                ];
                $this->addPackageRate($rate, $package);
                $subRateIndex++;
            }
            return;
        }

        $predefinedLogo = !empty($this->instance_settings['predefined_logo'])
            ? $this->instance_settings['predefined_logo']
            : '';
        if ($predefinedLogo) {
            $predefinedLogo = sprintf(
                '%s/%s',
                \plugins_url(
                    'includes/assets/frontend/img',
                    dirname(__FILE__)
                ),
                $predefinedLogo
            );
        }

        $shippingRateMetaData = [
            'carrier' => (string) ($this->instance_settings['carrier'] ?? ''),
            'customLogo' => (string) ($this->instance_settings['customLogo'] ?? ''),
            'description' => (string) ($this->instance_settings['description'] ?? ''),
            'enabledPickUpPointSelection' => $enabledPickUpPointSelection,
            'pickUpAgents' => $pickUpPoints,
            'predefinedLogo' => $predefinedLogo,
            'selectedPickUpPoint' => $selectedPickUpPoint,
            'shippingService' => $shippingService,
        ];
        Session::setShippingRateMetaData(
            $this->get_rate_id(),
            $shippingRateMetaData
        );

        $rate = [
            'cost' => (string) $this->cost,
            'id' => $this->get_rate_id(),
            'label' => $this->title,
            'package' => $package,
        ];

        $this->addPackageRate($rate, $package);
        return;
    }
    // phpcs:enable PSR1.Methods.CamelCapsMethodName.NotCamelCaps

    /**
     * Get items in package.
     * Based on WooCommerce Flat Rates shipping method.
     *
     * @see \WC_Shipping_Flat_Rate->get_package_item_qty()
     * @since Woocommerce 3.0.0
     */
    private function getPackageItemQty(array $package): int
    {
        $totalQuantity = 0;
        if (
            !empty($package['contents'])
            && is_array($package['contents'])
        ) {
            foreach ($package['contents'] as $values) {
                if (
                    isset($values['quantity'])
                    && $values['quantity'] > 0
                    && isset($values['data'])
                    && $values['data']->needs_shipping()
                ) {
                    $totalQuantity += (int) round(
                        (float) $values['quantity']
                    );
                }
            }
        }
        return $totalQuantity;
    }

    /**
     * NOTE This is a dummy method just to improve support with some third-party plugins.
     *
     * @SuppressWarnings(PHPMD.CamelCaseMethodName)
     * phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps
     */
    protected function evaluate_cost(
        string $sum,
        array $args = array()
    ): float {
        return $this->evaluateCost($sum, $args);
    }

    /**
     * Evaluate a cost from a sum/string.
     * Based on WooCommerce Flat Rate shipping method.
     *
     * @see \WC_Shipping_Flat_Rate->evaluate_cost()
     * @since Woocommerce 3.0.0
     */
    private function evaluateCost(
        string $sum,
        array $args = array()
    ): float {
        require_once(
            \WC()->plugin_path()
            . '/includes/libraries/class-wc-eval-math.php'
        );

        $cost = ($args['cost'] ?? '');
        $qty = ($args['qty'] ?? '');

        // Allow 3rd parties to process shipping cost arguments
        $args = \apply_filters(
            'woocommerce_evaluate_shipping_cost_args',
            $args,
            $sum,
            $this
        );
        $locale = \localeconv();
        /** @since Woocommerce 2.3 */
        $decimals = array(
            \wc_get_price_decimal_separator(),
            $locale['decimal_point'],
            $locale['mon_decimal_point'],
            ','
        );
        $this->fee_cost = $cost;

        // Expand short-codes
        \add_shortcode(
            'fee',
            [$this, 'fee']
        );
        $sum = \do_shortcode(
            str_replace(
                [
                    '[qty]',
                    '[cost]',
                ],
                [
                    $qty,
                    $cost,
                ],
                $sum
            )
        );
        \remove_shortcode(
            'fee'
        );

        // Remove whitespace from string
        $sum = preg_replace(
            '/\s+/',
            '',
            $sum
        );

        // Remove locale from string
        $sum = str_replace(
            $decimals,
            '.',
            $sum
        );

        // Trim invalid start/end characters
        $sum = rtrim(
            ltrim(
                $sum,
                "\t\n\r\0\x0B+*/"
            ),
            "\t\n\r\0\x0B+-*/"
        );

        // Do the math
        return $sum ? (float) \WC_Eval_Math::evaluate($sum) : 0.;
    }

    /**
     * Work out fee (shortcode).
     */
    public function fee(array $atts): string
    {
        $atts = \shortcode_atts(
            [
                'percent' => '',
                'min_fee' => '',
                'max_fee' => '',
            ],
            $atts,
            'fee'
        );

        $calculatedFee = 0;

        if ($atts['percent']) {
            $calculatedFee =
                $this->fee_cost * (floatval($atts['percent']) / 100);
        }

        if (
            $atts['min_fee']
            && $calculatedFee < $atts['min_fee']
        ) {
            $calculatedFee = $atts['min_fee'];
        }

        if (
            $atts['max_fee']
            && $calculatedFee > $atts['max_fee']
        ) {
            $calculatedFee = $atts['max_fee'];
        }

        return (string) $calculatedFee;
    }

    private function addPackageRate(
        array $rate,
        array $package
    ): void {
        // Support dynamic costs
        $rate['cost'] = $this->evaluateCost(
            $rate['cost'],
            [
                'cost' => $package['contents_cost'],
                'qty'  => $this->getPackageItemQty($package),
            ]
        );

        // Support free-shipping coupons
        $hasFreeShippingCoupon = false;
        if ($coupons = \WC()->cart->get_coupons()) {
            foreach ($coupons as $coupon) {
                if (
                    $coupon->is_valid()
                    && $coupon->get_free_shipping()
                ) {
                    $hasFreeShippingCoupon = true;
                    break;
                }
            }
        }
        if ($hasFreeShippingCoupon) {
            $rate['cost'] = 0;
            $rate['taxes'] = false;
        }

        if (!empty($this->rateMetaData)) {
            $rate['meta_data'] = [];
            $rate['meta_data'] += $this->rateMetaData;
        }

        \do_action(
            'oktagon-x-connect-for-woocommerce-add-rate',
            $this,
            $rate
        );
        \do_action(
            'woocommerce_' . $this->id . '_shipping_add_rate',
            $this,
            $rate
        );
        $this->add_rate($rate);
        Meta::log(
            sprintf(
                esc_html__(
                    '%s adding shipping rate: %s',
                    'oktagon-x-connect-for-woocommerce'
                ),
                __METHOD__,
                var_export(
                    [
                        'id' => $rate['id'],
                        'label' => $rate['label'],
                        'cost' => $rate['cost'],
                    ],
                    true
                )
            )
        );
    }

    /**
     * @throws \Exception
     * @suppress PhanTypeArraySuspiciousNullable
     */
    private static function getShippingOptionsQueryForPackage(
        array $package
    ): array {
        if (!empty($package)) {
            $zipCode = $package['destination']['postcode'];
            $country = $package['destination']['country'];

            if (
                !empty($country)
                && Session::getSelectedCustomZipCode()
            ) {
                $zipCode = Session::getSelectedCustomZipCode();
                Meta::log(
                    sprintf(
                        esc_html__(
                            'Setting destination postcode from session to: %s',
                            'oktagon-x-connect-for-woocommerce'
                        ),
                        var_export($zipCode, true)
                    )
                );
            }

            $packagePhysics = Physics::calculatePackageItems(
                $package,
                'cm',
                'g'
            );

            $recipientIsCompany = false; // TOD Make dynamic

            $defaultLocation =
                (string) \get_option('woocommerce_default_country');
            $senderCountry =
                $defaultLocation;
            if (
                strpos(
                    $defaultLocation,
                    ':'
                ) !== false
            ) {
                $explode = explode(
                    ':',
                    $defaultLocation
                );
                $senderCountry = $explode[0];
            };

            $query = [
                'cartPrice' => (int) round(
                    $package['cart_subtotal'] * 100
                ),
                'currency' => \get_woocommerce_currency(),
                'language' => self::getLocale(),
                'consignor' => [
                    'country' => $senderCountry,
                    'isCompany' => true,
                    'zipCode' => (string) \get_option('woocommerce_store_postcode'),
                ],
                'parcel' => [
                    'dimensionUnit' => 'cm',
                    'height' => (int) round($packagePhysics['height']),
                    'length' => (int) round($packagePhysics['length']),
                    'weight' => (int) round($packagePhysics['weight']),
                    'weightUnit' => 'g',
                ],
                'recipient' => [
                    'address1' => (string) ($package['destination']['address_1'] ?? ''),
                    'address2' => (string) ($package['destination']['address_2'] ?? ''),
                    'city' => (string) ($package['destination']['city'] ?? ''),
                    'country' => (string) ($package['destination']['country'] ?? ''),
                    'isCompany' => $recipientIsCompany,
                    'state' => (string) ($package['destination']['state'] ?? ''),
                    'zipCode' => $zipCode,
                ],
            ];

            return $query;
        } else {
            throw new \Exception(
                __(
                    'Invalid arguments for package query! Contact developers!',
                    'wc-postnord'
                )
            );
        }
    }

    public static function getLocale(): string
    {
        $language = '';

        // Do we have a WPML language code specified?
        if (
            !$language
            && defined('\ICL_LANGUAGE_CODE')
            && !empty(\ICL_LANGUAGE_CODE)
        ) {
            $language = (string) \ICL_LANGUAGE_CODE;
        }

        if (
            !$language
            && $wpLocale = \get_user_locale()
        ) {
            $langCountry = explode(
                '_',
                $wpLocale
            );
            if (
                $langCountry
                && !empty($langCountry[0])
            ) {
                $language = (string) $langCountry[0];
            }
        }

        return $language;
    }

    /**
     * @suppress PhanUndeclaredClassMethod
     */
    private function applyAdaptiveQliroOneSupport(
        array $pickUpPoints
    ): void {
        if (
            isset(\WC()->session)
            && \WC()->session->get('chosen_payment_method') === 'qliro_one'
        ) {
            // Ignore if Qliro One is configured to not use shipping methods in iframe
            $qliroSettings = (array) (\get_option('woocommerce_qliro_one_settings') ?? []);
            $qliroShippingInInframe = (string) ($qliroSettings['shipping_in_iframe'] ?? '');
            if ($qliroShippingInInframe !== 'wc_shipping') {
                Meta::log(
                    esc_html__(
                        'Qliro One is not configured to use shipping in iframe, ignorning..',
                        'oktagon-x-connect-for-woocommerce'
                    )
                );
                return;
            }

            Meta::log(
                esc_html__(
                    'Detected Qliro One, trying to add agents to iframe..',
                    'oktagon-x-connect-for-woocommerce'
                )
            );

            // Add pick up points in iframe
            $krokedilPickupPoints = [];
            foreach ($pickUpPoints as $pickUpPoint) {
                $pickupDistanceInMeters =
                    (int) ($pickUpPoint['distanceInMeters'] ?? 0);
                $pickupOpeningHours = [];
                if (
                    !empty($pickUpPoint['operationalHours'])
                    && is_array($pickUpPoint['operationalHours'])
                    && isset($pickUpPoint['operationalHours']['default'])
                    && is_array($pickUpPoint['operationalHours']['default'])
                    && isset($pickUpPoint['operationalHours']['overrides'])
                    && is_array($pickUpPoint['operationalHours']['overrides'])
                ) {
                    $pickupOpeningHours = $this->generateQliroOpeningHours(
                        $pickUpPoint['operationalHours']['default'],
                        $pickUpPoint['operationalHours']['overrides']
                    );
                }
                $krokedilPickupPoints[] = [
                    'address' => [
                        'city' => $pickUpPoint['city'],
                        'country' => $pickUpPoint['country'],
                        'postcode' => $pickUpPoint['zipCode'],
                        'street' => $pickUpPoint['address1'],
                    ],
                    'coordinates' => [
                        'latitude' => $pickUpPoint['geocoordinateLatitude'] ?? '',
                        'longitude' => $pickUpPoint['geocoordinateLongitude'] ?? '',
                    ],
                    'description' => $pickupDistanceInMeters
                        ? sprintf(
                            esc_html__(
                                '%d meters',
                                'oktagon-x-connect-for-woocommerce'
                            ),
                            $pickupDistanceInMeters
                        )
                        : '',
                    'eta' => [
                        'local' => '',
                        'utc' => '',
                    ],
                    'id' => $pickUpPoint['id'],
                    'name' => $pickUpPoint['title'],
                    'meta_data' => [],
                    'open_hours' => $pickupOpeningHours,
                ];
            }
            $this->rateMetaData['krokedil_pickup_points'] =
                json_encode($krokedilPickupPoints);
            Meta::log(
                sprintf(
                    esc_html__(
                        'Added %d agents to Qliro One iframe for rate id: %s',
                        'oktagon-x-connect-for-woocommerce'
                    ),
                    count($krokedilPickupPoints),
                    var_export($this->get_rate_id(), true)
                )
            );
        }
    }

    private function generateQliroOpeningHours(
        array $defaultOpeningHours,
        array $overrideOpeningHours
    ): array {
        $openingHours = [];
        $shortToLongDay = [
            'mon' => 'Monday',
            'tue' => 'Tuesday',
            'wed' => 'Wednesday',
            'thu' => 'Thursday',
            'fri' => 'Friday',
            'sat' => 'Saturday',
            'sun' => 'Sunday',
        ];

        // Set default opening hours for every weekday
        foreach ($defaultOpeningHours as $defaultOpeningHour) {
            if (
                !empty($defaultOpeningHour['open'])
                && is_string($defaultOpeningHour['open'])
                && !empty($defaultOpeningHour['close'])
                && is_string($defaultOpeningHour['close'])
                && empty($defaultOpeningHour['closed'])
                && is_bool($defaultOpeningHour['closed'])
            ) {
                foreach ($shortToLongDay as $longDay) {
                    $openingHours[$longDay] = [
                        'close' => $defaultOpeningHour['close'],
                        'day' => $longDay,
                        'open' => $defaultOpeningHour['open'],
                    ];
                }
            }
        }

        // Apply opening hour overrides for specific weekdays
        if (
            !empty($overrideOpeningHours['weekday'])
            && is_array($overrideOpeningHours['weekday'])
        ) {
            foreach ($overrideOpeningHours['weekday'] as $shortDay => $overrideOpeningHours) {
                if (
                    !empty($shortToLongDay[$shortDay])
                    && !empty($overrideOpeningHours)
                    && is_array($overrideOpeningHours)
                ) {
                    $longDay = $shortToLongDay[$shortDay];
                    foreach ($overrideOpeningHours as $overrideOpeningHour) {
                        if (
                            !empty($overrideOpeningHour['open'])
                            && is_string($overrideOpeningHour['open'])
                            && !empty($overrideOpeningHour['close'])
                            && is_string($overrideOpeningHour['close'])
                            && empty($overrideOpeningHour['closed'])
                            && is_bool($overrideOpeningHour['closed'])
                        ) {
                            $openingHours[$longDay] = [
                                'close' => $overrideOpeningHour['close'],
                                'day' => $longDay,
                                'open' => $overrideOpeningHour['open'],
                            ];
                        }
                    }
                }
            }
        }

        return array_values($openingHours);
    }
}
