<?php

declare(strict_types=1);

use MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter as DeliveryOptions;
use MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractShipmentOptionsAdapter;
use MyParcelNL\Sdk\src\Collection\Fulfilment\OrderNotesCollection;
use MyParcelNL\Sdk\src\Factory\DeliveryOptionsAdapterFactory;
use MyParcelNL\Sdk\src\Helper\TrackTraceUrl;
use MyParcelNL\Sdk\src\Model\Carrier\CarrierPostNL;
use MyParcelNL\Sdk\src\Model\Consignment\AbstractConsignment;
use MyParcelNL\Sdk\src\Model\Fulfilment\OrderNote;
use MyParcelNL\Sdk\src\Support\Str;
use MyParcelNL\WooCommerce\includes\admin\OrderSettings;
use MyParcelNL\WooCommerce\includes\Settings\Api\AccountSettings;
use MyParcelNL\WooCommerce\includes\Validators\WebhookCallbackUrlValidator;
use WPO\WC\MyParcel\Compatibility\Order as WCX_Order;
use WPO\WC\MyParcel\Compatibility\Product as WCX_Product;
use WPO\WC\MyParcel\Compatibility\WC_Core as WCX;
use WPO\WC\MyParcel\Entity\SettingsFieldArguments;

use MyParcelNL\Sdk\src\Concerns\HasApiKey;
if (! defined('ABSPATH')) {
    exit;
} // Exit if accessed directly

if (class_exists('WCMYPA_Admin')) {
    return new WCMYPA_Admin();
}

/**
 * Admin options, buttons & data
 */
class WCMYPA_Admin
{
    use HasApiKey;

    public const META_CONSIGNMENTS                = "_myparcel_consignments";
    public const META_CONSIGNMENT_ID              = "_myparcel_consignment_id";
    public const META_DELIVERY_OPTIONS            = "_myparcel_delivery_options";
    public const META_HIGHEST_SHIPPING_CLASS      = "_myparcel_highest_shipping_class";
    public const META_LAST_SHIPMENT_IDS           = "_myparcel_last_shipment_ids";
    public const META_SHIPMENTS_IDS              = "_myparcel_last_shipment_ids"; // Alias for backward compatibility
    public const META_RETURN_SHIPMENT_IDS         = "_myparcel_return_shipment_ids";
    public const META_ORDER_VERSION               = "_myparcel_order_version";
    public const META_DELIVERY_DATE               = "_myparcel_delivery_date";
    public const META_PGADDRESS                   = "_myparcel_pgaddress";
    public const META_SHIPMENTS                   = "_myparcel_shipments";
    public const META_SHIPMENT_OPTIONS_EXTRA      = "_myparcel_shipment_options_extra";
    public const META_TRACK_TRACE                 = "_myparcel_tracktrace";
    public const META_HS_CODE                     = "_myparcel_hs_code";
    public const META_HS_CODE_VARIATION           = "_myparcel_hs_code_variation";
    public const META_COUNTRY_OF_ORIGIN_VARIATION = "_myparcel_country_of_origin_variation";
    public const META_LAST_EXPORT_TIME            = "_myparcel_last_export_time";

    public const META_COUNTRY_OF_ORIGIN      = "_myparcel_country_of_origin";
    public const META_AGE_CHECK              = "_myparcel_age_check";
    public const META_PPS                    = '_myparcel_pps';

    public const META_PPS_EXPORTED           = 'pps_exported';
    public const META_PPS_EXPORT_DATE        = 'pps_export_date';
    public const META_PPS_UUID               = 'pps_uuid';

    public const BULK_ACTION_EXPORT       = 'wcmp_export';
    public const BULK_ACTION_PRINT        = 'wcmp_print';
    public const BULK_ACTION_EXPORT_PRINT = 'wcmp_export_print';

    public const OLD_RED_JE_PAKKETJE_NAME = 'redjepakketje';
    /**
     * @deprecated use weight property in META_SHIPMENT_OPTIONS_EXTRA.
     */
    public const META_ORDER_WEIGHT = "_myparcel_order_weight";

    /**
     * Legacy meta keys.
     */
    public const META_SHIPMENT_OPTIONS_LT_4_0_0 = "_myparcel_shipment_options";

    // Ids referring to shipment statuses.
    public const ORDER_STATUS_DELIVERED_AT_RECIPIENT      = 7;
    public const ORDER_STATUS_DELIVERED_READY_FOR_PICKUP  = 8;
    public const ORDER_STATUS_DELIVERED_PACKAGE_PICKED_UP = 9;
    public const ORDER_STATUS_PRINTED_LETTER              = 12;
    public const ORDER_STATUS_PRINTED_DIGITAL_STAMP       = 14;

    public const SHIPMENT_OPTIONS_FORM_NAME = "myparcel_options";

    public const PRODUCT_OPTIONS_ENABLED  = "yes";
    public const PRODUCT_OPTIONS_DISABLED = "no";
    public const PRODUCT_OPTIONS_DEFAULT  = null;

    public function __construct()
    {
        if (is_wp_version_compatible("4.7.0")) {
            // Support both traditional and HPOS order screens
            add_action("bulk_actions-edit-shop_order", [$this, "addBulkActions"], 100);
            add_action("bulk_actions-woocommerce_page_wc-orders", [$this, "addBulkActions"], 100);
            
            // Add bulk action handlers for both traditional and HPOS
            add_filter("handle_bulk_actions-edit-shop_order", [$this, "handleBulkActions"], 10, 3);
            add_filter("handle_bulk_actions-woocommerce_page_wc-orders", [$this, "handleBulkActions"], 10, 3);
        } else {
            add_action("admin_footer", [$this, "bulk_actions"]);
        }

        add_action("admin_footer", [$this, "renderOffsetDialog"]);
        add_action("admin_footer", [$this, "renderShipmentOptionsForm"]);

        /**
         * Orders page
         * --
         * showMyParcelSettings is on the woocommerce_admin_order_actions_end hook because there is no hook to put it
         * in the shipping address column... It is put in the right place after loading using JavaScript.
         *
         * @see wcmp-admin.js -> runTriggers()
         */
        add_action("woocommerce_admin_order_actions_end", [$this, "showMyParcelSettings"], 9999);
        add_action("woocommerce_admin_order_actions_end", [$this, "showOrderActions"], 20);

        /*
         * Single order page - support both traditional and HPOS
         */
        add_action("add_meta_boxes_shop_order", [$this, "add_order_meta_box"]);
        add_action("add_meta_boxes_woocommerce_page_wc-orders", [$this, "add_order_meta_box"]);
        add_action("woocommerce_admin_order_data_after_shipping_address", [$this, "single_order_shipment_options"]);

        add_action("wp_ajax_wcmp_save_shipment_options", [$this, "save_shipment_options_ajax"]);
        add_action("wp_ajax_wcmp_get_shipment_summary_status", [$this, "order_list_ajax_get_shipment_summary"]);
        add_action("wp_ajax_wcmp_get_shipment_options", [$this, "ajaxGetShipmentOptions"]);
        add_action("wp_ajax_wcmp_delete_export_timestamp", [$this, "ajax_delete_export_timestamp"]);
        add_action("wp_ajax_wcmp_save_insurance_settings", [$this, "ajax_save_insurance_settings"]);
        add_action("wp_ajax_wcmp_refresh_barcode", [$this, "ajax_refresh_barcode"]);
        add_action('woocommerce_order_note_added', [$this, 'saveOrderNote'], 2, 2);

        // Add barcode in order grid - support both traditional and HPOS
        add_filter("manage_edit-shop_order_columns", [$this, "barcode_add_new_order_admin_list_column"], 10, 1);
        add_action("manage_shop_order_posts_custom_column", [$this, "addBarcodeToOrderColumn"], 10, 2);
        // HPOS compatibility
        add_filter("manage_woocommerce_page_wc-orders_columns", [$this, "barcode_add_new_order_admin_list_column"], 10, 1);
        add_action("manage_woocommerce_page_wc-orders_custom_column", [$this, "addBarcodeToOrderColumn"], 10, 2);

        add_action('restrict_manage_posts', [$this, 'addDeliveryDayFilterToOrdergrid'], 10, 1);
        add_filter('request', [$this, 'getDeliveryDateFromOrder'], 10, 1);
        // HPOS compatibility for filters
        add_action('woocommerce_order_list_table_restrict_manage_orders', [$this, 'addDeliveryDayFilterToOrdergrid'], 10, 1);

        add_action('woocommerce_payment_complete', [$this, 'automaticExportOrder'], 1000);
        add_action('woocommerce_order_status_changed', [$this, 'automaticExportOrder'], 1000, 3);

        // At the moment this doens't work correctly. Shipment will disappear when status is delivered.
        // add_action("init", [$this, "registerDeliveredPostStatus"], 10, 1);
        // add_filter("wc_order_statuses", [$this, "displayDeliveredPostStatus"], 10, 2);

        add_action('woocommerce_product_after_variable_attributes', [$this, 'variation_hs_code_field'], 10, 3);
        add_action('woocommerce_save_product_variation', [$this, 'save_variation_hs_code_field'], 10, 2);
        add_filter('woocommerce_available_variation', [$this, 'load_variation_hs_code_field'], 10, 1);

        add_action("woocommerce_product_options_shipping", [$this, "productOptionsFields"]);
        add_action("woocommerce_process_product_meta", [$this, "productOptionsFieldSave"]);

        add_action('woocommerce_product_after_variable_attributes', [$this, 'renderVariationCountryOfOriginField'], 10, 3);
        add_action('woocommerce_save_product_variation', [$this, 'saveVariationCountryOfOriginField'], 10, 2);
        add_filter('woocommerce_available_variation', [$this, 'loadVariationCountryOfOriginField'], 10, 1);
    }

    /**
     * @throws \Exception
     */
    public function addDeliveryDayFilterToOrdergrid(): void
    {
        global $typenow;

        if (in_array($typenow, wc_get_order_types('order-meta-boxes'))
            && (apply_filters('deliveryDayFilter', true))) {
            $this->deliveryDayFilter();
        }
    }

    /**
     * @throws \Exception
     */
    public function deliveryDayFilter(): void
    {
        if (is_admin() && 'shop_order' === filter_input(INPUT_GET, 'post_type') && $this->anyActiveCarrierHasShowDeliveryDate()) {
            $selected = (filter_input(INPUT_GET, 'deliveryDate'))
                ? sanitize_text_field(filter_input(INPUT_GET, 'deliveryDate'))
                : false;
            ?>

          <select name="deliveryDate">
            <option value=""><?php esc_html_e('all_delivery_days', 'woocommerce-myparcel'); ?></option>
              <?php
              $carrierName       = CarrierPostNL::NAME;
              $deliveryDayWindow = (int) WCMYPA()->setting_collection->where('carrier', $carrierName)->getByName(
                  WCMYPA_Settings::SETTING_CARRIER_DELIVERY_DAYS_WINDOW
              );

              foreach (range(1, $deliveryDayWindow) as $number) {
                  $date       = gmdate('Y-m-d', strtotime($number . 'days'));
                  $dateString = wc_format_datetime(new WC_DateTime($date), 'D d-m');

                  if (1 === $number) {
                      $dateString = __('tomorrow', 'woocommerce-myparcel') . ' ' . $dateString;
                  }

                  printf(
                      '<option value="%s""%s">%s</option>',
                      esc_html($date),
                      selected($date, $selected),
                      esc_html($dateString)
                  );
              }
              ?>
          </select>
            <?php
        }
    }

    /**
     * @param array $deliveryDate
     *
     * @return array
     */
    public function getDeliveryDateFromOrder(array $deliveryDate): array
    {
        global $typenow;

        $hasDeliveryDate = filter_input(INPUT_GET, 'deliveryDate');

        if ($hasDeliveryDate && in_array($typenow, wc_get_order_types('order-meta-boxes'))) {
            $deliveryDate['meta_query'] = [
                [
                    'key'     => '_myparcel_delivery_date',
                    'value'   => sanitize_text_field(filter_input(INPUT_GET, 'deliveryDate')),
                    'compare' => '=',
                ],
            ];
        }

        return $deliveryDate;
    }

    /**
     * @param \MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractDeliveryOptionsAdapter $deliveryOptions
     */
    public static function renderPickupLocation(DeliveryOptions $deliveryOptions): void
    {
        $pickup = $deliveryOptions->getPickupLocation();

        if (! $pickup || ! $deliveryOptions->isPickup()) {
            return;
        }

        printf(
            '<div class="pickup-location"><strong>%s:</strong><br /> %s<br />%s %s<br />%s %s</div>',
            esc_html__('Pickup location', 'woocommerce-myparcel'),
            esc_html($pickup->getLocationName()),
            esc_html($pickup->getStreet()),
            esc_html($pickup->getNumber()),
            esc_html($pickup->getPostalCode()),
            esc_html($pickup->getCity())
        );

        echo '<hr/>';
    }

    /**
     * @param int    $loop
     * @param array  $variationData
     * @param object $variation
     */
    public function renderVariationCountryOfOriginField(int $loop, array $variationData, $variation): void
    {
        // Skip during AJAX to prevent HTML output in JSON response
        if (wp_doing_ajax()) {
            return;
        }
        
        woocommerce_wp_select(
            [
                'id'            => self::META_COUNTRY_OF_ORIGIN_VARIATION . "[{$loop}]",
                'name'          => self::META_COUNTRY_OF_ORIGIN_VARIATION . "[{$loop}]",
                'type'          => 'select',
                'options'       => array_merge(
                    [
                        null => __('Default', 'woocommerce-myparcel'),
                    ],
                    (new WC_Countries())->get_countries()
                ),
                'value'         => $variation->get_meta(self::META_COUNTRY_OF_ORIGIN_VARIATION, true),
                'label'         => __('product_variable_country_of_origin', 'woocommerce-myparcel'),
                'desc_tip'      => true,
                'description'   => __(
                    'product_variable_country_of_origin_description',
                    'woocommerce-myparcel'
                ),
                'wrapper_class' => 'form-row form-row-full',
            ]
        );
    }

    /**
     * @param int $variationId
     * @param int $loop
     */
    public function saveVariationCountryOfOriginField(int $variationId, int $loop): void
    {
        if (! isset($_POST[self::META_COUNTRY_OF_ORIGIN_VARIATION][$loop])) return;
        $countryOfOriginValue = sanitize_title(wp_unslash($_POST[self::META_COUNTRY_OF_ORIGIN_VARIATION][$loop]));

        if (! empty($countryOfOriginValue) && (new WC_Countries())->country_exists($countryOfOriginValue)) {
            // Use WC_Product for HPOS compatibility
            $variation = wc_get_product($variationId);
            if ($variation) {
                $variation->update_meta_data(self::META_COUNTRY_OF_ORIGIN_VARIATION, $countryOfOriginValue);
                $variation->save();
            }
        }
    }

    /**
     * @param array $variation
     *
     * @return array
     */
    public function loadVariationCountryOfOriginField(array $variation): array
    {
        $variation_product = wc_get_product($variation['variation_id']);
        $variation[self::META_COUNTRY_OF_ORIGIN_VARIATION] = $variation_product ? $variation_product->get_meta(self::META_COUNTRY_OF_ORIGIN_VARIATION, true) : '';

        return $variation;
    }

    /**
     * @param $loop
     * @param $variationData
     * @param $variation
     */
    public function variation_hs_code_field($loop, $variationData, $variation)
    {
        // Skip during AJAX to prevent HTML output in JSON response
        if (wp_doing_ajax()) {
            return;
        }
        
        woocommerce_wp_text_input(
            [
                'id'            => self::META_HS_CODE_VARIATION . "[{$loop}]",
                'name'          => self::META_HS_CODE_VARIATION . "[{$loop}]",
                'value'         => $variation->get_meta(self::META_HS_CODE_VARIATION, true),
                'label'         => __('hs_code', 'woocommerce-myparcel'),
                'desc_tip'      => true,
                'description'   => __('hs_code_variations', 'woocommerce-myparcel'),
                'wrapper_class' => 'form-row form-row-full',
            ]
        );
    }

    /**
     * @param $variationId
     * @param $loop
     */
    public function save_variation_hs_code_field($variationId, $loop)
    {
        if (!isset($_POST[self::META_HS_CODE_VARIATION][$loop])) return;
        $hsCodeValue = sanitize_title(wp_unslash($_POST[self::META_HS_CODE_VARIATION][$loop]));

        if (! $hsCodeValue || ! ctype_digit(str_replace(' ', '', $hsCodeValue))) {
            return;
        }

        $variation = wc_get_product($variationId);
        if ($variation) {
            $variation->update_meta_data(self::META_HS_CODE_VARIATION, esc_attr($hsCodeValue));
            $variation->save();
        }
    }

    /**
     * @param $variation
     *
     * @return mixed
     */
    public function load_variation_hs_code_field($variation)
    {
        $variation_product = wc_get_product($variation['variation_id']);
        $variation[self::META_HS_CODE_VARIATION] = $variation_product ? $variation_product->get_meta(self::META_HS_CODE_VARIATION, true) : '';

        return $variation;
    }

    /**
     * Add delivered post type to order statuses list
     */
    public function registerDeliveredPostStatus(): void
    {
        register_post_status('wc-custom-delivered',
            [
                'label'                     => 'Delivered',
                'public'                    => true,
                'exclude_from_search'       => false,
                'show_in_admin_all_list'    => true,
                'show_in_admin_status_list' => true,
                /* translators: %s: number of orders */
                'label_count'               => _n_noop('Delivered (%s)', 'Delivered (%s)', 'woocommerce-myparcel'),
            ]
        );
    }

    /**
     * @param array $order_statuses
     *
     * @return array
     */
    public function displayDeliveredPostStatus(array $order_statuses): array
    {
        $new_order_statuses = [];

        foreach ($order_statuses as $key => $status) {
            $new_order_statuses[$key] = $status;

            if ('wc-processing' === $key) {
                $new_order_statuses['wc-custom-delivered'] = 'Delivered';
            }
        }

        return $new_order_statuses;
    }

    /**
     * @param             $orderId
     * @param string|null $oldStatus
     * @param string|null $newStatus will be passed when order status change triggers this method
     *
     * @throws \ErrorException
     * @throws \MyParcelNL\Sdk\src\Exception\ApiException
     * @throws \MyParcelNL\Sdk\src\Exception\MissingFieldException
     */
    public function automaticExportOrder($orderId, ?string $oldStatus = null, ?string $newStatus = null): void
    {
        if (! WCMYPA()->setting_collection->isEnabled(WCMYPA_Settings::SETTING_AUTOMATIC_EXPORT)) {
            return;
        }

        $order                 = WCX::get_order($orderId);
        $orderSettings         = new OrderSettings($order);
        $orderStatusAutomation = WCMYPA()->setting_collection->getByName(
            WCMYPA_Settings::SETTING_ORDER_STATUS_AUTOMATION
        );
        $automaticOrderStatus  = WCMYPA()->setting_collection->getByName(
            WCMYPA_Settings::SETTING_AUTOMATIC_ORDER_STATUS
        );
        $alreadyExported       = $orderStatusAutomation && $newStatus === $automaticOrderStatus;

        if ($alreadyExported || $orderSettings->hasLocalPickup()) {
            return;
        }

        $newStatus             = $newStatus ?? WCMP_Settings_Data::NOT_ACTIVE;
        $automaticExportStatus = WCMYPA()->setting_collection->getByName(
            WCMYPA_Settings::SETTING_AUTOMATIC_EXPORT_STATUS
        );

        if ($automaticExportStatus === $newStatus) {
            (new WCMP_Export())->exportByOrderId($orderId);
        }
    }

    /**
     * @param WC_Order $order
     *
     * @throws Exception
     */
    public function showMyParcelSettings(WC_Order $order): void
    {
        try {
            $orderSettings = new OrderSettings($order);
        } catch (Exception $exception) {
            WCMP_Log::add(sprintf('Could not get OrderSettings for order %d', $order->get_id()),
                $exception->getMessage());
            printf(
                '<div class="wcmp__shipment-settings-wrapper">⚠ %s</div>',
                esc_html__('warning_faulty_order_settings', 'woocommerce-myparcel')
            );

            return;
        }

        $isAllowedDestination = WCMP_Country_Codes::isAllowedDestination($orderSettings->getShippingCountry());

        if (! $isAllowedDestination || $orderSettings->hasLocalPickup()) {
            return;
        }

        echo '<div class="wcmp__shipment-settings-wrapper" style="display: none;">';
        $this->printDeliveryDate($orderSettings->getDeliveryOptions());

        $consignments  = self::get_order_shipments($order);
        // if we have shipments, then we show status & link to Track & Trace, settings under i
        if (! empty($consignments)) :
            // only use last shipment
            $lastShipment   = array_pop($consignments);
            $lastShipmentId = $lastShipment['shipment_id'];

            ?>
          <a class="wcmp__shipment-summary__show">
            <span class="wcmp__encircle wcmp__shipment-summary__show">i</span>
          </a>
          <div
            class="wcmp__box wcmp__modal wcmp__shipment-summary__list"
            data-loaded=""
            data-shipment_id="<?php echo (int) $lastShipmentId; ?>"
            data-order_id="<?php echo (int) $order->get_id(); ?>"
            style="display: none;">
              <?php self::renderSpinner(); ?>
          </div>
        <?php endif;
        $packageType       = (new WCMP_Export())->getAllowedPackageType($order, $orderSettings->getPackageType());
        $packageTypeHuman  = WCMP_Data::getPackageTypeHuman(
            $packageType
        );
        $digitalStampRange = $orderSettings->getDigitalStampRange();

        printf(
            '<a href="#" class="wcmp__shipment-options__show" data-order-id="%s"><span class="wcmp__shipment-options__package-type">%s%s</span> &#x25BE;</a>',
            (int) $order->get_id(),
            esc_html($packageTypeHuman),
            AbstractConsignment::PACKAGE_TYPE_DIGITAL_STAMP_NAME === $packageType ? sprintf(' (%s - %s)', esc_html($digitalStampRange['min']), esc_html($digitalStampRange['max'])) : ''
        );

        echo '</div>';
    }

    /**
     * Get shipment status + Track & Trace link via AJAX
     *
     * @throws Exception
     */
    public function order_list_ajax_get_shipment_summary(): void
    {
        check_ajax_referer(WCMYPA::NONCE_ACTION, 'security');

        include('views/html-order-shipment-summary.php');
        die();
    }

    /**
     * @return array
     */
    public function getMyParcelBulkActions(): array
    {
        $exportMode  = WCMYPA()->setting_collection->getByName(WCMYPA_Settings::SETTING_EXPORT_MODE);
        $returnValue = [
            self::BULK_ACTION_EXPORT => __('myparcel_bulk_action_export', 'woocommerce-myparcel'),
        ];

        if (WCMP_Settings_Data::EXPORT_MODE_SHIPMENTS === $exportMode) {
            $returnValue[self::BULK_ACTION_PRINT]        = __('myparcel_bulk_action_print', 'woocommerce-myparcel');
            $returnValue[self::BULK_ACTION_EXPORT_PRINT] = __('myparcel_bulk_action_export_print', 'woocommerce-myparcel');
        }

        return $returnValue;
    }

    /**
     * Add export option to bulk action drop down menu.
     *
     * @param array $actions
     *
     * @return array
     * @since WordPress 4.7.0
     */
    public function addBulkActions(array $actions): array
    {
        $actions = array_merge(
            $actions,
            $this->getMyParcelBulkActions()
        );

        self::renderSpinner('bulkAction');

        return $actions;
    }

    /**
     * Handle bulk actions for both traditional and HPOS order screens
     *
     * @param string $redirect_to URL to redirect to
     * @param string $doaction The action being taken
     * @param array $post_ids Array of order IDs
     *
     * @return string Redirect URL
     */
    public function handleBulkActions(string $redirect_to, string $doaction, array $post_ids): string
    {
        WCMP_Log::add('[handleBulkActions] Called with action: ' . $doaction . ', Order IDs: ' . implode(',', $post_ids));
        
        // Check if this is one of our MyParcel bulk actions
        $myparcel_actions = [
            self::BULK_ACTION_EXPORT,
            self::BULK_ACTION_PRINT,
            self::BULK_ACTION_EXPORT_PRINT,
        ];
        
        if (!in_array($doaction, $myparcel_actions, true)) {
            WCMP_Log::add('[handleBulkActions] Not a MyParcel action, skipping');
            return $redirect_to;
        }
        
        WCMP_Log::add('[handleBulkActions] Processing MyParcel bulk action: ' . $doaction);
        
        // The actual export is handled via AJAX in wcmp-admin.js
        // We just need to return the redirect URL without adding any notices here
        // The JavaScript will handle the export and show the appropriate messages
        
        return $redirect_to;
    }

    /**
     * Add export option to bulk action drop down menu
     * Using Javascript until WordPress core fixes: http://core.trac.wordpress.org/ticket/16031
     *
     * Used pre WordPress 4.7.0
     *
     * @access public
     * @return void
     */
    public function bulk_actions()
    {
        global $post_type;

        if ('shop_order' === $post_type) {
            $bulk_actions = $this->getMyParcelBulkActions();
            ?>
          <script type="text/javascript">
          jQuery(document).ready(function () {
              <?php foreach ($bulk_actions as $action => $title) { ?>
            jQuery('<option>')
              .val('<?php echo esc_attr($action); ?>')
              .html('<?php echo esc_attr($title); ?>')
              .appendTo('select[name=\'action\'], select[name=\'action2\']');
              <?php }    ?>
          });
          </script>
            <?php
            self::renderSpinner();
        }
    }

    /**
     * Show dialog to choose print position (offset)
     *
     * @access public
     * @return void
     */
    public function renderOffsetDialog(): void
    {
        if (! WCMYPA()->setting_collection->isEnabled(WCMYPA_Settings::SETTING_ASK_FOR_PRINT_POSITION)) {
            return;
        }

        $field = [
            "name"              => "offset",
            "class"             => ["wcmp__d--inline-block"],
            "input_class"       => ["wcmp__offset-dialog__offset"],
            "type"              => "number",
            "custom_attributes" => [
                "step" => "1",
                "min"  => "0",
                "max"  => "4",
                "size" => "2",
            ],
        ];

        $class = new SettingsFieldArguments($field, '');
        ?>

      <div
        class="wcmp wcmp__box wcmp__modal wcmp__offset-dialog wcmp__ws--nowrap"
        style="display: none;">
        <div class="wcmp__offset-dialog__inner wcmp__d--flex">
          <div>
            <div class="wcmp__pb--2">
                <?php printf(
                    '<label for="%s">%s</label>',
                    esc_html($class->getId()),
                    esc_html__("Labels to skip", "woocommerce-myparcel")
                ); ?>
            </div>
            <div class="wcmp__d--flex wcmp__pb--2">
                <?php woocommerce_form_field($field["name"], $class->getArguments(false), ""); ?>
              <img
                src="<?php echo esc_url(WCMYPA()->plugin_url()), '/assets/img/offset.svg'; ?>"
                alt="Offset"
                class="wcmp__offset-dialog__icon wcmp__pl--1"/>
            </div>
            <div>
              <a
                href="#"
                class="wcmp__offset-dialog__button button"
                style="display: none;"
                target="_blank">
                  <?php esc_html_e('Print', 'woocommerce-myparcel'); ?>
                  <?php self::renderSpinner(); ?>
              </a>
            </div>
          </div>
          <div class="wcmp__close-button dashicons dashicons-no-alt wcmp__offset-dialog__close wcmp__pl--2"></div>
        </div>
      </div>
        <?php
    }

    /**
     * Hide an empty shipment options form in the footer.
     */
    public function renderShipmentOptionsForm(): void
    {
        echo '<div class="wcmp__box wcmp__modal wcmp__shipment-options-dialog" style="display: none; position: absolute;"></div>';
    }

    /**
     * Get the new html content for the shipment options form based on the passed order id.
     */
    public function ajaxGetShipmentOptions(): void
    {
        // Order is used in views/html-order-shipment-options.php
        $order = wc_get_order((int) filter_input(INPUT_POST, 'orderId'));

        include('views/html-order-shipment-options.php');

        die();
    }

    /**
     * Add print actions to the orders listing
     *
     * @param $order
     *
     * @throws Exception
     */
    public function showOrderActions($order): void
    {
        if (empty($order)) {
            return;
        }

        $shippingCountry = WCX_Order::get_prop($order, 'shipping_country');

        if (! WCMP_Country_Codes::isAllowedDestination($shippingCountry)) {
            return;
        }

        $listingActions = self::getListingActions($order);
        $attributes     = self::getListingAttributes($order);

        foreach ($listingActions as $data) {
            self::renderAction(
                $data['url'],
                $data['alt'],
                $data['img'],
                $attributes
            );
        }
    }

    /**
     * @param \WC_Order $order
     *
     * @return array|array[]
     * @throws \Exception
     */
    public static function getListingActions(WC_Order $order): array
    {
        $orderId         = WCX_Order::get_id($order);
        $orderSettings   = new OrderSettings($order);
        $shippingCountry = WCX_Order::get_prop($order, 'shipping_country');
        $exportMode      = WCMYPA()->setting_collection->getByName(WCMYPA_Settings::SETTING_EXPORT_MODE);
        $consignments    = self::get_order_shipments($order);
        $listingActions  = self::getDefaultListingActions($orderId);

        if (WCMP_Settings_Data::EXPORT_MODE_PPS === $exportMode) {
            $metaPps        = $order->get_meta(self::META_PPS, false);
            $listingActions = self::updateExportButtonForPps($listingActions, $metaPps);
        }

        if (empty($consignments) || WCMP_Settings_Data::EXPORT_MODE_PPS === $exportMode) {
            unset($listingActions[WCMP_Export::GET_LABELS]);
        }

        if (empty($consignments) || WCMP_Data::DEFAULT_COUNTRY_CODE !== $shippingCountry) {
            unset($listingActions[WCMP_Export::EXPORT_RETURN]);
        }

        if ($orderSettings->hasLocalPickup()) {
            unset($listingActions[WCMP_Export::GET_LABELS], $listingActions[WCMP_Export::EXPORT_ORDER]);
        }

        return $listingActions;
    }

    /**
     * @param \WC_Order $order
     *
     * @return array
     */
    public static function getListingAttributes(WC_Order $order): array
    {
        $downloadDisplay = WCMYPA()->setting_collection->getByName(WCMYPA_Settings::SETTING_DOWNLOAD_DISPLAY);
        $orderId         = WCX_Order::get_id($order);
        $metaPps         = $order->get_meta(self::META_PPS, false);
        $attributes      = [];

        if ('display' === $downloadDisplay) {
            $attributes['target'] = '_blank';
        }

        if (is_array($metaPps) && count($metaPps)) {
            $attributes['data-pps'] = esc_attr(wp_json_encode($metaPps));
        }

        return $attributes;
    }

    /**
     * @param int $orderId
     *
     * @return array[]
     */
    public static function getDefaultListingActions(int $orderId): array
    {
        $addShipments = WCMP_Export::EXPORT_ORDER;
        $getLabels    = WCMP_Export::GET_LABELS;
        $addReturn    = WCMP_Export::EXPORT_RETURN;
        $pluginUrl    = WCMYPA()->plugin_url();
        $baseUrl      = 'admin-ajax.php?action=' . WCMP_Export::EXPORT;

        return [
            $addShipments => [
                'url' => admin_url("$baseUrl&request=$addShipments&order_ids=$orderId"),
                'img' => "{$pluginUrl}/assets/img/export.svg",
                'alt' => __('action_export_to_myparcel', 'woocommerce-myparcel'),
            ],
            $getLabels    => [
                'url' => admin_url("$baseUrl&request=$getLabels&order_ids=$orderId"),
                'img' => "{$pluginUrl}/assets/img/print.svg",
                'alt' => __('action_print_myparcel_label', 'woocommerce-myparcel'),
            ],
            $addReturn    => [
                'url' => admin_url("$baseUrl&request=$addReturn&order_ids=$orderId"),
                'img' => "{$pluginUrl}/assets/img/return.svg",
                'alt' => __('action_email_return_label', 'woocommerce-myparcel'),
            ],
        ];
    }

    /**
     * @param array $listingActions
     * @param array $metaPps
     *
     * @return array
     */
    public static function updateExportButtonForPps(array $listingActions, array $metaPps): array
    {
        if (! $metaPps) {
            return $listingActions;
        }

        $pluginUrl                                        = WCMYPA()->plugin_url();
        $listingActions[WCMP_Export::EXPORT_ORDER]['img'] = "{$pluginUrl}/assets/img/myparcel.svg";

        foreach ($metaPps as $metaPpsFeedback) {

            if (is_array($metaPpsFeedback) && $metaPpsFeedback[self::META_PPS_EXPORTED]) {

                $listingActions[WCMP_Export::EXPORT_ORDER]['alt'] = __(
                    'export_hint_already_exported',
                    'woocommerce-myparcel'
                );

                break;
            }
        }

        return $listingActions;
    }

    /**
     * @param WC_Order $order
     * @param bool     $exclude_concepts
     *
     * @return array
     */
    public static function get_order_shipments(WC_Order $order, bool $exclude_concepts = false): array
    {
        $consignments = WCX_Order::get_meta($order, self::META_SHIPMENTS);

        // fallback to legacy consignment data (v1.X)
        if (empty($consignments)) {
            if ($consignment_id = WCX_Order::get_meta($order, self::META_CONSIGNMENT_ID)) {
                $consignments = [
                    [
                        "shipment_id" => $consignment_id,
                        "track_trace" => WCX_Order::get_meta($order, self::META_TRACK_TRACE),
                    ],
                ];
            } elseif ($legacy_consignments = WCX_Order::get_meta($order, self::META_CONSIGNMENTS)) {
                $consignments = [];
                foreach ($legacy_consignments as $consignment) {
                    if (isset($consignment["consignment_id"])) {
                        $consignments[] = [
                            "shipment_id" => $consignment["consignment_id"],
                            "track_trace" => $consignment["track_trace"],
                        ];
                    }
                }
            }
        }

        if (empty($consignments) || ! is_array($consignments)) {
            return [];
        }

        /**
         * Filter out concepts.
         */
        if ($exclude_concepts) {
            $consignments = array_filter($consignments,
                function($consignment) {
                    return isset($consignment["track_trace"]);
                }
            );
        }

        return $consignments;
    }

    /**
     * On saving shipment options from the bulk options form.
     *
     * @throws Exception
     * @see admin/views/html-order-shipment-options.php
     */
    public function save_shipment_options_ajax(): void
    {
        $post = wp_unslash(filter_input_array(INPUT_POST));

        parse_str($post['form_data'], $form_data);

        foreach ($form_data[self::SHIPMENT_OPTIONS_FORM_NAME] as $order_id => $data) {
            $order         = WCX::get_order($order_id);
            $data          = self::removeDisallowedDeliveryOptions($data, $order->get_shipping_country());
            $orderSettings = new OrderSettings($order, $data);

            WCX_Order::update_meta_data(
                $order,
                self::META_DELIVERY_OPTIONS,
                $orderSettings->getDeliveryOptions()->toArray()
            );

            // Save extra options
            WCX_Order::update_meta_data(
                $order,
                self::META_SHIPMENT_OPTIONS_EXTRA,
                array_merge(
                    $orderSettings->getExtraOptions(),
                    $data["extra_options"]
                )
            );
            
            // Save insurance type and percentage if provided
            if (isset($data['shipment_options']['insured_type'])) {
                WCX_Order::update_meta_data(
                    $order,
                    '_myparcel_insurance_type',
                    sanitize_text_field($data['shipment_options']['insured_type'])
                );
            }
            
            if (isset($data['shipment_options']['insured_percentage'])) {
                $percentage = intval($data['shipment_options']['insured_percentage']);
                if ($percentage >= 1 && $percentage <= 100) {
                    WCX_Order::update_meta_data(
                        $order,
                        '_myparcel_insurance_percentage',
                        $percentage
                    );
                }
            }
        }

        die();
    }

    /**
     * AJAX handler to save insurance settings before export
     */
    public function ajax_save_insurance_settings(): void
    {
        check_ajax_referer('wcmp_save_insurance', 'security');
        
        // Check user capabilities
        if (!current_user_can('edit_shop_orders')) {
            wp_send_json_error(['message' => 'Unauthorized access']);
            return;
        }
        
        $order_id = isset($_POST['order_id']) ? (int) $_POST['order_id'] : 0;
        $insurance_type = isset($_POST['insurance_type']) ? sanitize_text_field($_POST['insurance_type']) : 'default';
        $insurance_percentage = isset($_POST['insurance_percentage']) ? (int) $_POST['insurance_percentage'] : 100;
        $insurance_fixed_amount = isset($_POST['insurance_fixed_amount']) ? (int) $_POST['insurance_fixed_amount'] : 100;
        
        if (!$order_id) {
            wp_send_json_error(['message' => 'Invalid order ID']);
            return;
        }
        
        $order = wc_get_order($order_id);
        if (!$order) {
            wp_send_json_error(['message' => 'Order not found']);
            return;
        }
        
        // Validate insurance type
        if (!in_array($insurance_type, ['default', 'fixed', 'percentage'])) {
            $insurance_type = 'default';
        }
        
        // Validate percentage
        if ($insurance_percentage < 1 || $insurance_percentage > 100) {
            $insurance_percentage = 100;
        }
        
        // Save to order metadata
        $order->update_meta_data('_myparcel_insurance_type', $insurance_type);
        $order->update_meta_data('_myparcel_insurance_percentage', $insurance_percentage);
        $order->update_meta_data('_myparcel_insurance_fixed_amount', $insurance_fixed_amount);
        $order->save();
        
        wp_send_json_success(['message' => 'Settings saved']);
    }
    
    /**
     * AJAX handler to delete export timestamp
     */
    public function ajax_delete_export_timestamp(): void
    {
        check_ajax_referer(WCMYPA::NONCE_ACTION);
        
        // Check user capabilities
        if (!current_user_can('edit_shop_orders')) {
            wp_send_json_error(['message' => 'Unauthorized access']);
            return;
        }
        
        $order_id = filter_input(INPUT_GET, 'order_id', FILTER_VALIDATE_INT);
        
        if (!$order_id) {
            wp_send_json_error(['message' => 'Invalid order ID']);
            return;
        }
        
        $order = WCX::get_order($order_id);
        
        if (!$order) {
            wp_send_json_error(['message' => 'Order not found']);
            return;
        }
        
        // Delete last export time
        WCX_Order::delete_meta_data($order, self::META_LAST_EXPORT_TIME);
        
        // Delete exported_at from all shipments
        $shipments = WCX_Order::get_meta($order, self::META_SHIPMENTS);
        if (!empty($shipments) && is_array($shipments)) {
            foreach ($shipments as $shipment_id => $shipment_data) {
                if (isset($shipment_data['exported_at'])) {
                    unset($shipments[$shipment_id]['exported_at']);
                }
            }
            WCX_Order::update_meta_data($order, self::META_SHIPMENTS, $shipments);
        }
        
        wp_send_json_success(['message' => 'Timestamp verwijderd']);
    }

    /**
     * Add the meta box on the single order page
     */
    public function add_order_meta_box(): void
    {
        add_meta_box(
            "myparcel",
            __("MyParcel", "woocommerce-myparcel"),
            [$this, "createMetaBox"],
            "shop_order",
            "side",
            "default"
        );
        
        // Add MyParcel Actions meta box (above order actions)
        add_meta_box(
            "myparcel_actions",
            __("MyParcel Acties", "woocommerce-myparcel"),
            [$this, "createMyParcelActionsMetaBox"],
            "shop_order",
            "side",
            "high"
        );
        
        // HPOS compatibility for wc-orders page
        add_meta_box(
            "myparcel_actions",
            __("MyParcel Acties", "woocommerce-myparcel"),
            [$this, "createMyParcelActionsMetaBox"],
            "woocommerce_page_wc-orders",
            "side",
            "high"
        );
    }
    
    /**
     * Create MyParcel Actions meta box content
     * 
     * @param WC_Order|WP_Post|null $post_or_order
     * @throws Exception
     */
    public function createMyParcelActionsMetaBox($post_or_order = null): void
    {
        // Support both traditional posts and HPOS orders
        if ($post_or_order instanceof WC_Order) {
            $order = $post_or_order;
        } else {
            global $post_id;
            if ($post_or_order instanceof WP_Post) {
                $post_id = $post_or_order->ID;
            }
            $order = WCX::get_order($post_id);
        }

        if (! $order) {
            return;
        }

        $order_id = WCX_Order::get_id($order);
        $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
        
        if (! WCMP_Country_Codes::isAllowedDestination($shipping_country)) {
            echo '<p>' . esc_html__('Verzending niet beschikbaar voor dit land', 'woocommerce-myparcel') . '</p>';
            return;
        }

        $consignments = self::get_order_shipments($order);
        $hasShipments = !empty($consignments);
        
        // Get last export time
        $lastExportTime = WCX_Order::get_meta($order, self::META_LAST_EXPORT_TIME);
        
        // Get track & trace info
        $trackTraceInfo = [];
        if ($hasShipments) {
            $shipments = WCMYPA()->export->getShipmentData(array_keys($consignments), $order);
            foreach ($shipments as $shipment) {
                $trackTraceData = [
                    'code' => $shipment['track_trace'] ?? null,
                    'url' => !empty($shipment['track_trace']) ? self::getTrackTraceUrl($order_id, $shipment['track_trace']) : '',
                    'status' => $shipment['status'] ?? '',
                    'shipment_id' => $shipment['shipment_id'] ?? null,
                ];
                
                // Get export time - try from consignments meta first, then fall back to MyParcel API data
                if (isset($shipment['shipment_id']) && isset($consignments[$shipment['shipment_id']]['exported_at'])) {
                    $trackTraceData['exported_at'] = $consignments[$shipment['shipment_id']]['exported_at'];
                } elseif (isset($shipment['shipment']['created'])) {
                    // Fallback: use MyParcel API created timestamp
                    $trackTraceData['exported_at'] = $shipment['shipment']['created'];
                }
                
                $trackTraceInfo[] = $trackTraceData;
            }
        }
        
        // Check if we have shipment IDs but no barcode data (for refresh functionality)
        $shipmentIds = WCX_Order::get_meta($order, self::META_LAST_SHIPMENT_IDS);
        $shipmentsData = WCX_Order::get_meta($order, self::META_SHIPMENTS);
        $hasShipmentIdsButNoBarcode = !empty($shipmentIds) && empty($shipmentsData);
        
        // Create refresh barcode URL
        $refreshBarcodeUrl = wp_nonce_url(
            admin_url('admin-ajax.php?action=wcmp_refresh_barcode&order_id=' . $order_id),
            'wcmp_refresh_barcode_' . $order_id
        );
        
        // Create delete timestamp URL
        $deleteTimestampUrl = wp_nonce_url(
            admin_url('admin-ajax.php?action=wcmp_delete_export_timestamp&order_id=' . $order_id),
            WCMYPA::NONCE_ACTION
        );
        
        include('views/html-myparcel-actions-metabox.php');
    }

    /**
     * Callback: Create the meta box content on the single order page
     *
     * @throws Exception
     */
    public function createMetaBox(): void
    {
        global $post_id;
        // get order
        $order = WCX::get_order($post_id);

        if (! $order) {
            return;
        }

        $order_id = WCX_Order::get_id($order);

        $shipping_country = WCX_Order::get_prop($order, 'shipping_country');
        if (! WCMP_Country_Codes::isAllowedDestination($shipping_country)) {
            return;
        }

        // show buttons and check if WooCommerce > 3.3.0 is used and select the correct function and class
        echo '<div class="single_wc_actions">';
        $this->showOrderActions($order);
        echo '</div>';

        $downloadDisplay = WCMYPA()->setting_collection->getByName(WCMYPA_Settings::SETTING_DOWNLOAD_DISPLAY) === 'display';
        $consignments    = self::get_order_shipments($order);

        // show shipments if available
        if (empty($consignments)) {
            return;
        }

        include('views/html-order-track-trace-table.php');
    }

    /**
     * @param $order
     *
     * @throws Exception
     */
    public function single_order_shipment_options(WC_Order $order)
    {
        $shipping_country = WCX_Order::get_prop($order, "shipping_country");

        if (! WCMP_Country_Codes::isAllowedDestination($shipping_country)) {
            return;
        }

        $this->showMyParcelSettings($order);
    }

    /**
     * @param WC_Order $order
     *
     * @throws Exception
     */
    public function showDeliveryDateForOrder(WC_Order $order): void
    {
        $deliveryOptions = self::getDeliveryOptionsFromOrder($order);
        $this->printDeliveryDate($deliveryOptions);
    }

    /**
     * @param \WC_Order $order
     * @param bool      $isEmail
     *
     * @throws \Exception
     */
    public function showShipmentConfirmation(WC_Order $order, bool $isEmail): void
    {
        $deliveryOptions  = self::getDeliveryOptionsFromOrder($order);
        $confirmationData = $this->getConfirmationData($deliveryOptions);

        $isVirtual = true;
        foreach ($order->get_items() as $item) {
            $product = $item->get_product();
            if (! $product || $product->is_virtual()) {
                continue;
            }
            $isVirtual = false;
            break;
        }
        if ($isVirtual) return;

        $isEmail
            ? $this->printEmailConfirmation($confirmationData)
            : $this->printThankYouConfirmation($confirmationData);
    }

    /**
     * Go through all getProductOptions and show them on the screen
     */
    public function productOptionsFields(): void
    {
        echo '<div class="options_group">';
        foreach ($this->getProductOptions() as $productOption) {
            $type = $productOption['type'];
            if ('text' === $type) {
                woocommerce_wp_text_input(
                    [
                        'id'          => $productOption['id'],
                        'label'       => $productOption['label'],
                        'description' => $productOption['description'],
                    ]
                );
            } elseif ('select' === $type) {
                woocommerce_wp_select(
                    [
                        'id'          => $productOption['id'],
                        'label'       => $productOption['label'],
                        'options'     => $productOption['options'],
                        'description' => $productOption['description'],
                    ]
                );
            }
        }
        echo '</div>';
    }

    /**
     * @param int $postId
     */
    public function productOptionsFieldSave(int $postId): void
    {
        $product = wc_get_product($postId);
        $post    = wp_unslash(filter_input_array(INPUT_POST));

        foreach ($this->getProductOptions() as $productOption) {
            // check if hs code is passed and not an array (=variation hs code)
            $productOptionId = $productOption['id'];

            if (isset($post[$productOptionId]) && ! is_array($post[$productOptionId])) {
                $postedValue = $post[$productOptionId];

                if (! empty($postedValue)) {
                    WCX_Product::update_meta_data($product, $productOptionId, sanitize_text_field($postedValue));
                } else {
                    WCX_Product::delete_meta_data($product, $productOptionId);
                }
            }
        }
    }

    /**
     * @param $order_id
     * @param $track_trace
     *
     * @return string|void
     * @throws Exception
     */
    public static function getTrackTraceUrl($order_id, $track_trace)
    {
        if (empty($order_id)) {
            return;
        }

        $order    = WCX::get_order($order_id);
        $country  = WCX_Order::get_prop($order, 'shipping_country');
        $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'shipping_postcode'));

        $deliveryOptions = self::getDeliveryOptionsFromOrder($order);

        // use billing postcode for pickup/pakjegemak
        if ('NL' === $country && $deliveryOptions->isPickup()) {
            $postcode = preg_replace('/\s+/', '', WCX_Order::get_prop($order, 'billing_postcode'));
        }

        $trackTraceUrl = sprintf(
            'https://myparcel.me/track-trace/%s/%s/%s',
            $track_trace,
            $postcode,
            $country
        );

        return esc_url($trackTraceUrl);
    }

    /**
     * @return array
     */
    public function getProductOptions(): array
    {
        return [
            'HS-Code'           => [
                'id'          => self::META_HS_CODE,
                'label'       => __('HS Code', 'woocommerce-myparcel'),
                'type'        => 'text',
                'description' => wc_help_tip(
                    __(
                        'HS Codes are used for MyParcel world shipments, you can find the appropriate code on the site of the Dutch Customs',
                        'woocommerce-myparcel'
                    )
                ),
            ],
            'Country-of-origin' => [
                'id'          => self::META_COUNTRY_OF_ORIGIN,
                'label'       => __('product_options_country_of_origin', 'woocommerce-myparcel'),
                'type'        => 'select',
                'options'     => array_merge(
                    [
                        null => __('Default', 'woocommerce-myparcel'),
                    ],
                    (new WC_Countries())->get_countries()
                ),
                'description' => wc_help_tip(
                    __('setting_country_of_origin_help_text', 'woocommerce-myparcel')
                ),
            ],
            'Age-check'         => [
                'id'          => self::META_AGE_CHECK,
                'label'       => __('shipment_options_age_check', 'woocommerce-myparcel'),
                'type'        => 'select',
                'options'     => [
                    null                           => __('Default', 'woocommerce-myparcel'),
                    self::PRODUCT_OPTIONS_DISABLED => __('Disabled', 'woocommerce-myparcel'),
                    self::PRODUCT_OPTIONS_ENABLED  => __('Enabled', 'woocommerce-myparcel'),
                ],
                'description' => wc_help_tip(__('shipment_options_age_check_help_text', 'woocommerce-myparcel')),
            ],
        ];
    }

    /**
     * @snippet       Add Column to Orders Table (e.g. Barcode) - WooCommerce
     *
     * @param $columns
     *
     * @return mixed
     */
    public function barcode_add_new_order_admin_list_column($columns)
    {
        // I want to display Barcode column just after the date column
        return array_slice($columns, 0, 6, true) + ['barcode' => 'Barcode'] + array_slice($columns, 6, null, true);
    }

    /**
     * @param $column
     * @param $order_or_order_id - Can be order ID (traditional) or WC_Order object (HPOS)
     *
     * @throws Exception
     */
    public function addBarcodeToOrderColumn($column, $order_or_order_id = null)
    {
        // HPOS compatibility: Accept both order ID and order object
        if ($order_or_order_id instanceof WC_Order) {
            $order = $order_or_order_id;
        } elseif (is_numeric($order_or_order_id)) {
            $order = WCX::get_order($order_or_order_id);
        } else {
            // Fallback to global $post for backward compatibility
            global $post;
            if (!isset($post->ID)) {
                return;
            }
            $order = WCX::get_order($post->ID);
        }

        if ("barcode" === $column && $order) {
            $this->renderBarcodes($order);
        }
    }

    /**
     * @param WC_Order $order
     *
     * @return void
     * @throws Exception
     */
    public function renderBarcodes(WC_Order $order): void
    {
        $shipments  = self::get_order_shipments($order, false);
        $exportMode = WCMYPA()->setting_collection->getByName(WCMYPA_Settings::SETTING_EXPORT_MODE);

        if (WCMP_Settings_Data::EXPORT_MODE_PPS === $exportMode) {
            $orderId   = WCX_Order::get_id($order);
            $metaPps   = $order->get_meta(self::META_PPS, false);

            if ($metaPps) {
                $lastOrder = end($metaPps);

                if (isset($lastOrder[self::META_PPS_UUID])) {

                    echo (isset($lastOrder[self::META_TRACK_TRACE]) && $lastOrder[self::META_TRACK_TRACE])
                        ? sprintf(
                            '<a href="%s">%s</a>',
                            esc_url(TrackTraceUrl::create(
                                $lastOrder[self::META_TRACK_TRACE],
                                $order->get_shipping_postcode(),
                                $order->get_shipping_country()
                            )),
                            esc_html($lastOrder[self::META_TRACK_TRACE])
                        ) : esc_html(__('Concept created but not printed.', 'woocommerce-myparcel'));
                }
            } else {
                esc_html_e('export_hint_not_exported', 'woocommerce-myparcel');
            }
        } elseif (empty($shipments)) {
            esc_html_e('export_hint_no_label_created_yet', 'woocommerce-myparcel');

            return;
        }

        echo '<div class="wcmp__barcodes">';

        foreach ($shipments as $shipment_id => $shipment) {
            $shipmentStatusId = $shipment['shipment']['status'] ?? null;
            $printedStatuses  = [WCMYPA_Admin::ORDER_STATUS_PRINTED_DIGITAL_STAMP, WCMYPA_Admin::ORDER_STATUS_PRINTED_LETTER];

            if (in_array($shipmentStatusId, $printedStatuses)) {
                echo esc_html(__('The label has been printed.', 'woocommerce-myparcel')), '<br/>';
                continue;
            }

            if (empty($shipment['track_trace'])) {
                echo esc_html(__('Concept created but not printed.', 'woocommerce-myparcel')), '<br/>';
                continue;
            }

            printf(
                '<a target="_blank" class="wcmp__barcode-link" title="%2$s" href="%1$s">%2$s</a><br/>',
                esc_html(self::getTrackTraceUrl($order, $shipment['track_trace'])),
                esc_html($shipment['track_trace'])
            );
        }
        echo '</div>';
    }

    /**
     * Get DeliveryOptions object from the given order's meta data. Uses legacy delivery options if found, if that
     * data is invalid it falls back to defaults.
     *
     * @param WC_Order $order
     * @param array    $inputData
     *
     * @return DeliveryOptions
     * @throws \Exception
     * @see \WCMP_Checkout::save_delivery_options
     */
    public static function getDeliveryOptionsFromOrder(WC_Order $order, array $inputData = []): DeliveryOptions
    {
        $meta = WCX_Order::get_meta($order, self::META_DELIVERY_OPTIONS) ?: null;

        // $meta is a json string, create an instance
        if (! empty($meta) && ! $meta instanceof DeliveryOptions) {
            if (is_string($meta)) {
                $meta = json_decode(stripslashes($meta), true);
            }

            if (! $meta['carrier'] || ! AccountSettings::getInstance()->isEnabledCarrier($meta['carrier'])) {
                $meta['carrier'] = (WCMP_Data::DEFAULT_CARRIER_CLASS)::NAME;
            }

            $meta['date'] = $meta['date'] ?? '';

            try {
                // create new instance from known json
                $meta = DeliveryOptionsAdapterFactory::create((array) $meta);
            } catch (BadMethodCallException $e) {
                // create new instance from unknown json data
                $meta = new WCMP_DeliveryOptionsFromOrderAdapter(null, (array) $meta);
            }
        }

        // Create or update immutable adapter from order with a instanceof DeliveryOptionsAdapter
        if (empty($meta) || ! empty($inputData)) {
            $meta = new WCMP_DeliveryOptionsFromOrderAdapter($meta, $inputData);
        }

        return apply_filters('wc_myparcel_order_delivery_options', $meta, $order);
    }

    /**
     * @param WC_Order $order
     *
     * @return array
     * @throws JsonException
     */
    public static function getExtraOptionsFromOrder(WC_Order $order): array
    {
        $meta = WCX_Order::get_meta($order, self::META_SHIPMENT_OPTIONS_EXTRA) ?: null;

        if (empty($meta)) {
            $meta['collo_amount'] = OrderSettings::DEFAULT_COLLO_AMOUNT;
        }

        return (array) $meta;
    }

    /**
     * @return bool
     */
    private function anyActiveCarrierHasShowDeliveryDate(): bool
    {
        $enabledCarriers = AccountSettings::getInstance()->getEnabledCarriers();

        foreach ($enabledCarriers->all() as $carrier) {
            if (WCMYPA()->setting_collection->where('carrier', $carrier->getName())
                ->getByName(WCMYPA_Settings::SETTING_CARRIER_ALLOW_SHOW_DELIVERY_DATE)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Output the delivery date if there is a date and the show delivery day setting is enabled.
     *
     * @param DeliveryOptions $deliveryOptions
     *
     * @throws Exception
     */
    private function printDeliveryDate(DeliveryOptions $deliveryOptions): void
    {
        $deliveryDate = $deliveryOptions->getDate();
        $deliveryType = $deliveryOptions->getDeliveryType();

        if ($deliveryDate || AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME === $deliveryOptions->getPackageType()) {
            printf(
                '<div class="delivery-date"><strong>%s</strong><br />%s, %s</div>',
                esc_html__('MyParcel shipment:', 'woocommerce-myparcel'),
                esc_html(WCMP_Data::getDeliveryTypesHuman()[$deliveryType]),
                empty($deliveryDate) || $deliveryType === AbstractConsignment::DELIVERY_TYPE_PICKUP_NAME ? ''
                    : esc_html(wc_format_datetime(new WC_DateTime($deliveryOptions->getDate()), 'D d-m'))
            );
        }
    }

    /**
     * Output the chosen delivery options or the chosen pickup options.
     *
     * @param  DeliveryOptions  $deliveryOptions
     *
     * @return array[]|null
     * @throws \Exception
     */
    private function getConfirmationData(DeliveryOptions $deliveryOptions): ?array
    {
        $deliveryOptionsEnabled = WCMYPA()->setting_collection->isEnabled(
            WCMYPA_Settings::SETTING_DELIVERY_OPTIONS_ENABLED
        );

        if (! $deliveryOptionsEnabled || ! $deliveryOptions->getCarrier()) {
            return null;
        }

        $deliveryType = $this->getDeliveryTypeOptions($deliveryOptions);

        if (AbstractConsignment::DELIVERY_TYPE_PICKUP_NAME === $deliveryOptions->getDeliveryType()) {
            $pickupLocation = $deliveryOptions->getPickupLocation();

            return [
                __('delivery_type', 'woocommerce-myparcel')   => $deliveryType,
                __('pickup_location', 'woocommerce-myparcel') => sprintf(
                    "%s<br>%s %s<br>%s %s",
                    $pickupLocation->getLocationName(),
                    $pickupLocation->getStreet(),
                    $pickupLocation->getNumber(),
                    $pickupLocation->getPostalCode(),
                    $pickupLocation->getCity()
                ),
            ];
        }

        $confirmationData = [
            __('delivery_type', 'woocommerce-myparcel') => $deliveryType,
        ];

        if (WCMYPA()->setting_collection->isEnabled(WCMYPA_Settings::SETTING_SHOW_DELIVERY_DAY)) {
            $confirmationData[__('Date:', 'woocommerce-myparcel')] = wc_format_datetime(new WC_DateTime($deliveryOptions->getDate() ?? ''));
        }

        $extraOptions = $this->getExtraOptions($deliveryOptions->getShipmentOptions());
        if ($extraOptions) {
            $confirmationData[__('extra_options', 'woocommerce-myparcel')] = implode('<br/>', $extraOptions);
        }

        return $confirmationData;
    }

    /**
     * @param  DeliveryOptions  $deliveryOptions
     *
     * @return string
     */
    private function getDeliveryTypeOptions(DeliveryOptions $deliveryOptions): string
    {
        $deliveryType  = $deliveryOptions->getDeliveryType();
        $deliveryTitle = null;

        switch ($deliveryType) {
            case AbstractConsignment::DELIVERY_TYPE_STANDARD_NAME:
                $deliveryTitle = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_STANDARD_TITLE);
                break;
            case AbstractConsignment::DELIVERY_TYPE_MORNING_NAME:
                $deliveryTitle = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_MORNING_DELIVERY_TITLE);
                break;
            case AbstractConsignment::DELIVERY_TYPE_EVENING_NAME:
                $deliveryTitle = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_EVENING_DELIVERY_TITLE);
                break;
            case AbstractConsignment::DELIVERY_TYPE_PICKUP_NAME:
                $deliveryTitle = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_PICKUP_TITLE);
                break;
        }

        return $deliveryTitle ?: WCMP_Data::getDeliveryTypesHuman()[$deliveryType];
    }

    /**
     * @param \MyParcelNL\Sdk\src\Adapter\DeliveryOptions\AbstractShipmentOptionsAdapter|null $shipmentOptions
     *
     * @return array indexed array of strings holding the extra options, may be empty
     */
    private function getExtraOptions(?AbstractShipmentOptionsAdapter $shipmentOptions): array
    {
        $returnValue = [];

        if (! $shipmentOptions) {
            return $returnValue;
        }

        if ($shipmentOptions->hasSignature()) {
            $returnValue[] = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_SIGNATURE_TITLE);
        }
        if ($shipmentOptions->hasOnlyRecipient()) {
            $returnValue[] = WCMP_Checkout::getDeliveryOptionsTitle(WCMYPA_Settings::SETTING_ONLY_RECIPIENT_TITLE);
        }

        return $returnValue;
    }

    /**
     * Print a table with the chosen delivery options on the confirmation page.
     *
     * @param array[]|null $selectedDeliveryOptions
     */
    public function printThankYouConfirmation(?array $selectedDeliveryOptions): void
    {
        echo wp_kses_post($this->generateThankYouConfirmation($selectedDeliveryOptions));
    }

    /**
     * Print a table with the chosen delivery options in the confirmation email.
     *
     * @param array[]|null $selectedDeliveryOptions
     */
    public function printEmailConfirmation(?array $selectedDeliveryOptions): void
    {
        echo wp_kses_post($this->generateEmailConfirmation($selectedDeliveryOptions));
    }

    /**
     * @param string[]|null $options
     *
     * @return string|null
     */
    public function generateThankYouConfirmation(?array $options): ?string
    {
        if ($options) {
            $htmlHeader = '<h2 class="woocommerce-column__title"> ' . __('Delivery information:', 'woocommerce-myparcel') . '</h2><table>';

            foreach ($options as $key => $option) {
                if ($option) {
                    $htmlHeader .= "<tr><td>$key</td><td>" . $this->translate($option) . '</td></tr>';
                }
            }

            return $htmlHeader . '</table>';
        }

        return null;
    }

    /**
     * @param string[]|null $options
     *
     * @return string|null
     */
    public function generateEmailConfirmation(?array $options): ?string
    {
        if ($options) {
            $htmlHeader = "<h2 class='woocommerce-column__title'> " . __("Delivery information:", "woocommerce-myparcel") . "</h2>";
            $htmlHeader .= "<table cellspacing='0' style='border: 1px solid #e5e5e5; margin-bottom: 20px;'>";

            foreach ($options as $key => $option) {
                if ($option) {
                    $htmlHeader .= "<tr style='border: 1px solid #d5d5d5;'>
                              <td style='border: 1px solid #e5e5e5;'>$key</td>
                              <td style='border: 1px solid #e5e5e5;'>" . $this->translate($option) . "</td>
                            </tr>";
                }
            }

            return $htmlHeader . "</table>";
        }

        return null;
    }

    private function translate(string $option): string
    {
        switch ($option) {
            case 'shipment_options_delivery_morning':
                return __('shipment_options_delivery_morning', 'woocommerce-myparcel');
            case 'shipment_options_delivery_standard':
                return __('shipment_options_delivery_standard', 'woocommerce-myparcel');
            case 'shipment_options_delivery_evening':
                return __('shipment_options_delivery_evening', 'woocommerce-myparcel');
            case 'shipment_options_delivery_pickup':
                return __('shipment_options_delivery_pickup', 'woocommerce-myparcel');
            case 'shipment_options_delivery_same_day':
                return __('shipment_options_delivery_same_day', 'woocommerce-myparcel');
            default:
                return $option;
        }
    }

    /**
     * Output a spinner.
     *
     * @param string $state
     * @param array  $args
     */
    public static function renderSpinner(string $state = '', array $args = []): void
    {
        $spinners = [
            'loading' => get_site_url() . '/wp-admin/images/spinner.gif',
            'success' => get_site_url() . '/wp-admin/images/yes.png',
            'failed'  => get_site_url() . '/wp-admin/images/no.png',
        ];

        $arguments = [];

        $args['class'][] = 'wcmp__spinner';

        if ($state) {
            $args['class'][] = "wcmp__spinner--$state";
        }

        foreach ($args as $arg => $value) {
            if (is_array($value)) {
                $value = implode(' ', $value);
            }
            $arguments[] = "$arg=\"$value\"";
        }

        $attributes = implode(' ', $arguments);

        echo wp_kses_post("<span $attributes>");
        foreach ($spinners as $spinnerState => $icon) {
            printf(
                '<img class="wcmp__spinner__%1$s" alt="%1$s" src="%2$s" style="display: %3$s;" />',
                esc_attr($spinnerState),
                esc_url($icon),
                $state === $spinnerState ? 'block' : 'none'
            );
        }
        echo '</span>';
    }

    /**
     * @param string $url
     * @param string $alt
     * @param string $icon
     * @param array  $rawAttributes
     */
    public static function renderAction(string $url, string $alt, string $icon, array $rawAttributes = []): void
    {
        echo wp_kses_post(sprintf(
            '<a href="%1$s" 
                class="button tips wcmp__action" 
                data-tip="%2$s" 
                %4$s>
                <img class="wcmp__action__img wcmp__m--auto" src="%3$s" alt="%2$s" />',
            wp_nonce_url($url, WCMYPA::NONCE_ACTION),
            esc_attr($alt),
            esc_attr($icon),
            wc_implode_html_attributes($rawAttributes)
        ));

        self::renderSpinner();
        echo '</a>';
    }

    /**
     * @param array $shipment
     * @param int   $order_id
     *
     * @throws Exception
     */
    public static function renderTrackTraceLink(array $shipment, int $order_id): void
    {
        $track_trace = $shipment['track_trace'] ?? null;

        if ($track_trace) {
            $track_trace_url  = self::getTrackTraceUrl($order_id, $track_trace);
            $track_trace_link = sprintf(
                '<a href="%s" target="_blank">%s</a>',
                $track_trace_url,
                $track_trace
            );
        } elseif (isset($shipment['shipment']['options'])) {
            $package_type     = WCMP_Export::getPackageTypeHuman($shipment['shipment']['options']['package_type']);
            $track_trace_link = "($package_type)";
        } else {
            $track_trace_link = __('(Unknown)', 'woocommerce-myparcel');
        }

        echo wp_kses_post($track_trace_link);
    }

    /**
     * @param array $shipment
     * @param int   $order_id
     */
    public static function renderStatus(array $shipment, int $order_id): void
    {
        echo esc_html($shipment['status'] ?? '–');
    }

    /**
     * Remove options that aren't allowed and return the edited array.
     *
     * @param array  $data
     * @param string $country
     *
     * @return mixed
     */
    public static function removeDisallowedDeliveryOptions(array $data, string $country): array
    {
        $data['package_type'] = $data['package_type'] ?? AbstractConsignment::DEFAULT_PACKAGE_TYPE_NAME;
        $isHomeCountry        = WCMP_Data::isHomeCountry($country);
        $isEuCountry          = WCMP_Country_Codes::isEuCountry($country);
        $isPackage            = AbstractConsignment::PACKAGE_TYPE_PACKAGE_NAME === $data['package_type'];
        $isDigitalStamp       = AbstractConsignment::PACKAGE_TYPE_DIGITAL_STAMP_NAME === $data['package_type'];

        if (! $isHomeCountry || ! $isPackage) {
            $data['shipment_options']['age_check'] = false;
            $data['shipment_options']['return']    = false;
        }

        if (! $isPackage || (! $isHomeCountry && ! $isEuCountry)) {
            $data['shipment_options']['large_format'] = false;
        }

        if (! $isDigitalStamp) {
            unset($data['extra_options']['weight']);
        }

        return $data;
    }

    /**
     * Whether the current site is capable of receiving webhooks. Checks for SSL connection.
     *
     * @return bool
     */
    public static function canUseWebhooks(): bool
    {
        $validator = new WebhookCallbackUrlValidator();
        $validator->validateAll(get_rest_url());

        try {
            $validator->report();
            return true;
        } catch (Exception $e) {
            // TODO: improve this when validator has a way to check if there are errors without throwing.
        }

        return false;
    }

    /**
     * @param  int       $commentId
     * @param  \WC_Order $order
     *
     * @return void
     */
    public function saveOrderNote(int $commentId, WC_Order $order): void
    {
        $orderMeta  = $order->get_meta(self::META_PPS, false);
        $lastOrder  = end($orderMeta);
        $orderUuid  = $lastOrder ? $lastOrder[self::META_PPS_UUID] : null;
        $comment    = get_comment($commentId);
        $orderNotes = (new OrderNotesCollection())->setApiKey(
            WCMYPA()->setting_collection->getByName(
                WCMYPA_Settings::SETTING_API_KEY
            )
        );

        if (! $orderUuid || 'system' === $comment->comment_author) {
            return;
        }

        $orderNote = new OrderNote([
            'note'      => $comment->comment_content,
            'author'    => 'webshop',
            'orderUuid' => $orderUuid,
        ]);

        WCMP_Export::addValidOrderNote($orderNotes, $orderNote);

        $orderNotes->save();
    }

    /**
     * Refresh barcode data for existing shipments via AJAX
     */
    public function ajax_refresh_barcode()
    {
        // Security check
        if (!current_user_can('edit_shop_orders')) {
            wp_send_json_error('Insufficient permissions');
            return;
        }

        $order_id = absint($_POST['order_id'] ?? 0);

        if (!$order_id) {
            wp_send_json_error('Invalid order ID');
            return;
        }

        // Verify nonce for security - check both possible nonce formats
        $nonce_valid = false;
        if (isset($_POST['_wpnonce'])) {
            $nonce_valid = wp_verify_nonce($_POST['_wpnonce'], 'wcmp_refresh_barcode_' . $order_id);
        }
        if (!$nonce_valid && isset($_GET['_wpnonce'])) {
            $nonce_valid = wp_verify_nonce($_GET['_wpnonce'], 'wcmp_refresh_barcode_' . $order_id);
        }
        
        if (!$nonce_valid) {
            wp_send_json_error('Security check failed - invalid nonce');
            return;
        }

        try {
            $order = wc_get_order($order_id);
            if (!$order) {
                throw new Exception('Order not found');
            }

            // Get existing shipment IDs
            $shipment_ids = $order->get_meta('_myparcel_last_shipment_ids');
            
            if (empty($shipment_ids)) {
                wp_send_json_error('No shipment IDs found for this order');
                return;
            }

            // Ensure $shipment_ids is an array (it might be stored as string or JSON)
            if (is_string($shipment_ids)) {
                // Try JSON decode first
                $decoded = json_decode($shipment_ids, true);
                if (is_array($decoded)) {
                    $shipment_ids = $decoded;
                } else {
                    // If not JSON, try comma-separated values
                    $shipment_ids = array_map('trim', explode(',', $shipment_ids));
                }
            }
            
            // Make sure we have an array
            if (!is_array($shipment_ids)) {
                $shipment_ids = array($shipment_ids);
            }

            // Refresh barcode data using export class
            $export = new WCMP_Export();
            $shipment_data = $export->getShipmentData($shipment_ids, $order);

            if (empty($shipment_data)) {
                wp_send_json_error('Could not retrieve barcode data from MyParcel API');
                return;
            }

            // Save the order to persist metadata changes
            $order->save();
            
            // Get the updated barcode HTML for display
            ob_start();
            $this->renderBarcodes($order);
            $barcode_html = ob_get_clean();

            wp_send_json_success([
                'message' => 'Barcode data refreshed successfully',
                'count' => count($shipment_data),
                'barcode_html' => $barcode_html,
                'shipment_data' => array_map(function($data) {
                    return [
                        'shipment_id' => $data['shipment_id'],
                        'track_trace' => $data['track_trace'],
                        'status' => $data['status']
                    ];
                }, $shipment_data)
            ]);
        } catch (Exception $e) {
            wp_send_json_error('Error refreshing barcode: ' . $e->getMessage());
        }
    }
}

return new WCMYPA_Admin();
