<?php

namespace BWDK\Admin;

use BWDK\Support\Constant;
use WC_Order;

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

/**
 * Get stored BWDK order details from WC order meta (raw JSON).
 * Meta key: _bwdk_order_json_cb (Constant::META_BWDK_JSON_CB).
 * Returns array or null; never throws.
 */
if ( ! function_exists( 'bwdk_get_stored_order_details_json' ) ) {
    function bwdk_get_stored_order_details_json( \WC_Order $order ): ?array {
        $raw = $order->get_meta( Constant::META_BWDK_JSON_CB );
        if ( is_array( $raw ) ) {
            return $raw;
        }
        if ( is_string( $raw ) ) {
            $trimmed = trim( $raw );
            if ( $trimmed === '' ) {
                return null;
            }
            $decoded = json_decode( $trimmed, true );
            if ( is_array( $decoded ) ) {
                return $decoded;
            }
        }
        if ( $raw instanceof \stdClass ) {
            return (array) $raw;
        }
        return null;
    }
}

/**
 * Parse API / ISO datetime string to Unix timestamp (display pipeline only).
 */
if ( ! function_exists( 'bwdk_dx_datetime_string_to_timestamp' ) ) {
    function bwdk_dx_datetime_string_to_timestamp( string $datetime_str ): ?int {
        $datetime_str = trim( $datetime_str );
        if ( $datetime_str === '' ) {
            return null;
        }
        if ( strncmp( $datetime_str, "\xEF\xBB\xBF", 3 ) === 0 ) {
            $datetime_str = substr( $datetime_str, 3 );
        }
        // Unicode minus U+2212 (sometimes pasted / returned by APIs) breaks ISO parsers.
        $datetime_str = str_replace( "\xE2\x88\x92", '-', $datetime_str );

        $stripped_frac = $datetime_str;
        if ( strpos( $stripped_frac, '.' ) !== false ) {
            $stripped_frac = (string) preg_replace( '/\.\d+/', '', $stripped_frac, 1 );
        }

        $candidates = array_unique(
            array_filter(
                [
                    $datetime_str,
                    $stripped_frac,
                    str_replace( 'T', ' ', $stripped_frac ),
                    str_replace( 't', ' ', $stripped_frac ),
                    preg_replace( '/^(\d{4}-\d{2}-\d{2})[Tt]/', '$1 ', $stripped_frac ),
                ]
            )
        );

        foreach ( $candidates as $candidate ) {
            try {
                return ( new \DateTimeImmutable( $candidate ) )->getTimestamp();
            } catch ( \Throwable $e ) {
                // continue
            }
            try {
                return ( new \DateTimeImmutable( $candidate, wp_timezone() ) )->getTimestamp();
            } catch ( \Throwable $e ) {
                // continue
            }
        }

        foreach ( $candidates as $candidate ) {
            $ts = strtotime( $candidate );
            if ( $ts !== false ) {
                return $ts;
            }
        }

        return null;
    }
}

/**
 * Format DigiExpress date/time for WordPress admin UI (localized via wp_date()).
 * Backend truth remains unchanged; this is display-only.
 *
 * @param mixed $datetime Unix timestamp or parseable datetime string.
 */
if ( ! function_exists( 'bwdk_dx_format_datetime_for_ui' ) ) {
    function bwdk_dx_format_datetime_for_ui( $datetime ): string {
        if ( empty( $datetime ) ) {
            return '-';
        }

        if ( is_numeric( $datetime ) ) {
            $timestamp = (int) $datetime;
        } else {
            $parsed = bwdk_dx_datetime_string_to_timestamp( (string) $datetime );
            if ( $parsed === null ) {
                return '-';
            }
            $timestamp = $parsed;
        }

        if ( $timestamp === 0 ) {
            return '-';
        }

        $formatted = wp_date(
            get_option( 'date_format' ) . ' ' . get_option( 'time_format' ),
            $timestamp,
            wp_timezone()
        );

        /**
         * Allow Jalali / site-specific calendar plugins to replace the admin line (e.g. wp-parsidate).
         *
         * @param string     $formatted Formatted string from wp_date().
         * @param int        $timestamp Unix timestamp used for display.
         * @param mixed      $datetime  Original input passed to bwdk_dx_format_datetime_for_ui().
         */
        return (string) apply_filters( 'bwdk_dx_format_datetime_for_ui', $formatted, $timestamp, $datetime );
    }
}

/**
 * Format DigiExpress date (without time) for WordPress admin UI.
 * Display-only; backend truth remains unchanged.
 *
 * @param mixed $datetime Unix timestamp or parseable datetime string.
 */
if ( ! function_exists( 'bwdk_dx_format_date_only_for_ui' ) ) {
    function bwdk_dx_format_date_only_for_ui( $datetime ): string {
        if ( empty( $datetime ) ) {
            return '-';
        }

        if ( is_numeric( $datetime ) ) {
            $timestamp = (int) $datetime;
        } else {
            $parsed = bwdk_dx_datetime_string_to_timestamp( (string) $datetime );
            if ( $parsed === null ) {
                return '-';
            }
            $timestamp = $parsed;
        }

        if ( $timestamp === 0 ) {
            return '-';
        }

        $formatted = wp_date(
            get_option( 'date_format' ),
            $timestamp,
            wp_timezone()
        );

        return (string) apply_filters( 'bwdk_dx_format_date_only_for_ui', $formatted, $timestamp, $datetime );
    }
}

/**
 * Localize ISO-like Gregorian dates inside arbitrary UI text.
 *
 * Supported patterns:
 * - 2026/04/25
 * - 2026-04-25
 * - 2026/04/25 11:00
 * - 2026-04-25 11:00:00
 *
 * Display-only: does not mutate stored meta / API payloads.
 */
if ( ! function_exists( 'bwdk_dx_localize_dates_in_text_for_ui' ) ) {
    function bwdk_dx_localize_dates_in_text_for_ui( $text ) {
        if ( empty( $text ) || ! is_string( $text ) ) {
            return $text;
        }

        // ISO-8601 from API: T or t, fractional seconds, Z or ±offset (with or without colon in offset).
        $text = preg_replace_callback(
            '/\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}(?::?\d{2,4})?)?/',
            static function ( $matches ) {
                if ( ! function_exists( 'bwdk_dx_format_datetime_for_ui' ) ) {
                    return $matches[0];
                }
                $out = bwdk_dx_format_datetime_for_ui( $matches[0] );
                return $out !== '-' ? $out : $matches[0];
            },
            $text
        );

        return preg_replace_callback(
            '/\b(\d{4})[\/\-](\d{2})[\/\-](\d{2})(?:\s+(\d{2}):(\d{2})(?::(\d{2}))?)?\b/',
            static function ( $matches ) {
                $datetime = sprintf(
                    '%s-%s-%s%s',
                    $matches[1],
                    $matches[2],
                    $matches[3],
                    isset( $matches[4] ) ? sprintf( ' %s:%s:%s', $matches[4], $matches[5], $matches[6] ?? '00' ) : ''
                );

                return bwdk_dx_format_date_only_for_ui( $datetime );
            },
            $text
        );
    }
}

/**
 * Single display pipeline for API date/time strings (summary "تاریخ بارگیری", Cargo Ready row, debug UI line).
 * Prefer wp_date (via bwdk_dx_format_datetime_for_ui); fall back to text ISO replacement.
 */
if ( ! function_exists( 'bwdk_dx_format_shipment_datetime_for_display' ) ) {
    function bwdk_dx_format_shipment_datetime_for_display( string $raw ): string {
        $raw = trim( str_replace( "\xE2\x88\x92", '-', $raw ) );
        if ( $raw === '' ) {
            return '';
        }
        if ( function_exists( 'bwdk_dx_format_datetime_for_ui' ) ) {
            $out = bwdk_dx_format_datetime_for_ui( $raw );
            if ( $out !== '-' ) {
                return $out;
            }
        }
        if ( function_exists( 'bwdk_dx_localize_dates_in_text_for_ui' ) ) {
            $out = bwdk_dx_localize_dates_in_text_for_ui( $raw );
            if ( is_string( $out ) && $out !== '' && $out !== $raw && strpos( $out, 'T' ) === false ) {
                return $out;
            }
        }
        return $raw;
    }
}

if ( ! function_exists( 'bwdk_can_cancel_dx_shipment' ) ) {
    /**
     * Whether DigiExpress shipment may be cancelled: backend allows cancel AND event is pre-pickup (ids 0–3).
     *
     * @param array $selected_shipping_data Shape from BWDK manager order (selected_shipping_data).
     */
    function bwdk_can_cancel_dx_shipment( array $selected_shipping_data ): bool {
        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
            ? $selected_shipping_data['detail']
            : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] )
            ? $detail['event_type']
            : [];
        if ( ! array_key_exists( 'id', $event_type ) ) {
            return false;
        }
        $event_id = (int) $event_type['id'];
        $has_cancel = ! empty( $event_type['has_cancel_shipment'] );
        return $has_cancel && in_array( $event_id, [ 0, 1, 2, 3 ], true );
    }
}

if ( ! function_exists( 'bwdk_can_change_dx_shipping' ) ) {
    /**
     * MVP rule: allow changing shipping away from DigiExpress only before pickup (event ids 0–3).
     *
     * Unlike cancel, this does NOT depend on has_cancel_shipment; we still may fail later if backend denies.
     *
     * @param array $selected_shipping_data Shape from BWDK manager order (selected_shipping_data).
     */
    function bwdk_can_change_dx_shipping( array $selected_shipping_data ): bool {
        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
            ? $selected_shipping_data['detail']
            : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] )
            ? $detail['event_type']
            : [];
        if ( ! array_key_exists( 'id', $event_type ) ) {
            return false;
        }
        $event_id = (int) $event_type['id'];
        return in_array( $event_id, [ 0, 1, 2, 3 ], true );
    }
}

if ( ! function_exists( 'bwdk_can_revive_dx_shipment' ) ) {
    /**
     * Whether DigiExpress shipment may be revived (backend flag + local state checks).
     *
     * @param array $selected_shipping_data Shape from BWDK manager order (selected_shipping_data).
     */
    function bwdk_can_revive_dx_shipment( array $selected_shipping_data ): bool {
        // Backwards compatible: keep backend hint, but final decision must be based on actual shipment state.
        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
            ? $selected_shipping_data['detail']
            : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] )
            ? $detail['event_type']
            : [];

        if ( class_exists( '\BWDK_DigiExpress_Service', true ) ) {
            $svc = new \BWDK_DigiExpress_Service();
            $decision = $svc->decide_digiexpress_action( $selected_shipping_data, null );
            return $decision === \BWDK_DigiExpress_Service::ACTION_REVIVE;
        }

        // Legacy fallback only when service class is not available.
        return ! empty( $event_type['has_revive_with_same_ref'] );
    }
}

/**
 * DigiExpress (BWDK) meta box on WooCommerce order edit screen.
 * Uses stored order JSON first (_bwdk_order_json_cb), then API fallback.
 */
class BWDK_Admin_DX_Meta_Box {

    const TRANSIENT_PREFIX   = 'bwdk_order_details_';
    const CACHE_TTL          = 60;
    const PRINT_LABEL_ACTION  = 'bwdk_print_dx_label';
    const PRINT_LABEL_NONCE   = 'bwdk_print_dx_label_nonce';
    const REFRESH_DATA_ACTION = 'bwdk_dx_refresh';
    const REFRESH_DATA_NONCE  = 'bwdk_dx_refresh_nonce';
    const CANCEL_SHIPMENT_ACTION   = 'bwdk_dx_cancel_shipment';
    const CANCEL_SHIPMENT_NONCE_PREFIX = 'bwdk_dx_cancel_shipment_';
    const TRANSIENT_CANCEL_NOTICE = 'bwdk_dx_cancel_notice_';
    const REVIVE_SHIPMENT_ACTION   = 'bwdk_dx_revive_shipment';
    const REVIVE_SHIPMENT_NONCE_PREFIX = 'bwdk_dx_revive_shipment_';
    const TRANSIENT_REVIVE_NOTICE = 'bwdk_dx_revive_notice_';
    const CHANGE_SHIPPING_ACTION = 'bwdk_dx_change_shipping';
    const CHANGE_SHIPPING_NONCE_PREFIX = 'bwdk_dx_change_shipping_';
    const TRANSIENT_CHANGE_SHIPPING_NOTICE = 'bwdk_dx_change_shipping_notice_';
    /** One-shot admin notice after DX refresh redirect (value: array{type,message}|legacy string). */
    const TRANSIENT_REFRESH_NOTICE = 'bwdk_dx_refresh_notice_';
    const META_DX_WC_SHIPPING_METHOD_ID = '_bwdk_dx_wc_shipping_method_id';
    const META_DX_WC_SHIPPING_METHOD_INSTANCE_ID = '_bwdk_dx_wc_shipping_method_instance_id';
    const META_DX_WC_SHIPPING_METHOD_TITLE = '_bwdk_dx_wc_shipping_method_title';
    /** Cargo Ready instant from API sync only (UTC); display via dx_format_jalali_datetime(). */
    const META_DX_CARGO_READY_AT = '_dx_cargo_ready_at';

    /**
     * Queue refresh forms to be rendered in admin footer.
     * This avoids nested <form> inside the WooCommerce order edit form (invalid HTML; browsers may drop inner forms).
     *
     * @var array<int, true>
     */
    protected array $queued_refresh_forms = [];

    public function __construct() {
        add_action( 'add_meta_boxes', [ $this, 'register_meta_box' ], 999 );
        add_action( 'admin_post_' . self::PRINT_LABEL_ACTION, [ $this, 'handle_print_label' ] );
        add_action( 'admin_post_nopriv_' . self::PRINT_LABEL_ACTION, [ $this, 'handle_print_label_nopriv' ] );
        add_action( 'admin_post_' . self::REFRESH_DATA_ACTION, [ $this, 'handle_refresh_data' ] );
        add_action( 'admin_post_' . self::CANCEL_SHIPMENT_ACTION, [ $this, 'handle_cancel_shipment' ] );
        add_action( 'admin_post_' . self::REVIVE_SHIPMENT_ACTION, [ $this, 'handle_revive_shipment' ] );
        add_action( 'admin_post_' . self::CHANGE_SHIPPING_ACTION, [ $this, 'handle_change_shipping' ] );
        add_action( 'admin_notices', [ $this, 'render_dx_cancel_admin_notice' ] );
        add_action( 'admin_notices', [ $this, 'render_dx_revive_admin_notice' ] );
        add_action( 'admin_notices', [ $this, 'render_dx_change_shipping_admin_notice' ] );
        add_action( 'admin_notices', [ $this, 'render_dx_refresh_admin_notice' ] );
        add_action( 'admin_footer', [ $this, 'render_queued_dx_refresh_forms' ] );
    }

    /**
     * Render queued refresh forms in admin footer (outside the main order edit <form>).
     */
    public function render_queued_dx_refresh_forms(): void {
        if ( ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return;
        }
        if ( empty( $this->queued_refresh_forms ) ) {
            return;
        }

        foreach ( array_keys( $this->queued_refresh_forms ) as $order_id ) {
            $order_id = (int) $order_id;
            if ( $order_id <= 0 ) {
                continue;
            }
            $form_id = 'bwdk-dx-refresh-form-' . $order_id;
            echo '<form id="' . esc_attr( $form_id ) . '" method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" style="display:none;">';
            wp_nonce_field( 'bwdk_dx_refresh_' . $order_id );
            echo '<input type="hidden" name="action" value="' . esc_attr( self::REFRESH_DATA_ACTION ) . '">';
            echo '<input type="hidden" name="order_id" value="' . esc_attr( (string) $order_id ) . '">';
            echo '</form>';
        }
    }

    /**
     * Register meta box. Removes stale bwdk_digi_express; adds with instance callback.
     */
    public function register_meta_box(): void {
        if ( ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return;
        }
        try {
            $screen = function_exists( 'wc_get_page_screen_id' )
                ? wc_get_page_screen_id( 'shop-order' )
                : 'shop_order';
            if ( ! is_string( $screen ) || $screen === '' ) {
                $screen = 'shop_order';
            }

            remove_meta_box( 'bwdk_digi_express', 'shop_order', 'normal' );
            remove_meta_box( 'bwdk_digi_express', 'shop_order', 'side' );
            if ( function_exists( 'wc_get_page_screen_id' ) ) {
                $hpos_screen = wc_get_page_screen_id( 'shop-order' );
                if ( is_string( $hpos_screen ) && $hpos_screen !== '' ) {
                    remove_meta_box( 'bwdk_digi_express', $hpos_screen, 'normal' );
                    remove_meta_box( 'bwdk_digi_express', $hpos_screen, 'side' );
                }
            }

            $order_for_screen = $this->resolve_order_for_dx_meta_box_screen();
            if ( ! $order_for_screen instanceof WC_Order ) {
                return;
            }
            $order_uuid_gate = trim( (string) $order_for_screen->get_meta( '_bwdk_order_uuid', true ) );
            if ( $order_uuid_gate === '' || ! $this->order_should_register_digiexpress_metabox( $order_for_screen ) ) {
                return;
            }

            add_meta_box(
                'bwdk_digiexpress_box',
                'دیجی‌اکسپرس (BWDK)',
                [ $this, 'render_meta_box' ],
                $screen,
                'normal',
                'default'
            );
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX META BOX] register_meta_box: ' . $e->getMessage() );
            }
        }
    }

    /** Meta keys written by DigiExpress webhook (API-verified). */
    const META_DX_PANEL_MESSAGE       = '_bwdk_dx_panel_message';
    const META_SELECTED_SHIPPING_DATA = '_bwdk_selected_shipping_data';
    const META_SELECTED_SHIPPING_METHOD = '_bwdk_selected_shipping_method';
    const META_DX_HTML_LABEL          = '_bwdk_dx_html_label';
    const META_DX_PDF_LABEL           = '_bwdk_dx_pdf_label';
    const META_DX_LAST_EVENT_ID       = '_bwdk_dx_last_event_id';
    const META_DX_LAST_MESSAGE        = '_bwdk_dx_last_message';
    const META_DX_LAST_STATUS         = '_bwdk_dx_last_status';
    const META_DX_HISTORY             = '_bwdk_dx_history';
    const META_DX_SHIPPING_STATUS     = '_bwdk_dx_shipping_status';
    const META_DX_DETAIL              = '_bwdk_dx_detail';
    const META_DX_SHIPMENT_CODE       = '_bwdk_dx_shipment_code';
    const META_DX_EVENT_ID            = '_bwdk_dx_event_id';
    const META_DX_OCCURRED_AT         = '_bwdk_dx_occurred_at';
    const META_DX_LAST_REVIVE_HASH     = '_bwdk_dx_last_revive_hash';
    const META_DX_LAST_REVIVE_AT       = '_bwdk_dx_last_revive_at';
    const META_DX_LAST_REVIVE_REASON   = '_bwdk_dx_last_revive_reason';
    const META_DX_LAST_REVIVE_RESPONSE = '_bwdk_dx_last_revive_response';

    /**
     * Get BWDK order details: stored meta first, then API (transient then GET).
     * Used by meta box and print label handler.
     */
    protected function get_details_for_order( WC_Order $order ): ?array {
        $stored = bwdk_get_stored_order_details_json( $order );
        if ( is_array( $stored ) ) {
            return $stored;
        }
        $order_uuid = $order->get_meta( '_bwdk_order_uuid', true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            return null;
        }
        return $this->get_order_details( $order_uuid );
    }

    /**
     * BWDK order JSON for DX detection: prefer stored/API details, fill missing keys from synced meta (meta never overrides backend fields).
     *
     * @return array Merged shape; may be empty if no details and no usable meta.
     */
    protected function get_bwdk_details_for_dx_detection( WC_Order $order ): array {
        $details = $this->get_details_for_order( $order );
        $out     = is_array( $details ) ? $details : [];

        $method_meta = $order->get_meta( self::META_SELECTED_SHIPPING_METHOD, true );
        if ( is_array( $method_meta ) && ! empty( $method_meta ) ) {
            if ( empty( $out['selected_shipping_method'] ) || ! is_array( $out['selected_shipping_method'] ) ) {
                $out['selected_shipping_method'] = $method_meta;
            }
        }

        $data_meta = $order->get_meta( self::META_SELECTED_SHIPPING_DATA, true );
        if ( is_array( $data_meta ) && ! empty( $data_meta ) ) {
            if ( empty( $out['selected_shipping_data'] ) || ! is_array( $out['selected_shipping_data'] ) ) {
                $out['selected_shipping_data'] = $data_meta;
            }
        }

        return $out;
    }

    /**
     * Same merge as get_bwdk_details_for_dx_detection() but uses stored order JSON only (no API).
     * For lightweight admin list rows; detection matches is_digi_express() on merged meta.
     */
    protected function get_bwdk_details_for_dx_detection_local( WC_Order $order ): array {
        $details = bwdk_get_stored_order_details_json( $order );
        $out     = is_array( $details ) ? $details : [];

        $method_meta = $order->get_meta( self::META_SELECTED_SHIPPING_METHOD, true );
        if ( is_array( $method_meta ) && ! empty( $method_meta ) ) {
            if ( empty( $out['selected_shipping_method'] ) || ! is_array( $out['selected_shipping_method'] ) ) {
                $out['selected_shipping_method'] = $method_meta;
            }
        }

        $data_meta = $order->get_meta( self::META_SELECTED_SHIPPING_DATA, true );
        if ( is_array( $data_meta ) && ! empty( $data_meta ) ) {
            if ( empty( $out['selected_shipping_data'] ) || ! is_array( $out['selected_shipping_data'] ) ) {
                $out['selected_shipping_data'] = $data_meta;
            }
        }

        return $out;
    }

    /**
     * Whether the order is DigiExpress using plugin is_digi_express() rules on stored meta + JSON only (no API).
     */
    public static function order_is_digiexpress_for_list( WC_Order $order ): bool {
        $helper = new class() extends BWDK_Admin_DX_Meta_Box {
            public function __construct() {
                // Intentionally omit parent::__construct() — no admin hooks on this instance.
            }

            public function run( WC_Order $order ): bool {
                $details = $this->get_bwdk_details_for_dx_detection_local( $order );

                return $this->is_digi_express( $details );
            }
        };

        return $helper->run( $order );
    }

    /**
     * Resolve the order being edited on the WooCommerce order admin screen (CPT or HPOS).
     */
    protected function resolve_order_for_dx_meta_box_screen(): ?WC_Order {
        $order_id = 0;
        if ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        } elseif ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        } elseif ( isset( $_REQUEST['id'] ) ) {
            $order_id = absint( $_REQUEST['id'] );
        } elseif ( isset( $_REQUEST['post'] ) ) {
            $order_id = absint( $_REQUEST['post'] );
        } else {
            global $post;
            if ( $post && isset( $post->ID ) ) {
                $order_id = (int) $post->ID;
            }
        }
        if ( $order_id <= 0 ) {
            return null;
        }
        $order = wc_get_order( $order_id );

        return $order instanceof WC_Order ? $order : null;
    }

    /**
     * Whether the DigiExpress meta box should be registered for this order (stored/meta + WC shipping display only; no API).
     */
    protected function order_should_register_digiexpress_metabox( WC_Order $order ): bool {
        if ( self::order_is_digiexpress_for_list( $order ) ) {
            return true;
        }
        $title = trim( (string) $order->get_meta( self::META_DX_WC_SHIPPING_METHOD_TITLE, true ) );
        if ( $title !== '' && $this->shipping_method_name_looks_like_digi_express( $title ) ) {
            return true;
        }
        foreach ( $order->get_items( 'shipping' ) as $item ) {
            if ( ! $item instanceof \WC_Order_Item_Shipping ) {
                continue;
            }
            $name = trim( (string) $item->get_name() );
            if ( $this->shipping_method_name_looks_like_digi_express( $name ) ) {
                return true;
            }
            $mid = trim( (string) $item->get_method_id() );
            if ( $mid !== '' && stripos( $mid, 'digi' ) !== false && stripos( $mid, 'express' ) !== false ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Get DigiExpress data for meta box display: prefer webhook-synced meta _bwdk_selected_shipping_data, then API/details (only if is_digi_express).
     *
     * @return array|null selected_shipping_data shape or null.
     */
    protected function get_dx_data_for_display( WC_Order $order ): ?array {
        $from_meta = $order->get_meta( self::META_SELECTED_SHIPPING_DATA, true );
        if ( is_array( $from_meta ) && ! empty( $from_meta ) ) {
            return $from_meta;
        }
        $details = $this->get_bwdk_details_for_dx_detection( $order );
        if ( is_array( $details ) && $this->is_digi_express( $details ) && isset( $details['selected_shipping_data'] ) && is_array( $details['selected_shipping_data'] ) ) {
            return $details['selected_shipping_data'];
        }
        return null;
    }

    /**
     * Display-only snapshot when _bwdk_dx_shipping_status is cancelled: aligns stale selected_shipping_data
     * with authoritative meta so the main UI does not show UnregisteredShipment / event 0.
     * Does not write to the database; debug may still use the original array.
     */
    protected function apply_cancelled_display_overrides( array $data, WC_Order $order ): array {
        if ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) !== 'cancelled' ) {
            return $data;
        }
        $json = wp_json_encode( $data );
        $out  = $json ? json_decode( $json, true ) : null;
        if ( ! is_array( $out ) ) {
            $out = $data;
        }
        $out['shipment_status'] = 'cancelled';

        $last_eid = $order->get_meta( self::META_DX_LAST_EVENT_ID, true );
        $eid      = is_numeric( $last_eid ) ? (int) $last_eid : 8;
        if ( ! in_array( $eid, [ 8, 9 ], true ) ) {
            $eid = 8;
        }
        $detail = isset( $out['detail'] ) && is_array( $out['detail'] ) ? $out['detail'] : [];
        $et     = isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ? $detail['event_type'] : [];
        $et['id']                     = $eid;
        $et['has_cancel_shipment']    = false;
        $detail['event_type']         = $et;
        $out['detail']                = $detail;

        return $out;
    }

    /**
     * Fetch BWDK order details from API: transient first, then GET manager/<uuid>/.
     * Returns decoded array or null. Never throws.
     */
    protected function get_order_details( string $order_uuid ): ?array {
        $order_uuid = trim( $order_uuid );
        if ( $order_uuid === '' ) {
            return null;
        }
        $key = self::TRANSIENT_PREFIX . sanitize_key( $order_uuid );
        $cached = get_transient( $key );
        if ( is_array( $cached ) ) {
            return $cached;
        }

        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                return null;
            }
            $base   = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url    = $base . '/orders/api/v1/manager/' . $order_uuid . '/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            $args   = [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Content-Type'  => 'application/json; charset=utf-8',
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
            ];
            $response = wp_remote_get( $url, $args );
            if ( is_wp_error( $response ) ) {
                return null;
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            if ( $code !== 200 ) {
                return null;
            }
            $body = wp_remote_retrieve_body( $response );
            $data = json_decode( $body, true );
            if ( ! is_array( $data ) ) {
                return null;
            }
            set_transient( $key, $data, self::CACHE_TTL );
            return $data;
        } catch ( \Throwable $e ) {
            return null;
        }
    }

    /**
     * Fetch fresh BWDK order details from API (bypass cache).
     * Returns array on success, ['error' => string] on failure.
     */
    protected function fetch_fresh_order_details( string $order_uuid ): array {
        $order_uuid = trim( $order_uuid );
        if ( $order_uuid === '' ) {
            return ['error' => 'شناسه سفارش BWDK برای این سفارش یافت نشد.'];
        }

        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                return ['error' => 'کلید API تنظیم نشده است. از بخش تنظیمات افزونه، API Key را بررسی کنید.'];
            }
            $base   = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url    = $base . '/orders/api/v1/manager/' . $order_uuid . '/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            if ( $api_key === '' ) {
                return ['error' => 'کلید API تنظیم نشده است. از بخش تنظیمات افزونه، API Key را بررسی کنید.'];
            }
            $args   = [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Content-Type'  => 'application/json; charset=utf-8',
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
            ];
            $response = wp_remote_get( $url, $args );
            if ( is_wp_error( $response ) ) {
                $error_message = $response->get_error_message();
                $mapped_error = $this->map_transport_error( $error_message );
                $this->log_api_error( $url, 'GET', 0, $response->get_error_code(), $error_message, '' );
                return ['error' => $mapped_error];
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( $code === 401 || $code === 403 ) {
                $backend_msg = $this->extract_api_error_message( $body );
                $error = 'احراز هویت با سرویس خرید با دیجی‌کالا ناموفق بود. API Key نامعتبر است یا دسترسی این مرچنت/دامنه تأیید نشده است.';
                if ( $backend_msg ) {
                    $error .= ' ' . $backend_msg;
                }
                $this->log_api_error( $url, 'GET', $code, '', '', $body );
                return ['error' => $error];
            }
            if ( $code >= 400 ) {
                $backend_msg = $this->extract_api_error_message( $body );
                if ( $backend_msg ) {
                    $error = 'درخواست توسط سرویس خرید با دیجی‌کالا رد شد: ' . $backend_msg;
                } else {
                    $error = 'از سرویس خرید با دیجی‌کالا پاسخ معتبر دریافت نشد. لطفاً لاگ خطا را بررسی کنید.';
                }
                $this->log_api_error( $url, 'GET', $code, '', '', $body );
                return ['error' => $error];
            }
            $data = json_decode( $body, true );
            if ( $data === null && $body !== '' ) {
                $this->log_api_error( $url, 'GET', $code, '', 'Invalid JSON', $body );
                return ['error' => 'از سرویس خرید با دیجی‌کالا پاسخ معتبر دریافت نشد. لطفاً لاگ خطا را بررسی کنید.'];
            }
            return $data;
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX Meta Box] API exception: ' . $e->getMessage() );
            }
            return ['error' => 'خطای داخلی سرور. لطفاً لاگ خطا را بررسی کنید.'];
        }
    }

    /**
     * Apply BWDK manager GET response to DigiExpress-related order meta, save, and refresh order-details transient cache.
     *
     * @param WC_Order $order       Order to update.
     * @param array    $api_result  Decoded JSON from GET /orders/api/v1/manager/{uuid}/.
     * @param string   $order_uuid  BWDK order UUID (for transient key).
     */
    protected function sync_dx_meta_from_bwdk_order_response( WC_Order $order, array $api_result, string $order_uuid ): void {
        error_log('DX PATCH VERSION 2026-04-22 LABEL TRACE ACTIVE');
        $selected_shipping_data = $api_result['selected_shipping_data'] ?? null;
        if ( ! is_array( $selected_shipping_data ) ) {
            return;
        }

        $had_local_cancelled = ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'cancelled' );

        // Guard against out-of-order refresh responses overwriting newer local snapshot.
        // We only compare when both sides provide a parsable occurred_at timestamp.
        $incoming_detail  = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] ) ? $selected_shipping_data['detail'] : [];
        $incoming_payload = isset( $incoming_detail['payload'] ) && is_array( $incoming_detail['payload'] ) ? $incoming_detail['payload'] : [];
        $incoming_occurred_at = isset( $incoming_payload['occurred_at'] ) ? trim( (string) $incoming_payload['occurred_at'] ) : '';
        $incoming_ts = false;
        if ( $incoming_occurred_at !== '' ) {
            try {
                $incoming_ts = ( new \DateTimeImmutable( $incoming_occurred_at, wp_timezone() ) )->getTimestamp();
            } catch ( \Exception $e ) {
                $incoming_ts = false;
            }
        }

        $current_detail = $order->get_meta( self::META_DX_DETAIL, true );
        $current_detail = is_array( $current_detail ) ? $current_detail : [];
        $current_payload = isset( $current_detail['payload'] ) && is_array( $current_detail['payload'] ) ? $current_detail['payload'] : [];
        $current_occurred_at = isset( $current_payload['occurred_at'] ) ? trim( (string) $current_payload['occurred_at'] ) : '';
        $current_ts = false;
        if ( $current_occurred_at !== '' ) {
            try {
                $current_ts = ( new \DateTimeImmutable( $current_occurred_at, wp_timezone() ) )->getTimestamp();
            } catch ( \Exception $e ) {
                $current_ts = false;
            }
        }

        if ( $incoming_ts !== false && $current_ts !== false && $current_ts > $incoming_ts ) {
            // Keep newer local meta; do not regress selected/detail/shipment_code/pdf_label.
            return;
        }

        if ( isset( $api_result['selected_shipping_method'] ) ) {
            $order->update_meta_data( self::META_SELECTED_SHIPPING_METHOD, $api_result['selected_shipping_method'] );
        }
        $order->update_meta_data( self::META_SELECTED_SHIPPING_DATA, $selected_shipping_data );

        $pdf_label = isset( $selected_shipping_data['pdf_label'] ) ? trim( (string) $selected_shipping_data['pdf_label'] ) : '';
        error_log( 'DX PDF label candidate: ' . wp_json_encode( [
            'order_id'       => $order->get_id(),
            'has_pdf_label'  => ! empty( $pdf_label ),
            'pdf_label'      => $pdf_label,
        ] ) );
        if ( $pdf_label !== '' ) {
            $order->update_meta_data( self::META_DX_PDF_LABEL, esc_url_raw( $pdf_label ) );

            $old_html = $order->get_meta( self::META_DX_HTML_LABEL, true );
            if ( is_string( $old_html ) && $old_html !== '' ) {
                $order->delete_meta_data( self::META_DX_HTML_LABEL );
            }
        }
        if ( isset( $selected_shipping_data['shipment_code'] ) ) {
            $order->update_meta_data( self::META_DX_SHIPMENT_CODE, (string) $selected_shipping_data['shipment_code'] );
        }

        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] ) ? $selected_shipping_data['detail'] : [];
        $payload = isset( $detail['payload'] ) && is_array( $detail['payload'] ) ? $detail['payload'] : [];

        if ( isset( $detail['event_type']['id'] ) ) {
            $event_id = (int) $detail['event_type']['id'];
            $order->update_meta_data( self::META_DX_EVENT_ID, $event_id );
            $order->update_meta_data( self::META_DX_LAST_EVENT_ID, $event_id );
            $shipping_status = $this->map_event_id_to_shipping_status( $event_id );
            if ( $shipping_status !== null ) {
                $api_reflects_cancel = $this->dx_api_selected_reflects_cancelled( $selected_shipping_data );
                if ( ! ( $had_local_cancelled && ! $api_reflects_cancel ) ) {
                    $order->update_meta_data( self::META_DX_SHIPPING_STATUS, $shipping_status );
                }
            }
        }
        if ( isset( $payload['occurred_at'] ) && (string) $payload['occurred_at'] !== '' ) {
            $order->update_meta_data( self::META_DX_OCCURRED_AT, (string) $payload['occurred_at'] );
        }
        $order->update_meta_data( self::META_DX_DETAIL, $detail );

        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] ) ? $selected_shipping_data['detail'] : [];
        $resolved_msg = null;
        if ( ! empty( $detail ) && class_exists( '\DX_Status_Engine' ) ) {
            $resolved_msg = \DX_Status_Engine::resolve_message( $detail );
            if ( is_array( $resolved_msg ) ) {
                $api_reflects_cancel = $this->dx_api_selected_reflects_cancelled( $selected_shipping_data );
                if ( ! ( $had_local_cancelled && ! $api_reflects_cancel ) ) {
                    $order->update_meta_data( self::META_DX_LAST_MESSAGE, isset( $resolved_msg['message'] ) ? trim( (string) $resolved_msg['message'] ) : '' );
                    $order->update_meta_data( self::META_DX_LAST_STATUS, isset( $resolved_msg['status'] ) ? (int) $resolved_msg['status'] : 3 );
                    $order->update_meta_data( self::META_DX_PANEL_MESSAGE, [
                        'title'   => isset( $resolved_msg['title'] ) ? trim( (string) $resolved_msg['title'] ) : null,
                        'message' => isset( $resolved_msg['message'] ) ? trim( (string) $resolved_msg['message'] ) : '',
                        'status'  => $this->map_status_int_to_string( isset( $resolved_msg['status'] ) ? (int) $resolved_msg['status'] : 3 ),
                    ] );
                }
            }
        }

        // Full history sync on refresh (like webhook snapshot truth).
        if ( isset( $api_result['history'] ) && is_array( $api_result['history'] ) ) {
            $order->update_meta_data( self::META_DX_HISTORY, $api_result['history'] );
        }

        // Keep local state consistent: append DX history from the same snapshot (deduped).
        if ( is_array( $resolved_msg ) ) {
            $api_reflects_cancel = $this->dx_api_selected_reflects_cancelled( $selected_shipping_data );
            if ( ! ( $had_local_cancelled && ! $api_reflects_cancel ) ) {
                $this->maybe_append_dx_history_from_snapshot( $order, $detail, $selected_shipping_data, $resolved_msg );
            }
        }

        $order->save();

        $key = self::TRANSIENT_PREFIX . sanitize_key( trim( $order_uuid ) );
        delete_transient( $key );
        set_transient( $key, $api_result, self::CACHE_TTL );
    }

    /**
     * Build & append a normalized DX history item from the same API snapshot.
     * Dedupe priority: event_message_id, then a safe fallback key.
     *
     * @param array $detail DigiExpress detail object (selected_shipping_data.detail).
     * @param array $selected_shipping_data selected_shipping_data object (for shipment identifiers).
     * @param array $resolved_msg DX_Status_Engine::resolve_message($detail) result.
     */
    protected function maybe_append_dx_history_from_snapshot( WC_Order $order, array $detail, array $selected_shipping_data, array $resolved_msg ): void {
        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) ) {
            $history = [];
        }

        $payload = isset( $detail['payload'] ) && is_array( $detail['payload'] ) ? $detail['payload'] : [];
        $occurred_at = isset( $payload['occurred_at'] ) ? trim( (string) $payload['occurred_at'] ) : '';
        $incoming_ts = false;
        if ( $occurred_at !== '' ) {
            try {
                $incoming_ts = ( new \DateTimeImmutable( $occurred_at, wp_timezone() ) )->getTimestamp();
            } catch ( \Exception $e ) {
                $incoming_ts = false;
            }
        }

        $event_message_id = null;
        if ( isset( $payload['event_message_id'] ) && (string) $payload['event_message_id'] !== '' ) {
            $event_message_id = (string) $payload['event_message_id'];
        }

        $event_id = isset( $detail['event_type']['id'] ) ? (int) $detail['event_type']['id'] : null;
        if ( $event_id === null ) {
            return;
        }

        // Dedupe: event_message_id first.
        if ( $event_message_id !== null ) {
            foreach ( $history as $h ) {
                if ( is_array( $h ) && isset( $h['event_message_id'] ) && (string) $h['event_message_id'] === $event_message_id ) {
                    return;
                }
            }
        }

        // Fallback dedupe key when event_message_id is missing.
        $shipment_code = isset( $selected_shipping_data['shipment_code'] ) ? trim( (string) $selected_shipping_data['shipment_code'] ) : '';
        $shipment_id   = isset( $selected_shipping_data['shipment_id'] ) ? trim( (string) $selected_shipping_data['shipment_id'] ) : '';
        $fallback_key = 'fallback:' . (string) $event_id . '|' . $occurred_at . '|' . $shipment_code . '|' . $shipment_id;
        foreach ( $history as $h ) {
            if ( is_array( $h ) && isset( $h['dedupe_key'] ) && (string) $h['dedupe_key'] === $fallback_key ) {
                return;
            }
        }

        // If we can compare times, only append when newer than last known occurred_at.
        if ( $incoming_ts !== false ) {
            $last = null;
            for ( $i = count( $history ) - 1; $i >= 0; $i-- ) {
                if ( is_array( $history[ $i ] ) ) {
                    $last = $history[ $i ];
                    break;
                }
            }
            if ( is_array( $last ) && isset( $last['occurred_at'] ) ) {
                $last_ts = false;
                try {
                    $last_ts = ( new \DateTimeImmutable( (string) $last['occurred_at'], wp_timezone() ) )->getTimestamp();
                } catch ( \Exception $e ) {
                    $last_ts = false;
                }
                if ( $last_ts !== false && $last_ts >= $incoming_ts ) {
                    return;
                }
            }
        }

        $status_int = isset( $resolved_msg['status'] ) ? (int) $resolved_msg['status'] : 3;
        $history[] = [
            'event_id'          => $event_id,
            'title'             => isset( $resolved_msg['title'] ) ? trim( (string) $resolved_msg['title'] ) : null,
            'message'           => isset( $resolved_msg['message'] ) ? trim( (string) $resolved_msg['message'] ) : '',
            'status'            => $status_int,
            'timestamp'         => current_time( 'mysql' ),
            'occurred_at'       => $occurred_at !== '' ? $occurred_at : null,
            'event_message_id'  => $event_message_id,
            'dedupe_key'        => $fallback_key,
            'bwdk_source'       => 'admin_refresh',
        ];
        $order->update_meta_data( self::META_DX_HISTORY, $history );
    }

    /**
     * POST cancel-shipment (no body). Returns ok or error message key for logging.
     *
     * @return array{ok:bool, error?:string, raw_error?:string}
     */
    protected function request_cancel_dx_shipment_api( string $order_uuid ): array {
        $order_uuid = trim( $order_uuid );
        if ( $order_uuid === '' ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API: empty uuid' );
            }
            return [ 'ok' => false, 'error' => 'empty_uuid', 'raw_error' => 'Empty order UUID' ];
        }

        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] cancel API: missing endpoint/api-key option' );
                }
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Missing API endpoint/api-key option' ];
            }
            $base    = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url     = $base . '/orders/api/v1/manager/' . rawurlencode( $order_uuid ) . '/cancel-shipment/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            if ( $api_key === '' ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] cancel API: empty api-key' );
                }
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Empty api-key option' ];
            }
            $args = [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
            ];
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API request url=' . $url );
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API before wp_remote_post' );
            }
            $t0 = microtime( true );
            $response = wp_remote_post( $url, $args );
            $elapsed_ms = (int) round( ( microtime( true ) - $t0 ) * 1000 );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $rtype = is_object( $response ) ? get_class( $response ) : gettype( $response );
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API after wp_remote_post elapsed_ms=' . (string) $elapsed_ms . ' response_type=' . $rtype );
            }
            if ( is_wp_error( $response ) ) {
                $error_message = $response->get_error_message();
                $this->log_api_error( $url, 'POST', 0, $response->get_error_code(), $error_message, '' );
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] cancel API WP_Error code=' . $response->get_error_code() . ' message=' . $error_message );
                }
                return [ 'ok' => false, 'error' => 'transport', 'raw_error' => $this->get_bwdk_error_message_from_response( $response ) ];
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $body_len = is_string( $body ) ? strlen( $body ) : 0;
                $safe_msg = $this->get_bwdk_error_message_from_response( $response );
                $safe_msg = is_string( $safe_msg ) ? trim( $safe_msg ) : '';
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API response code=' . (string) $code . ' body_length=' . (string) $body_len . ' message=' . $safe_msg );
            }
            if ( $code >= 200 && $code < 300 ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] cancel API success' );
                }
                return [ 'ok' => true ];
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API non-2xx code=' . (string) $code . ' (will return error)' );
            }
            $this->log_api_error( $url, 'POST', $code, '', '', $body );
            return [
                'ok'        => false,
                'error'     => 'http_' . (string) $code,
                'raw_error' => $this->get_bwdk_error_message_from_response( $response ),
            ];
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX Meta Box] cancel shipment API: ' . $e->getMessage() );
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] cancel API exception: ' . $e->getMessage() );
            }
            return [ 'ok' => false, 'error' => 'exception', 'raw_error' => $e->getMessage() ];
        }
    }

    /**
     * POST revive-shipment with preparation_time (days). Returns ok or raw_error.
     *
     * @return array{ok:bool, error?:string, raw_error?:string}
     */
    protected function request_revive_dx_shipment_api( string $order_uuid, int $preparation_time ): array {
        $order_uuid = trim( $order_uuid );
        if ( $order_uuid === '' ) {
            return [ 'ok' => false, 'error' => 'empty_uuid', 'raw_error' => 'Empty order UUID' ];
        }
        if ( $preparation_time < 0 ) {
            return [ 'ok' => false, 'error' => 'invalid_preparation_time', 'raw_error' => 'Invalid preparation_time' ];
        }

        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Missing API endpoint/api-key option' ];
            }
            $base    = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url     = $base . '/orders/api/v1/manager/' . rawurlencode( $order_uuid ) . '/revive-shipment/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            if ( $api_key === '' ) {
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Empty api-key option' ];
            }

            $args = [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Content-Type'  => 'application/json; charset=utf-8',
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
                'body'      => wp_json_encode( [ 'preparation_time' => $preparation_time ] ),
            ];

            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX REVIVE] request url=' . $url . ' preparation_time=' . (string) $preparation_time );
            }

            $response = wp_remote_post( $url, $args );
            if ( is_wp_error( $response ) ) {
                $error_message = $response->get_error_message();
                $this->log_api_error( $url, 'POST', 0, $response->get_error_code(), $error_message, '' );
                return [ 'ok' => false, 'error' => 'transport', 'raw_error' => $this->get_bwdk_error_message_from_response( $response ) ];
            }

            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( $code >= 200 && $code < 300 ) {
                return [ 'ok' => true ];
            }

            $this->log_api_error( $url, 'POST', $code, '', '', is_string( $body ) ? $body : '' );
            return [
                'ok'        => false,
                'error'     => 'http_' . (string) $code,
                'raw_error' => $this->get_bwdk_error_message_from_response( $response ),
            ];
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX Meta Box] revive shipment API: ' . $e->getMessage() );
            }
            return [ 'ok' => false, 'error' => 'exception', 'raw_error' => $e->getMessage() ];
        }
    }

    /**
     * True when BWDK selected_shipping_data already reflects a cancelled shipment (authoritative API / webhook).
     */
    protected function dx_api_selected_reflects_cancelled( array $selected_shipping_data ): bool {
        $raw = isset( $selected_shipping_data['shipment_status'] ) ? strtolower( trim( (string) $selected_shipping_data['shipment_status'] ) ) : '';
        if ( in_array( $raw, [ 'cancelled', 'canceled' ], true ) ) {
            return true;
        }
        $detail = isset( $selected_shipping_data['detail'] ) && is_array( $selected_shipping_data['detail'] )
            ? $selected_shipping_data['detail']
            : [];
        $event_type = isset( $detail['event_type'] ) && is_array( $detail['event_type'] ) ? $detail['event_type'] : [];
        if ( isset( $event_type['id'] ) ) {
            $eid = (int) $event_type['id'];
            if ( in_array( $eid, [ 8, 9 ], true ) ) {
                return true;
            }
        }
        return false;
    }

    /**
     * When cancel API succeeded but GET still lags: patch meta + selected_shipping_data so the meta box shows cancelled.
     * Webhook/refresh can overwrite later with authoritative data.
     */
    protected function apply_local_cancelled_shipment_fallback( WC_Order $order, string $order_uuid ): void {
        $order_uuid = trim( $order_uuid );
        $msg = __( 'سفارش با موفقیت لغو شد.', 'bwdk' );
        $info  = class_exists( '\DX_Status_Engine', true ) ? \DX_Status_Engine::STATUS_INFO : 3;

        $selected = $order->get_meta( self::META_SELECTED_SHIPPING_DATA, true );
        if ( ! is_array( $selected ) ) {
            $selected = [];
        }

        if ( ! $this->dx_api_selected_reflects_cancelled( $selected ) ) {
            $selected['shipment_status'] = 'cancelled';
            $detail = isset( $selected['detail'] ) && is_array( $selected['detail'] ) ? $selected['detail'] : [];
            $eid    = isset( $detail['event_type']['id'] ) ? (int) $detail['event_type']['id'] : null;
            if ( $eid === null || ! in_array( $eid, [ 8, 9 ], true ) ) {
                if ( ! isset( $detail['event_type'] ) || ! is_array( $detail['event_type'] ) ) {
                    $detail['event_type'] = [];
                }
                $detail['event_type']['id']                   = 8;
                $detail['event_type']['has_cancel_shipment'] = false;
                $selected['detail']                           = $detail;
            }
            $order->update_meta_data( self::META_SELECTED_SHIPPING_DATA, $selected );
        }

        $detail_meta = isset( $selected['detail'] ) && is_array( $selected['detail'] ) ? $selected['detail'] : [];
        $order->update_meta_data( self::META_DX_SHIPPING_STATUS, 'cancelled' );
        $order->update_meta_data( self::META_DX_LAST_MESSAGE, $msg );
        $order->update_meta_data( self::META_DX_LAST_STATUS, $info );
        $order->update_meta_data( self::META_DX_LAST_EVENT_ID, 8 );
        $order->update_meta_data( self::META_DX_EVENT_ID, 8 );
        $order->update_meta_data( self::META_DX_PANEL_MESSAGE, [
            'title'   => __( 'ارسال لغو شد', 'bwdk' ),
            'message' => $msg,
            'status'  => 3,
        ] );
        $order->update_meta_data( self::META_DX_DETAIL, $detail_meta );

        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) ) {
            $history = [];
        }
        $dup = false;
        foreach ( $history as $h ) {
            if ( is_array( $h ) && isset( $h['bwdk_source'] ) && in_array( $h['bwdk_source'], [ 'admin_cancel', 'admin_cancel_shipment' ], true ) ) {
                $dup = true;
                break;
            }
        }
        if ( ! $dup ) {
            $history[] = [
                'event_id'    => 8,
                'title'       => __( 'لغو ارسال', 'bwdk' ),
                'message'     => $msg,
                'status'      => 3,
                'timestamp'   => current_time( 'mysql' ),
                'bwdk_source' => 'admin_cancel_shipment',
            ];
            $order->update_meta_data( self::META_DX_HISTORY, $history );
        }

        $order->save();

        $this->persist_cancel_meta_direct( (int) $order->get_id(), $msg );

        if ( $order_uuid !== '' ) {
            delete_transient( self::TRANSIENT_PREFIX . sanitize_key( $order_uuid ) );
        }
    }

    /**
     * Force-cancel meta on the order storage (HPOS + post tables). Called after CRUD save so values are never missing.
     *
     * @param string $msg Localized success message (same as META_DX_LAST_MESSAGE).
     */
    protected function persist_cancel_meta_direct( int $order_id, string $msg ): void {
        if ( $order_id <= 0 ) {
            return;
        }
        $panel = [
            'title'   => __( 'ارسال لغو شد', 'bwdk' ),
            'message' => $msg,
            'status'  => 3,
        ];
        if ( function_exists( 'wc_update_order_meta' ) ) {
            wc_update_order_meta( $order_id, self::META_DX_SHIPPING_STATUS, 'cancelled' );
            wc_update_order_meta( $order_id, self::META_DX_LAST_MESSAGE, $msg );
            wc_update_order_meta( $order_id, self::META_DX_LAST_STATUS, 3 );
            wc_update_order_meta( $order_id, self::META_DX_LAST_EVENT_ID, 8 );
            wc_update_order_meta( $order_id, self::META_DX_EVENT_ID, 8 );
            wc_update_order_meta( $order_id, self::META_DX_PANEL_MESSAGE, $panel );
        } else {
            update_post_meta( $order_id, self::META_DX_SHIPPING_STATUS, 'cancelled' );
            update_post_meta( $order_id, self::META_DX_LAST_MESSAGE, $msg );
            update_post_meta( $order_id, self::META_DX_LAST_STATUS, 3 );
            update_post_meta( $order_id, self::META_DX_LAST_EVENT_ID, 8 );
            update_post_meta( $order_id, self::META_DX_EVENT_ID, 8 );
            update_post_meta( $order_id, self::META_DX_PANEL_MESSAGE, $panel );
        }
    }

    /**
     * Get detail array for message mapper: prefer _bwdk_dx_last_webhook_raw.detail, fallback to selected_shipping_data.detail.
     *
     * @param WC_Order $order
     * @param array    $selected_shipping_data Current selected_shipping_data (from stored/API).
     * @return array|null
     */
    protected function get_detail_for_message( WC_Order $order, array $selected_shipping_data ): ?array {
        $last_webhook = $order->get_meta( '_bwdk_dx_last_webhook_raw', true );
        if ( is_array( $last_webhook ) && isset( $last_webhook['detail'] ) && is_array( $last_webhook['detail'] ) ) {
            return $last_webhook['detail'];
        }
        $detail = $selected_shipping_data['detail'] ?? null;
        return is_array( $detail ) ? $detail : null;
    }

    /**
     * Render a WP admin notice block for DigiExpress message (title + message, status maps to notice class).
     *
     * @param array $msg ['title' => string, 'message' => string, 'status' => 'warning'|'error'|'info'|'success']
     */
    protected function render_dx_message_notice( array $msg ): void {
        $status  = isset( $msg['status'] ) ? trim( (string) $msg['status'] ) : 'info';
        $classes = [
            'warning' => 'notice-warning',
            'error'   => 'notice-error',
            'info'    => 'notice-info',
            'success' => 'notice-success',
        ];
        $class = $classes[ $status ] ?? 'notice-info';
        $title   = isset( $msg['title'] ) ? $msg['title'] : '';
        $message = isset( $msg['message'] ) ? $msg['message'] : '';
        if ( $title === '' && $message === '' ) {
            return;
        }
        echo '<div class="notice ' . esc_attr( $class ) . ' inline" style="margin: 0 0 1em 0; padding: 0.75em 1em;">';
        if ( $title !== '' ) {
            echo '<p style="margin: 0 0 0.25em 0;"><strong>' . esc_html( $title ) . '</strong></p>';
        }
        if ( $message !== '' ) {
            echo '<p style="margin: 0;">' . esc_html( $message ) . '</p>';
        }
        echo '</div>';
    }

    /**
     * Build normalized status data for display.
     */
    protected function get_dx_status_data( WC_Order $order, $panel_msg, ?array $data ): array {
        if ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'cancelled' ) {
            return [
                'title'   => __( 'ارسال لغو شد', 'bwdk' ),
                'message' => __( 'سفارش با موفقیت لغو شد.', 'bwdk' ),
                'status'  => 3,
            ];
        }
        if ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'switched_to_other' ) {
            return [
                'title'   => __( 'روش ارسال تغییر یافت', 'bwdk' ),
                'message' => __( 'روش ارسال از دیجی‌اکسپرس به حالت عادی تغییر یافت.', 'bwdk' ),
                'status'  => 3,
            ];
        }

        $status_data = [
            'title'   => null,
            'message' => '',
            'status'  => 3,
        ];

        // API / selected_shipping_data overrides stale panel_msg (summary aligned with timeline).
        if ( is_array( $data ) && ! empty( $data ) ) {
            $shipment_id     = isset( $data['shipment_id'] ) ? trim( (string) $data['shipment_id'] ) : '';
            $shipment_code   = isset( $data['shipment_code'] ) ? trim( (string) $data['shipment_code'] ) : '';
            $raw_shipment    = isset( $data['shipment_status'] ) ? trim( (string) $data['shipment_status'] ) : '';
            $has_identifiers = ( $shipment_id !== '' || $shipment_code !== '' );

            if ( $raw_shipment !== '' && class_exists( '\DX_Status_Engine', true ) && method_exists( '\DX_Status_Engine', 'resolve_shipment_status' ) ) {
                $resolved = \DX_Status_Engine::resolve_shipment_status( $raw_shipment, $has_identifiers );
                if ( is_array( $resolved ) ) {
                    return $resolved;
                }
            }

            $from_api = $this->build_fallback_status_message( $order, $data );
            if ( $from_api !== null ) {
                return $from_api;
            }
        }

        if ( is_array( $panel_msg ) ) {
            if ( ! empty( $panel_msg['title'] ) ) {
                $status_data['title'] = trim( (string) $panel_msg['title'] );
            }
            if ( ! empty( $panel_msg['message'] ) ) {
                $status_data['message'] = trim( (string) $panel_msg['message'] );
            }
            $status_raw = isset( $panel_msg['status'] ) ? $panel_msg['status'] : '';
            if ( is_numeric( $status_raw ) ) {
                $n = (int) $status_raw;
                if ( in_array( $n, [ 1, 2, 3, 4 ], true ) ) {
                    $status_data['status'] = $n;
                }
            } else {
                $status = trim( (string) $status_raw );
                if ( $status === 'warning' ) {
                    $status_data['status'] = 1;
                } elseif ( $status === 'error' ) {
                    $status_data['status'] = 2;
                } elseif ( $status === 'success' ) {
                    $status_data['status'] = 4;
                } else {
                    $status_data['status'] = 3;
                }
            }
        }

        if ( $status_data['message'] === '' ) {
            $legacy_text = $order->get_meta( self::META_DX_LAST_MESSAGE, true );
            if ( is_string( $legacy_text ) && $legacy_text !== '' ) {
                $status_data['message'] = trim( $legacy_text );
            }
        }

        if ( $status_data['status'] === 3 && $status_data['message'] !== '' ) {
            $legacy_status = $order->get_meta( self::META_DX_LAST_STATUS, true );
            if ( is_numeric( $legacy_status ) ) {
                $default = (int) $legacy_status;
                if ( in_array( $default, [1, 2, 3, 4], true ) ) {
                    $status_data['status'] = $default;
                }
            }
        }

        // Fallback logic if message is still empty
        if ( $status_data['message'] === '' ) {
            $fallback = $this->build_fallback_status_message( $order, $data );
            if ( $fallback !== null ) {
                $status_data['title']   = $fallback['title'] ?? $status_data['title'];
                $status_data['message'] = $fallback['message'];
                $status_data['status']  = $fallback['status'] ?? $status_data['status'];
            }
        }

        if ( $status_data['message'] === '' ) {
            $status_data['message'] = esc_html__( 'وضعیت نامشخص است', 'bwdk' );
        }

        return $status_data;
    }

    /**
     * Build fallback status message from raw data when panel message is empty.
     */
    protected function build_fallback_status_message( WC_Order $order, ?array $data ): ?array {
        // Prefer shipment_status-based resolution when shipment identifiers exist, so UI doesn't show
        // misleading "ثبت نشده" for UnregisteredShipment just because label/detail is missing/zero.
        $shipment_id   = '';
        $shipment_code = '';
        $raw_shipment  = '';
        if ( is_array( $data ) ) {
            $shipment_id   = isset( $data['shipment_id'] ) ? trim( (string) $data['shipment_id'] ) : '';
            $shipment_code = isset( $data['shipment_code'] ) ? trim( (string) $data['shipment_code'] ) : '';
            $raw_shipment  = isset( $data['shipment_status'] ) ? trim( (string) $data['shipment_status'] ) : '';
        }
        $has_identifiers = ( $shipment_id !== '' || $shipment_code !== '' );
        if ( $raw_shipment !== '' && class_exists( '\DX_Status_Engine', true ) && method_exists( '\DX_Status_Engine', 'resolve_shipment_status' ) ) {
            $resolved = \DX_Status_Engine::resolve_shipment_status( $raw_shipment, $has_identifiers );
            if ( is_array( $resolved ) ) {
                return $resolved;
            }
        }

        // selected_shipping_data.detail (API) before order meta and before normalized shipping_status (avoids stale "pending").
        if ( is_array( $data ) ) {
            $detail_arr = isset( $data['detail'] ) && is_array( $data['detail'] ) ? $data['detail'] : [];
            if ( ! empty( $detail_arr ) && class_exists( '\DX_Status_Engine', true ) ) {
                $resolved = \DX_Status_Engine::resolve_message( $detail_arr );
                if ( $resolved !== null ) {
                    return $resolved;
                }
            }
            $event    = is_array( $detail_arr ) ? ( $detail_arr['event_type'] ?? [] ) : [];
            $event_id = isset( $event['id'] ) ? (int) $event['id'] : ( isset( $data['event_id'] ) ? (int) $data['event_id'] : 0 );
            if ( $event_id > 0 ) {
                $title = $this->map_event_id_to_title( $event_id );
                if ( $title !== null ) {
                    $st = class_exists( '\DX_Status_Engine', true ) ? \DX_Status_Engine::STATUS_INFO : 3;
                    if ( in_array( $event_id, [ 18, 19 ], true ) ) {
                        $st = \DX_Status_Engine::STATUS_SUCCESS;
                    } elseif ( in_array( $event_id, [ 4, 5, 6, 7, 20 ], true ) ) {
                        $st = \DX_Status_Engine::STATUS_ERROR;
                    } elseif ( in_array( $event_id, [ 8, 9 ], true ) ) {
                        $st = \DX_Status_Engine::STATUS_WARNING;
                    }
                    return [
                        'title'   => $title,
                        'message' => $title,
                        'status'  => $st,
                    ];
                }
            }

            $progress_tr = isset( $event['progress_type_translation'] ) ? trim( (string) $event['progress_type_translation'] ) : '';
            if ( $progress_tr !== '' ) {
                return [
                    'title'   => null,
                    'message' => $progress_tr,
                    'status'  => 3,
                ];
            }

            $shipping_tr = isset( $event['shipping_status_translation'] ) ? trim( (string) $event['shipping_status_translation'] ) : '';
            if ( $shipping_tr !== '' ) {
                return [
                    'title'   => null,
                    'message' => $shipping_tr,
                    'status'  => 3,
                ];
            }

            $progress_type = isset( $event['progress_type'] ) ? trim( (string) $event['progress_type'] ) : '';
            if ( $progress_type === '' && isset( $data['progress_type'] ) ) {
                $progress_type = trim( (string) $data['progress_type'] );
            }
            if ( $progress_type !== '' ) {
                $translated_progress = $this->translate_progress_type( $progress_type );
                if ( $translated_progress !== '' ) {
                    return [
                        'title'   => null,
                        'message' => $translated_progress,
                        'status'  => 3,
                    ];
                }
            }

            if ( $raw_shipment !== '' ) {
                if ( class_exists( '\DX_Status_Engine', true ) && method_exists( '\DX_Status_Engine', 'resolve_shipment_status' ) ) {
                    $resolved = \DX_Status_Engine::resolve_shipment_status( $raw_shipment, $has_identifiers );
                    if ( is_array( $resolved ) ) {
                        return $resolved;
                    }
                }
                return [
                    'title'   => null,
                    'message' => $this->translate_shipment_status( $raw_shipment ),
                    'status'  => 3,
                ];
            }
        }

        $detail = $order->get_meta( self::META_DX_DETAIL, true );
        if ( is_array( $detail ) && class_exists( '\DX_Status_Engine', true ) ) {
            $resolved = \DX_Status_Engine::resolve_message( $detail );
            if ( $resolved !== null ) {
                return $resolved;
            }
        }

        $shipping_status = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        if ( is_string( $shipping_status ) && $shipping_status !== '' ) {
            return [
                'title'   => null,
                'message' => $this->translate_shipping_status( $shipping_status ),
                'status'  => 3,
            ];
        }

        return null;
    }

    /**
     * Map event_id to human-readable title (delegates to DX_Status_Engine::get_event_display_title).
     */
    protected function map_event_id_to_title( int $event_id ): ?string {
        if ( ! class_exists( '\DX_Status_Engine', true ) ) {
            return null;
        }
        $title = \DX_Status_Engine::get_event_display_title( $event_id );
        return $title !== '' ? $title : null;
    }

    /**
     * Translate progress_type.
     */
    protected function translate_progress_type( string $progress_type ): string {
        $translations = [
            'pending' => 'در انتظار',
            'in_progress' => 'در حال انجام',
            'completed' => 'تکمیل شده',
            'failed' => 'ناموفق',
        ];
        return $translations[ strtolower( $progress_type ) ] ?? '';
    }

    /**
     * Render status box using standardized color coding.
     */
    protected function render_dx_status_box( array $status_data ): void {
        $status      = isset( $status_data['status'] ) ? (int) $status_data['status'] : 3;
        $title       = isset( $status_data['title'] ) ? trim( (string) $status_data['title'] ) : '';
        $message     = isset( $status_data['message'] ) ? trim( (string) $status_data['message'] ) : '';
        $colors      = [
            1 => '#f7f2cc', // yellow
            2 => '#fdd',   // red
            3 => '#dfefff', // blue
            4 => '#e6f4ea', // green
        ];
        $border      = [
            1 => '#f1c40f',
            2 => '#e74c3c',
            3 => '#3498db',
            4 => '#2ecc71',
        ];

        $bg  = isset( $colors[ $status ] ) ? $colors[ $status ] : $colors[3];
        $bd  = isset( $border[ $status ] ) ? $border[ $status ] : $border[3];

        echo '<div style="background:' . esc_attr( $bg ) . '; border:1px solid ' . esc_attr( $bd ) . '; border-radius:4px; padding:10px; margin-bottom:10px;">';
        if ( $title !== '' ) {
            echo '<strong style="display:block; margin-bottom:4px;">' . esc_html( $title ) . '</strong>';
        }
        echo '<div>' . esc_html( $message ) . '</div>';
        echo '</div>';
    }

    /**
     * Render clear status badge at top.
     */
    protected function render_dx_status_badge( array $status_data ): void {
        $title   = isset( $status_data['title'] ) ? trim( (string) $status_data['title'] ) : '';
        $message = isset( $status_data['message'] ) ? trim( (string) $status_data['message'] ) : '';

        echo '<style>
        .bwdk-dx-current-status-card{position:relative;display:flex;align-items:center;justify-content:flex-end;gap:12px;min-height:38px;padding:10px 14px;margin:12px 0;border:1px solid #d9d9d9;border-radius:6px;background:#fff;direction:rtl;text-align:left}
        .bwdk-dx-current-status-message{color:#1f2937;font-size:13px;line-height:1.7;padding-inline-end:8px}
        .bwdk-dx-current-status-badge{display:inline-flex;align-items:center;justify-content:center;max-width:50%;padding:3px 9px;border-radius:0;background:#13a8a8;color:#fff;font-size:12px;font-weight:600;line-height:1.4;white-space:nowrap;position:absolute;right:-6px;top:15px}
        .bwdk-dx-current-status-badge:after{content:"";position:absolute;inset-inline-start:0;top:100%;width:0;height:0;border:3px solid #13a8a8;border-inline-start-color:transparent;border-block-end-color:transparent}
        .bwdk-dx-summary-table{margin:0 0 14px !important;max-width:100%}
        .bwdk-dx-summary-table th{text-align:right;font-weight:600;width:220px;vertical-align:top}
        .bwdk-dx-summary-table td{vertical-align:top}
        .bwdk-dx-actions-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:8px 0 12px;direction:rtl}
        .bwdk-dx-actions-row form{margin:0}
        .bwdk-dx-cancel-shipment-wrap form{margin:0}
        .bwdk-dx-actions-row .bwdk-dx-btn--cancel,.bwdk-dx-cancel-shipment-wrap .bwdk-dx-btn--cancel{background:#ef4444;border-color:#ef4444;color:#fff}
        .bwdk-dx-actions-row .bwdk-dx-btn--cancel:hover,.bwdk-dx-actions-row .bwdk-dx-btn--cancel:focus,
        .bwdk-dx-cancel-shipment-wrap .bwdk-dx-btn--cancel:hover,.bwdk-dx-cancel-shipment-wrap .bwdk-dx-btn--cancel:focus{background:#dc2626;border-color:#dc2626;color:#fff}
        .bwdk-dx-change-shipping-card{position:relative;margin:12px 0 0;padding:14px;border:1px solid #d9d9d9;border-radius:6px;background:#fff;direction:rtl;text-align:left}
        .bwdk-dx-change-shipping-badge{display:inline-flex;align-items:center;justify-content:center;max-width:80%;padding:3px 9px;border-radius:0;background:#13a8a8;color:#fff;font-size:12px;font-weight:600;line-height:1.4;white-space:nowrap;position:absolute;right:-6px;top:15px}
        .bwdk-dx-change-shipping-badge:after{content:"";position:absolute;inset-inline-start:0;top:100%;width:0;height:0;border:3px solid #13a8a8;border-inline-start-color:transparent;border-block-end-color:transparent}
        .bwdk-dx-change-shipping-body{margin-top:35px;text-align:right}
        .bwdk-dx-change-shipping-card .description{margin:6px 0 0;color:#646970;font-size:12px}
        @media (max-width:782px){.bwdk-dx-change-shipping-badge{position:relative;right:auto;top:auto;max-width:100%;white-space:normal}.bwdk-dx-change-shipping-badge:after{display:none}}
        @media (max-width:782px){.bwdk-dx-current-status-card{align-items:flex-start;flex-direction:column-reverse}.bwdk-dx-current-status-badge{position:relative;right:auto;top:auto;max-width:100%;white-space:normal}.bwdk-dx-current-status-badge:after{display:none}}
        </style>';

        echo '<div class="bwdk-dx-current-status-card" dir="rtl">';
        echo '<div class="bwdk-dx-current-status-message">' . esc_html( $message ) . '</div>';
        if ( $title !== '' ) {
            echo '<span class="bwdk-dx-current-status-badge">' . esc_html( $title ) . '</span>';
        }
        echo '</div>';
    }

    /**
     * Render refresh button for DigiExpress data.
     */
    protected function render_dx_refresh_button( WC_Order $order ): void {
        $order_id = (int) $order->get_id();
        if ( $order_id <= 0 ) {
            return;
        }

        // Queue a real <form> in admin footer; the visible button submits that form via the "form" attribute.
        $this->queued_refresh_forms[ $order_id ] = true;
        $form_id = 'bwdk-dx-refresh-form-' . $order_id;

        echo '<button type="submit" form="' . esc_attr( $form_id ) . '" class="button button-primary bwdk-dx-btn bwdk-dx-btn--refresh">';
        esc_html_e( 'بروزرسانی وضعیت دیجی‌اکسپرس', 'bwdk' );
        echo '</button>';
    }

    /**
     * Cancel shipment button (POST admin-post) when pre-pickup; otherwise muted hint.
     * Self-contained form only (nonce/action must match handle_cancel_shipment()).
     */
    protected function render_dx_cancel_shipment_button( WC_Order $order, ?array $dx_data ): void {
        if ( ! is_array( $dx_data ) || empty( $dx_data ) ) {
            return;
        }

        $dx_status = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        if ( $dx_status === 'cancelled' || $dx_status === 'switched_to_other' ) {
            return;
        }

        if ( ! bwdk_can_cancel_dx_shipment( $dx_data ) ) {
            echo '<p class="description" style="color:#646970; font-size:12px; margin:4px 0 10px;">' . esc_html__( 'لغو ارسال فقط قبل از pickup امکان‌پذیر است.', 'bwdk' ) . '</p>';
            return;
        }

        $order_id = (int) $order->get_id();

        echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
        wp_nonce_field( self::CANCEL_SHIPMENT_NONCE_PREFIX . $order_id );
        echo '<input type="hidden" name="action" value="' . esc_attr( self::CANCEL_SHIPMENT_ACTION ) . '">';
        echo '<input type="hidden" name="order_id" value="' . esc_attr( (string) $order_id ) . '">';
        echo '<button type="submit" class="button bwdk-dx-btn bwdk-dx-btn--cancel" onclick="return confirm(\'' . esc_js( __( 'آیا از لغو ارسال دیجی‌اکسپرس مطمئن هستید؟', 'bwdk' ) ) . '\');">';
        esc_html_e( 'لغو ارسال', 'bwdk' );
        echo '</button>';
        echo '</form>';
    }

    /**
     * Revive shipment UI (POST admin-post) with preparation_time days input; only shown when backend allows revive.
     */
    protected function render_dx_revive_shipment_ui( WC_Order $order, ?array $dx_data ): void {
        if ( ! is_array( $dx_data ) || empty( $dx_data ) ) {
            return;
        }

        $dx_status = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        if ( $dx_status === 'cancelled' || $dx_status === 'switched_to_other' ) {
            return;
        }

        $dx_status_meta = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        $dx_event_id_meta = $order->get_meta( self::META_DX_EVENT_ID, true );
        $method_meta = $order->get_meta( self::META_SELECTED_SHIPPING_METHOD, true );
        $shipment = $dx_data;
        if ( is_array( $method_meta ) && ! empty( $method_meta ) ) {
            $shipment['selected_shipping_method'] = $method_meta;
        }
        if ( is_string( $dx_status_meta ) && $dx_status_meta !== '' ) {
            $shipment['bwdk_dx_shipping_status'] = $dx_status_meta;
        }
        if ( is_numeric( $dx_event_id_meta ) ) {
            $shipment['bwdk_dx_event_id'] = (int) $dx_event_id_meta;
        }
        // progress_type can exist at root; pass through for possession detection.
        if ( isset( $dx_data['progress_type'] ) && is_string( $dx_data['progress_type'] ) ) {
            $shipment['progress_type'] = $dx_data['progress_type'];
        }

        $svc = class_exists( '\BWDK_DigiExpress_Service', true ) ? new \BWDK_DigiExpress_Service() : null;
        $action = is_object( $svc ) ? $svc->decide_digiexpress_action( $shipment, null ) : null;
        $is_allowed = ( $action === ( class_exists( '\BWDK_DigiExpress_Service', true ) ? \BWDK_DigiExpress_Service::ACTION_REVIVE : 'revive' ) );
        $order_id   = (int) $order->get_id();

        echo '<div class="bwdk-dx-revive-shipment" style="margin:10px 0 12px; padding:10px; border:1px solid #dcdcde; border-radius:4px; background:#fff;">';

        if ( ! $is_allowed ) {
            if ( $action === ( class_exists( '\BWDK_DigiExpress_Service', true ) ? \BWDK_DigiExpress_Service::ACTION_CREATE_NEW_SHIPMENT : 'create_new_shipment' ) ) {
                echo '<p class="description" style="margin:0; color:#646970; font-size:12px;">' . esc_html__( 'تحویل ناموفق است؛ revive معنی ندارد و باید درخواست جدید برای دیجی‌اکسپرس ساخته شود.', 'bwdk' ) . '</p>';
            } else {
                echo '<p class="description" style="margin:0; color:#646970; font-size:12px;">' . esc_html__( 'در این مرحله امکان فعال‌سازی مجدد ارسال وجود ندارد.', 'bwdk' ) . '</p>';
            }
            echo '</div>';
            return;
        }

        echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
        wp_nonce_field( self::REVIVE_SHIPMENT_NONCE_PREFIX . $order_id );
        echo '<input type="hidden" name="action" value="' . esc_attr( self::REVIVE_SHIPMENT_ACTION ) . '" />';
        echo '<input type="hidden" name="order_id" value="' . esc_attr( (string) $order_id ) . '" />';

        $field_id = 'bwdk-dx-revive-prep-time-' . $order_id;
        echo '<label for="' . esc_attr( $field_id ) . '" style="display:block; margin-bottom:6px; font-weight:600;">' . esc_html( 'تعداد روز اضافه به زمان آماده‌سازی / pickup' ) . '</label>';
        echo '<input id="' . esc_attr( $field_id ) . '" type="number" name="preparation_time" min="0" step="1" value="0" style="width:120px;" />';
        echo '<p class="description" style="margin:6px 0 0; color:#646970; font-size:12px;">' . esc_html( 'این عدد به زمان آماده‌سازی / pickup اضافه می‌شود.' ) . '</p>';

        echo '<p style="margin:10px 0 0;">';
        echo '<button type="submit" class="button button-secondary">' . esc_html( 'فعال‌سازی مجدد ارسال' ) . '</button>';
        echo '</p>';
        echo '</form>';
        echo '</div>';
    }

    /**
     * Render "change shipping method" UI: a button that reveals a select of enabled WooCommerce shipping methods.
     */
    protected function render_dx_change_shipping_method_ui( WC_Order $order, ?array $dx_data = null ): void {
        $resolved = $this->get_available_wc_shipping_methods_for_order( $order );
        $options  = $resolved['options'] ?? [];
        $meta_map = $resolved['meta'] ?? [];
        $source   = $resolved['source'] ?? 'none';
        $zone     = $resolved['zone'] ?? null;
        $has_methods = is_array( $options ) && ! empty( $options );
        $is_allowed = is_array( $dx_data ) ? bwdk_can_change_dx_shipping( $dx_data ) : false;

        $order_id = (int) $order->get_id();
        $field_id = 'bwdk-dx-change-shipping-' . $order_id;

        $current_id = $order->get_meta( self::META_DX_WC_SHIPPING_METHOD_ID, true );
        $current_instance_id = $order->get_meta( self::META_DX_WC_SHIPPING_METHOD_INSTANCE_ID, true );
        $current_title = $order->get_meta( self::META_DX_WC_SHIPPING_METHOD_TITLE, true );
        if ( ! is_string( $current_id ) ) {
            $current_id = '';
        }
        $current_instance_id_int = is_numeric( $current_instance_id ) ? (int) $current_instance_id : 0;
        if ( ! is_string( $current_title ) ) {
            $current_title = '';
        }

        $current_value = '';
        if ( $current_id !== '' ) {
            $current_value = $current_instance_id_int > 0 ? ( $current_id . ':' . (string) $current_instance_id_int ) : $current_id;
        }

        $default_selected = $current_value !== ''
            ? $current_value
            : ( $has_methods ? (string) array_key_first( $options ) : '' );

        echo '<div class="bwdk-dx-change-shipping-card" dir="rtl">';
        echo '<span class="bwdk-dx-change-shipping-badge">' . esc_html__( 'تغییر روش ارسال', 'bwdk' ) . '</span>';
        echo '<div class="bwdk-dx-change-shipping-body">';
        if ( $current_id !== '' ) {
            $label = $current_title !== '' ? $current_title : ( $current_instance_id_int > 0 ? ( $current_id . ':' . (string) $current_instance_id_int ) : $current_id );
            echo '<p class="description">' . esc_html__( 'روش انتخاب‌شده:', 'bwdk' ) . ' <code>' . esc_html( $label ) . '</code></p>';
        }

        // Form is visible by default (some admin setups block inline JS/click handlers).
        echo '<div id="' . esc_attr( $field_id ) . '">';
        echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '">';
        wp_nonce_field( self::CHANGE_SHIPPING_NONCE_PREFIX . $order_id );
        echo '<input type="hidden" name="action" value="' . esc_attr( self::CHANGE_SHIPPING_ACTION ) . '" />';
        echo '<input type="hidden" name="order_id" value="' . esc_attr( (string) $order_id ) . '" />';

        echo '<label for="' . esc_attr( $field_id . '-select' ) . '" style="display:block; margin-bottom:6px; font-weight:600;">' . esc_html__( 'تغییر روش ارسال از دیجی اکسپرس به سایر روش های ارسال', 'bwdk' ) . '</label>';
        echo '<select id="' . esc_attr( $field_id . '-select' ) . '" name="new_shipping_method" style="min-width:280px; max-width:100%;">';
        if ( $has_methods ) {
            foreach ( $options as $value => $label ) {
                $selected = ( $default_selected === $value ) ? 'selected="selected"' : '';
                echo '<option value="' . esc_attr( (string) $value ) . '" ' . $selected . '>' . esc_html( (string) $label ) . '</option>';
            }
        } else {
            echo '<option value="">' . esc_html__( 'هیچ روش ارسال فعالی در ووکامرس پیدا نشد.', 'bwdk' ) . '</option>';
        }
        echo '</select>';

        if ( ! $is_allowed ) {
            echo '<p class="description" style="margin:8px 0 0;">' . esc_html__( 'در این مرحله امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ) . '</p>';
        } elseif ( $source === 'all_zones' ) {
            echo '<p class="description" style="margin:8px 0 0;">' . esc_html__( 'روش‌ها از همه ناحیه‌های فعال نمایش داده شده‌اند.', 'bwdk' ) . '</p>';
        } elseif ( ! $has_methods ) {
            echo '<p class="description" style="margin:8px 0 0;">' . esc_html__( 'هیچ روش ارسال فعالی در ووکامرس پیدا نشد.', 'bwdk' ) . '</p>';
        }

        // Do not render internal shipping-method debug in the main UI. (Keep it in Debug section instead.)

        echo '<p style="margin:10px 0 0;">';
        $can_submit = $has_methods && $is_allowed;
        $disabled_attr = $can_submit ? '' : 'disabled="disabled"';
        echo '<button type="submit" class="button button-primary" ' . $disabled_attr . ' onclick="return confirm(\'' . esc_js( __( 'این عملیات، ارسال دیجی‌اکسپرس را لغو کرده و روش ارسال را به حالت عادی تغییر می‌دهد. روش انتخابی ووکامرس فقط به‌صورت محلی در وردپرس ذخیره می‌شود. ادامه می‌دهید؟', 'bwdk' ) ) . '\');">';
        echo esc_html__( 'اعمال تغییر', 'bwdk' );
        echo '</button>';
        echo '</p>';

        echo '</form>';
        echo '</div>';
        echo '</div>';
        echo '</div>';
    }

    /**
     * Resolve available WooCommerce shipping methods for an order using zones/instances.
     *
     * - Prefer the matched shipping zone for the order destination
     * - Fallback to enabled methods from all zones (including "rest of the world")
     *
     * IMPORTANT: WooCommerce shipping methods are zone/instance-based; global method classes are not enough.
     *
     * @return array{options: array<string,string>, meta: array<string,array{method_id:string, instance_id:int, title:string}>, source: 'matched_zone'|'all_zones'|'none', zone?: array{id:int,name:string}}
     */
    protected function get_available_wc_shipping_methods_for_order( WC_Order $order ): array {
        $out = [
            'options' => [],
            'meta'    => [],
            'source'  => 'none',
        ];

        if ( ! class_exists( '\WC_Shipping_Zones' ) ) {
            return $out;
        }

        $package = [
            'destination' => [
                'country'   => (string) $order->get_shipping_country(),
                'state'     => (string) $order->get_shipping_state(),
                'postcode'  => (string) $order->get_shipping_postcode(),
                'city'      => (string) $order->get_shipping_city(),
                'address'   => (string) $order->get_shipping_address_1(),
                'address_2' => (string) $order->get_shipping_address_2(),
            ],
            // Keep contents empty; we only need zone matching for destination.
            'contents'        => [],
            'contents_cost'   => 0,
            'applied_coupons' => [],
            'user'            => [ 'ID' => (int) $order->get_user_id() ],
        ];

        // 1) Matched zone methods.
        $zone = null;
        if ( is_callable( [ '\WC_Shipping_Zones', 'get_zone_matching_package' ] ) ) {
            $zone = \WC_Shipping_Zones::get_zone_matching_package( $package );
        }
        if ( $zone && is_object( $zone ) && is_callable( [ $zone, 'get_shipping_methods' ] ) ) {
            $methods = $zone->get_shipping_methods( true );
            $this->append_enabled_zone_methods( $methods, $out['options'], $out['meta'] );
            if ( ! empty( $out['options'] ) ) {
                $out['source'] = 'matched_zone';
                if ( is_callable( [ $zone, 'get_id' ] ) ) {
                    $out['zone'] = [
                        'id'   => (int) $zone->get_id(),
                        'name' => is_callable( [ $zone, 'get_zone_name' ] ) ? (string) $zone->get_zone_name() : '',
                    ];
                }
                return $out;
            }
        }

        // 2) Fallback: all zones + "rest of the world".
        $all = [];
        if ( is_callable( [ '\WC_Shipping_Zones', 'get_zones' ] ) ) {
            $all = \WC_Shipping_Zones::get_zones();
        }
        if ( is_array( $all ) ) {
            foreach ( $all as $z ) {
                if ( is_array( $z ) && isset( $z['id'] ) ) {
                    $zobj = \WC_Shipping_Zones::get_zone( (int) $z['id'] );
                    if ( $zobj && is_object( $zobj ) && is_callable( [ $zobj, 'get_shipping_methods' ] ) ) {
                        $methods = $zobj->get_shipping_methods( true );
                        $this->append_enabled_zone_methods( $methods, $out['options'], $out['meta'] );
                    }
                }
            }
        }

        // Rest of the world zone (id = 0).
        $row = \WC_Shipping_Zones::get_zone( 0 );
        if ( $row && is_object( $row ) && is_callable( [ $row, 'get_shipping_methods' ] ) ) {
            $methods = $row->get_shipping_methods( true );
            $this->append_enabled_zone_methods( $methods, $out['options'], $out['meta'] );
        }

        if ( ! empty( $out['options'] ) ) {
            $out['source'] = 'all_zones';
        }
        return $out;
    }

    /**
     * Append enabled shipping method instances to options/meta maps.
     *
     * @param array $methods List of WC_Shipping_Method objects (instances).
     * @param array $options value => label
     * @param array $meta    value => [method_id, instance_id, title]
     */
    protected function append_enabled_zone_methods( array $methods, array &$options, array &$meta ): void {
        foreach ( $methods as $m ) {
            if ( ! is_object( $m ) ) {
                continue;
            }
            $enabled = isset( $m->enabled ) ? (string) $m->enabled : '';
            if ( $enabled !== 'yes' ) {
                continue;
            }
            $method_id = isset( $m->id ) ? (string) $m->id : '';
            if ( $method_id === '' ) {
                continue;
            }
            $instance_id = isset( $m->instance_id ) ? (int) $m->instance_id : 0;
            $title = '';
            if ( isset( $m->title ) && is_string( $m->title ) && trim( $m->title ) !== '' ) {
                $title = trim( $m->title );
            } elseif ( isset( $m->method_title ) && is_string( $m->method_title ) && trim( $m->method_title ) !== '' ) {
                $title = trim( $m->method_title );
            } else {
                $title = $instance_id > 0 ? ( $method_id . ':' . (string) $instance_id ) : $method_id;
            }

            $value = $instance_id > 0 ? ( $method_id . ':' . (string) $instance_id ) : $method_id;
            if ( isset( $options[ $value ] ) ) {
                continue;
            }
            $options[ $value ] = $title;
            $meta[ $value ] = [
                'method_id'    => $method_id,
                'instance_id'  => $instance_id,
                'title'        => $title,
            ];
        }
    }

    /**
     * Render DigiExpress history timeline.
     */
    protected function render_dx_history( WC_Order $order ): void {
        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) || empty( $history ) ) {
            return;
        }

        usort( $history, static function ( $a, $b ) {
            $tz = wp_timezone();
            $t1 = 0;
            $t2 = 0;
            try {
                if ( isset( $a['timestamp'] ) && $a['timestamp'] !== '' && $a['timestamp'] !== null ) {
                    $t1 = is_numeric( $a['timestamp'] )
                        ? (int) $a['timestamp']
                        : ( new \DateTimeImmutable( (string) $a['timestamp'], $tz ) )->getTimestamp();
                }
                if ( isset( $b['timestamp'] ) && $b['timestamp'] !== '' && $b['timestamp'] !== null ) {
                    $t2 = is_numeric( $b['timestamp'] )
                        ? (int) $b['timestamp']
                        : ( new \DateTimeImmutable( (string) $b['timestamp'], $tz ) )->getTimestamp();
                }
            } catch ( \Exception $e ) {
                // Keep as 0 for invalid values.
            }
            return $t1 <=> $t2;
        } );

        echo '<h4 style="margin-top:0;">' . esc_html__( 'تاریخچه وضعیت دیجی‌اکسپرس', 'bwdk' ) . '</h4>';
        echo '<style>
        .bwdk-dx-steps{margin-top:12px;direction:rtl}
        .bwdk-dx-step{position:relative;display:grid;grid-template-columns:14px 1fr;column-gap:12px;padding-bottom:18px}
        .bwdk-dx-step:last-child{padding-bottom:0}
        .bwdk-dx-step-tail{position:absolute;top:17px;right:6px;width:2px;height:calc(100% - 13px);background:#d9d9d9}
        .bwdk-dx-step:last-child .bwdk-dx-step-tail{display:none}
        .bwdk-dx-step-marker{position:relative;z-index:1;width:8px;height:8px;margin-top:6px;margin-inline:auto;border-radius:50%;background:#1677ff}
        .bwdk-dx-step.is-waiting .bwdk-dx-step-marker,
        .bwdk-dx-step.is-inactive .bwdk-dx-step-marker{background:#8c8c8c}
        .bwdk-dx-step.is-waiting .bwdk-dx-step-tail,
        .bwdk-dx-step.is-inactive .bwdk-dx-step-tail{background:#8c8c8c}
        .bwdk-dx-step-content{min-width:0;padding-top:2px}
        .bwdk-dx-step-head{display:flex;align-items:baseline;justify-content:space-between;gap:12px}
        .bwdk-dx-step-title{color:#1f2937;font-size:13px;font-weight:600;line-height:1.5}
        .bwdk-dx-step-description{margin-top:4px;color:#4b5563;font-size:12px;line-height:1.6}
        .bwdk-dx-step-time{color:#8c8c8c;font-size:11px;white-space:nowrap}
        @media (max-width:782px){.bwdk-dx-step-head{display:block}.bwdk-dx-step-time{display:block;margin-top:2px}}
        </style>';

        echo '<div class="bwdk-dx-steps" dir="rtl">';
        $history_newest_first = array_reverse( $history );
        $i = 0;
        foreach ( $history_newest_first as $item ) {
            $event_id  = isset( $item['event_id'] ) ? (int) $item['event_id'] : 0;
            $message   = isset( $item['message'] ) ? (string) $item['message'] : '';
            $timestamp = isset( $item['timestamp'] ) ? (string) $item['timestamp'] : '';
            $status    = isset( $item['status'] ) ? (int) $item['status'] : 0;

            $custom_title = isset( $item['title'] ) ? trim( (string) $item['title'] ) : '';
            $human_title  = $custom_title !== '' ? $custom_title : $this->get_event_title( $event_id );
            if ( $human_title === '' ) {
                $human_title = esc_html__( 'رویداد', 'bwdk' ) . ' ' . (string) $event_id;
            }

            $human_title_ui = bwdk_dx_localize_dates_in_text_for_ui( $human_title );
            $message_ui     = bwdk_dx_localize_dates_in_text_for_ui( $message );

            $classes = 'bwdk-dx-step';
            if ( $i === 0 ) {
                $classes .= ' is-current';
            } elseif ( class_exists( '\DX_Status_Engine', true ) && $status === \DX_Status_Engine::STATUS_INFO ) {
                $classes .= ' is-waiting';
            }
            echo '<div class="' . esc_attr( $classes ) . '">';
            echo '<div class="bwdk-dx-step-tail"></div>';
            echo '<div class="bwdk-dx-step-marker" aria-hidden="true"></div>';
            echo '<div class="bwdk-dx-step-content">';
            echo '<div class="bwdk-dx-step-head">';
            echo '<strong class="bwdk-dx-step-title">' . esc_html( $human_title_ui ) . '</strong>';
            if ( $timestamp !== '' && strpos( $timestamp, 'T' ) !== false && function_exists( 'bwdk_dx_localize_dates_in_text_for_ui' ) ) {
                $ts_ui = bwdk_dx_localize_dates_in_text_for_ui( $timestamp );
            } else {
                $ts_ui = bwdk_dx_format_datetime_for_ui( $timestamp );
            }
            if ( $ts_ui !== '-' ) {
                echo '<span class="bwdk-dx-step-time">' . esc_html( $ts_ui ) . '</span>';
            }
            echo '</div>';
            if ( $message_ui !== '' ) {
                echo '<div class="bwdk-dx-step-description">' . esc_html( $message_ui ) . '</div>';
            }
            echo '</div>';
            echo '</div>';
            $i++;
        }
        echo '</div>';
    }

    /**
     * Get human-readable title from event ID (delegates to DX_Status_Engine::get_event_display_title).
     */
    protected function get_event_title( int $event_id ): string {
        if ( ! class_exists( '\DX_Status_Engine', true ) ) {
            return '';
        }
        return \DX_Status_Engine::get_event_display_title( $event_id );
    }

    /**
     * Direct PDF label link for the actions row (no admin-post).
     */
    protected function render_dx_label_preview( WC_Order $order, ?array $data ): void {
        $pdf_label = trim( (string) $order->get_meta( self::META_DX_PDF_LABEL, true ) );
        if ( $pdf_label === '' ) {
            return;
        }
        echo '<a href="' . esc_url( $pdf_label ) . '" target="_blank" rel="noopener noreferrer" class="button button-secondary bwdk-dx-btn bwdk-dx-btn--label">';
        echo esc_html__( 'چاپ لیبل دیجی‌اکسپرس', 'bwdk' );
        echo '</a>';
    }

    /**
     * Shipment code + cargo-ready above the status card (same datetime formatter as timeline).
     */
    protected function render_dx_summary_key_facts( ?array $data, ?WC_Order $order = null ): void {
        $row       = is_array( $data ) ? $data : [];
        $code      = isset( $row['shipment_code'] ) ? trim( (string) $row['shipment_code'] ) : '';
        $cargo_raw = isset( $row['cargo_ready_date_time'] ) ? trim( (string) $row['cargo_ready_date_time'] ) : '';
        $cargo_at  = '';
        if ( $order instanceof WC_Order ) {
            $cargo_at = trim( (string) $order->get_meta( self::META_DX_CARGO_READY_AT, true ) );
        }
        if ( $order instanceof WC_Order && $code === '' ) {
            $code = trim( (string) $order->get_meta( self::META_DX_SHIPMENT_CODE, true ) );
        }
        if ( $code === '' && $cargo_raw === '' && $cargo_at === '' ) {
            return;
        }
        if ( $cargo_at !== '' && function_exists( 'dx_parse_order_meta_datetime_utc_to_timestamp' ) && function_exists( 'dx_format_jalali_datetime' ) ) {
            $cargo_ts = dx_parse_order_meta_datetime_utc_to_timestamp( $cargo_at );
            $formatted = ( $cargo_ts !== null )
                ? dx_format_jalali_datetime( $cargo_ts )
                : '-';
            echo '<div class="dx-cargo-ready" dir="rtl" style="margin:0 0 10px 0;">' . esc_html__( 'Cargo Ready', 'bwdk' ) . ': ' . esc_html( $formatted ) . '</div>';
        }
        echo '<table class="widefat striped bwdk-dx-summary-table" dir="rtl"><tbody>';
        if ( $code !== '' ) {
            echo '<tr><th scope="row">' . esc_html__( 'کد ارسال', 'bwdk' ) . '</th><td><code>' . esc_html( $code ) . '</code></td></tr>';
        }
        if ( $cargo_raw !== '' ) {
            $cargo_disp = bwdk_dx_format_datetime_for_ui( $cargo_raw );
            echo '<tr><th scope="row">' . esc_html__( 'تاریخ بارگیری از فروشگاه', 'bwdk' ) . '</th><td>';
            echo esc_html( $cargo_disp );
            echo '</td></tr>';
        }
        echo '</tbody></table>';
    }

    /**
     * Whether BWDK order JSON / merged meta indicates DigiExpress shipping.
     *
     * Uses selected_shipping_method and selected_shipping_data first; legacy root shipping_type only as fallback.
     * event_type.id === 0 is still DX (early/unregistered); do not use event id alone to exclude DX.
     */
    protected function is_digi_express( array $details ): bool {
        $method = isset( $details['selected_shipping_method'] ) && is_array( $details['selected_shipping_method'] )
            ? $details['selected_shipping_method']
            : [];

        if ( isset( $method['shipping_type'] ) && (int) $method['shipping_type'] === 2 ) {
            return true;
        }

        if ( isset( $method['get_shipping_type_display'] ) ) {
            $disp = trim( (string) $method['get_shipping_type_display'] );
            if ( $disp !== '' && strcasecmp( $disp, 'digi express' ) === 0 ) {
                return true;
            }
        }

        $name = isset( $method['name'] ) ? (string) $method['name'] : '';
        if ( $this->shipping_method_name_looks_like_digi_express( $name ) ) {
            return true;
        }

        if ( isset( $details['selected_shipping_data'] ) && is_array( $details['selected_shipping_data'] ) ) {
            if ( $this->selected_shipping_data_indicates_digi_express( $details['selected_shipping_data'] ) ) {
                return true;
            }
        }

        $st = $details['shipping_type'] ?? null;
        if ( $st !== null && $st !== '' ) {
            if ( is_numeric( $st ) && (int) $st === 2 ) {
                return true;
            }
            if ( is_string( $st ) && ( stripos( $st, 'digi' ) !== false || stripos( $st, 'express' ) !== false ) ) {
                return true;
            }
        }

        return false;
    }

    /**
     * Persian/English labels for DigiExpress on selected_shipping_method.name (handles ZWJ and spacing variants).
     */
    protected function shipping_method_name_looks_like_digi_express( string $name ): bool {
        if ( $name === '' ) {
            return false;
        }
        $n = str_replace( "\xE2\x80\x8C", '', $name );
        if ( function_exists( 'mb_stripos' ) ) {
            if ( mb_stripos( $n, 'digi express', 0, 'UTF-8' ) !== false ) {
                return true;
            }
            if ( mb_stripos( $n, 'دیجی', 0, 'UTF-8' ) !== false && mb_stripos( $n, 'اکسپرس', 0, 'UTF-8' ) !== false ) {
                return true;
            }
        } else {
            if ( stripos( $n, 'digi express' ) !== false ) {
                return true;
            }
        }
        return false;
    }

    /**
     * DX shipment payload signals (shipment fields or detail.event_type present; event id may be 0).
     */
    protected function selected_shipping_data_indicates_digi_express( array $ssd ): bool {
        if ( array_key_exists( 'shipment_id', $ssd ) && $ssd['shipment_id'] !== null && $ssd['shipment_id'] !== '' ) {
            return true;
        }
        if ( array_key_exists( 'shipment_code', $ssd ) && $ssd['shipment_code'] !== null && trim( (string) $ssd['shipment_code'] ) !== '' ) {
            return true;
        }
        $detail = isset( $ssd['detail'] ) && is_array( $ssd['detail'] ) ? $ssd['detail'] : [];
        if ( array_key_exists( 'event_type', $detail ) && is_array( $detail['event_type'] ) ) {
            return true;
        }
        return false;
    }

    /**
     * Render meta box: DigiExpress details or fallback messages.
     */
    public function render_meta_box( $post ): void {
        error_log('DX METABOX RENDER ENTERED');
        if ( ! function_exists( 'bwdk_is_digiexpress_enabled' ) || ! bwdk_is_digiexpress_enabled() ) {
            return;
        }
        $order_id = 0;
        if ( $post instanceof WC_Order ) {
            $order_id = $post->get_id();
        } elseif ( is_object( $post ) && isset( $post->ID ) ) {
            $order_id = (int) $post->ID;
        }

        $order = $order_id > 0 ? wc_get_order( $order_id ) : false;
        if ( ! $order || ! $order instanceof WC_Order ) {
            echo '<p>' . esc_html__( 'سفارش یافت نشد.', 'bwdk' ) . '</p>';
            return;
        }

        $order_uuid = $order->get_meta( '_bwdk_order_uuid', true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            echo '<p>' . esc_html__( 'این سفارش BWDK نیست.', 'bwdk' ) . '</p>';
            return;
        }

        $data = $this->get_dx_data_for_display( $order );
        $has_dx_data = ( is_array( $data ) && ! empty( $data ) );
        if ( ! $has_dx_data ) {
            $details = $this->get_bwdk_details_for_dx_detection( $order );
            if ( is_array( $details ) && ! empty( $details ) && ! $this->is_digi_express( $details ) ) {
                echo '<p>' . esc_html__( 'روش ارسال دیجی‌اکسپرس نیست.', 'bwdk' ) . '</p>';
            } else {
                echo '<p>' . esc_html__( 'دیتای دیجی‌اکسپرس هنوز ایجاد نشده است.', 'bwdk' ) . '</p>';
            }
        }

        $panel_msg = $order->get_meta( self::META_DX_PANEL_MESSAGE, true );
        if ( ! is_array( $panel_msg ) || ( empty( $panel_msg['title'] ) && empty( $panel_msg['message'] ) ) ) {
            $last_message = $order->get_meta( self::META_DX_LAST_MESSAGE, true );
            $last_status  = $order->get_meta( self::META_DX_LAST_STATUS, true );

            if ( is_string( $last_message ) && $last_message !== '' ) {
                $panel_msg = [
                    'title'   => null,
                    'message' => $last_message,
                    'status'  => is_numeric( $last_status ) ? ( $last_status === \DX_Status_Engine::STATUS_SUCCESS ? BWDK_DX_STATUS_SUCCESS : ( $last_status === \DX_Status_Engine::STATUS_ERROR ? BWDK_DX_STATUS_ERROR : ( $last_status === \DX_Status_Engine::STATUS_WARNING ? BWDK_DX_STATUS_WARNING : BWDK_DX_STATUS_INFO ) ) ) : BWDK_DX_STATUS_INFO,
                ];
            }
        }

        $data_ui     = $has_dx_data ? $this->apply_cancelled_display_overrides( $data, $order ) : null;
        $status_data = $this->get_dx_status_data( $order, $panel_msg, is_array( $data_ui ) ? $data_ui : null );

        // Status Summary Section
        try {
            echo '<div class="bwdk-status-summary" style="margin-bottom:20px;">';
            $this->render_dx_summary_key_facts( is_array( $data_ui ) ? $data_ui : null, $order );
            $this->render_dx_status_badge( $status_data );
            if ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'cancelled' ) {
                echo '<p style="margin:0 0 10px 0;"><span style="display:inline-block;background:#e8f4fd;border:1px solid #3498db;color:#1d6fa5;padding:4px 10px;border-radius:3px;font-size:12px;">' . esc_html__( 'ارسال دیجی‌اکسپرس لغو شده است.', 'bwdk' ) . '</span></p>';
            }
            if ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'switched_to_other' ) {
                echo '<p style="margin:0 0 10px 0;"><span style="display:inline-block;background:#f6f7f7;border:1px solid #8c8f94;color:#2c3338;padding:4px 10px;border-radius:3px;font-size:12px;">' . esc_html__( 'روش ارسال از دیجی‌اکسپرس تغییر یافته است.', 'bwdk' ) . '</span></p>';
            }
            echo '<div class="bwdk-dx-actions-row" dir="rtl">';
            $this->render_dx_refresh_button( $order );
            $this->render_dx_label_preview( $order, is_array( $data_ui ) ? $data_ui : null );
            echo '</div>';
            $this->render_dx_revive_shipment_ui( $order, is_array( $data_ui ) ? $data_ui : null );
            $this->render_dx_change_shipping_method_ui( $order, is_array( $data_ui ) ? $data_ui : null );
            echo '</div>';
        } catch ( \Throwable $e ) {
            $error_msg = esc_html__( 'خطا در نمایش بخش خلاصه وضعیت.', 'bwdk' );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                $error_msg .= ' ' . esc_html__( 'جزئیات خطا:', 'bwdk' ) . ' ' . esc_html( $e->getMessage() );
            }
            echo '<div class="notice notice-error inline" style="margin-bottom:20px;"><p>' . $error_msg . '</p></div>';
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX META BOX] Status summary error: ' . $e->getMessage() );
            }
        }

        if ( $has_dx_data ) {
            // Timeline Section
            try {
                echo '<div class="bwdk-timeline" style="margin-bottom:20px;">';
                $this->render_dx_history( $order );
                echo '</div>';
            } catch ( \Throwable $e ) {
                $error_msg = esc_html__( 'خطا در نمایش بخش تاریخچه.', 'bwdk' );
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                    $error_msg .= ' ' . esc_html__( 'جزئیات خطا:', 'bwdk' ) . ' ' . esc_html( $e->getMessage() );
                }
                echo '<div class="notice notice-error inline" style="margin-bottom:20px;"><p>' . $error_msg . '</p></div>';
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX META BOX] Timeline error: ' . $e->getMessage() );
                }
            }

            echo '<div class="bwdk-dx-cancel-shipment-wrap" style="margin:10px 0;">';
            $this->render_dx_cancel_shipment_button( $order, is_array( $data_ui ) ? $data_ui : null );
            echo '</div>';

            // Debug Section
            try {
                $this->render_dx_debug_section( $order, is_array( $data_ui ) ? $data_ui : $data );
            } catch ( \Throwable $e ) {
                $error_msg = esc_html__( 'خطا در نمایش بخش دیباگ.', 'bwdk' );
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                    $error_msg .= ' ' . esc_html__( 'جزئیات خطا:', 'bwdk' ) . ' ' . esc_html( $e->getMessage() );
                }
                echo '<div class="notice notice-error inline" style="margin-bottom:20px;"><p>' . $error_msg . '</p></div>';
                if ( function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX META BOX] Debug section error: ' . $e->getMessage() );
                }
            }
        }
    }

    /**
     * Output DigiExpress data as widefat striped table (A–D sections).
     *
     * @param bool $omit_summary_fields When true (e.g. debug under summary), skip fields already shown in summary.
     */
    protected function render_dx_table( array $data, WC_Order $order, bool $omit_summary_fields = false ): void {
        if ( ! is_array( $data ) ) {
            echo '<p>' . esc_html__( 'داده فنی در دسترس نیست.', 'bwdk' ) . '</p>';
            return;
        }

        $is_cancelled_ui = ( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) === 'cancelled' );

        $detail = $data['detail'] ?? [];
        $event  = is_array( $detail ) ? ( $detail['event_type'] ?? [] ) : [];
        $payload = is_array( $detail ) ? ( $detail['payload'] ?? [] ) : [];
        $parcels = $data['parcels'] ?? [];
        $first   = is_array( $parcels ) && ! empty( $parcels ) ? $parcels[0] : [];

        // Get resolved message for human-readable status (display $data may already be cancelled override)
        $resolved_msg = class_exists( '\DX_Status_Engine' ) ? \DX_Status_Engine::resolve_message( $detail ) : null;
        $human_status = is_array( $resolved_msg ) && isset( $resolved_msg['title'] ) ? $resolved_msg['title'] : '';

        echo '<table class="widefat striped"><tbody>';

        // Key field: Order UUID (avoid duplicating in Debug section).
        $this->row( __( 'Order UUID', 'bwdk' ), (string) $order->get_meta( '_bwdk_order_uuid', true ) );

        // A) Shipment summary
        if ( ! $omit_summary_fields ) {
            $this->row( __( 'Shipment Code', 'bwdk' ), (string) ( $data['shipment_code'] ?? '' ) );
        }
        $this->row( __( 'Shipment ID', 'bwdk' ), (string) ( $data['shipment_id'] ?? '' ) );
        $raw_shipment_status = (string) ( $data['shipment_status'] ?? '' );
        $shipping_status_meta = $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        $translated_shipment_status = $this->translate_shipment_status( $raw_shipment_status );
        if ( $is_cancelled_ui ) {
            $ship_display = __( 'لغو شده', 'bwdk' );
        } else {
            $ship_display = $translated_shipment_status . ( $raw_shipment_status !== '' ? ' (' . $raw_shipment_status . ')' : '' );
        }
        $this->row( __( 'Shipment Status', 'bwdk' ), $ship_display );
        $translated_shipping_status = $this->translate_shipping_status( is_string( $shipping_status_meta ) ? $shipping_status_meta : '' );
        if ( $is_cancelled_ui ) {
            $translated_shipping_status = __( 'لغو شده', 'bwdk' );
        }
        $this->row( __( 'Shipping Status', 'bwdk' ), $translated_shipping_status );
        $cargo_raw_tbl = isset( $data['cargo_ready_date_time'] ) ? trim( (string) $data['cargo_ready_date_time'] ) : '';
        $cargo_cell    = $cargo_raw_tbl !== '' && function_exists( 'bwdk_dx_format_shipment_datetime_for_display' )
            ? bwdk_dx_format_shipment_datetime_for_display( $cargo_raw_tbl )
            : '';
        $this->row( __( 'Cargo Ready', 'bwdk' ), $cargo_cell );
        $this->row( __( 'Promise Start', 'bwdk' ), bwdk_dx_format_datetime_for_ui( $data['promise_start_date_time'] ?? '' ) );
        $this->row( __( 'Promise End', 'bwdk' ), bwdk_dx_format_datetime_for_ui( $data['promise_end_date_time'] ?? '' ) );
        $this->row( __( 'POD Code', 'bwdk' ), (string) ( $data['constant_proof_of_delivery_code_for_all_parcels'] ?? '' ) );

        // B) Latest event
        $event_id = (int) ( $event['id'] ?? 0 );
        if ( $is_cancelled_ui ) {
            $disp_eid = (int) $order->get_meta( self::META_DX_LAST_EVENT_ID, true );
            if ( ! in_array( $disp_eid, [ 8, 9 ], true ) ) {
                $disp_eid = 8;
            }
            $this->row( __( 'Latest Status', 'bwdk' ), __( 'لغو ارسال', 'bwdk' ) . ' (Event ID: ' . $disp_eid . ')' );
        } elseif ( $human_status !== '' ) {
            $this->row( __( 'Latest Status', 'bwdk' ), $human_status . ' (Event ID: ' . $event_id . ')' );
        } else {
            $this->row( __( 'Event ID', 'bwdk' ), (string) $event_id );
        }
        $this->row( __( 'Progress', 'bwdk' ), (string) ( $event['progress_type_translation'] ?? '' ) );
        $this->row( __( 'Shipping Status (tr)', 'bwdk' ), (string) ( $event['shipping_status_translation'] ?? '' ) );
        $this->row( __( 'Occurred At', 'bwdk' ), bwdk_dx_format_datetime_for_ui( $payload['occurred_at'] ?? '' ) );
        $this->row( __( 'Event Message ID', 'bwdk' ), (string) ( $payload['event_message_id'] ?? '' ) );

        // D) Capabilities
        $has_cancel = isset( $event['has_cancel_shipment'] ) && $event['has_cancel_shipment'];
        $cancel_txt = $has_cancel ? __( 'امکان لغو: دارد', 'bwdk' ) : __( 'امکان لغو: ندارد', 'bwdk' );
        $this->row( __( 'Cancel', 'bwdk' ), $cancel_txt );

        // C) Parcels
        $parcels_count = is_array( $parcels ) ? count( $parcels ) : 0;
        $this->row( __( 'Parcels count', 'bwdk' ), (string) $parcels_count );
        if ( $parcels_count > 0 && is_array( $first ) ) {
            $dims = $first['dimensionsInCMs'] ?? null;
            if ( is_array( $dims ) && isset( $dims['length'], $dims['width'], $dims['height'] ) ) {
                $dims_str = sprintf( '%s×%s×%s', $dims['length'], $dims['width'], $dims['height'] );
            } elseif ( is_string( $dims ) ) {
                $dims_str = $dims;
            } else {
                $dims_str = '-';
            }
            $this->row( __( 'Dimensions (L×W×H cm)', 'bwdk' ), $dims_str );
            $w = $first['weight_grams'] ?? null;
            $weight_str = ( $w === null || $w === '' || (int) $w === 0 ) ? __( 'ثبت نشده', 'bwdk' ) : (string) $w;
            $this->row( __( 'Weight (g)', 'bwdk' ), $weight_str );
            $this->row( __( 'Content value (Rials)', 'bwdk' ), (string) ( $first['content_value_price_rials'] ?? '' ) );
        }

        echo '</tbody></table>';
    }

    protected function row( string $label, string $value ): void {
        if ( $value === '' ) {
            return;
        }
        echo '<tr><th scope="row">' . esc_html( $label ) . '</th><td>' . esc_html( $value ) . '</td></tr>';
    }

    /**
     * Render label row with improved UX.
     */
    /**
     * Translate raw shipment status to Persian.
     */
    protected function translate_shipment_status( string $status ): string {
        $translations = [
            'pending' => 'در انتظار',
            'confirmed' => 'تأیید شده',
            'in_transit' => 'در مسیر',
            'delivered' => 'تحویل شده',
            'cancelled' => 'لغو شده',
            'canceled' => 'لغو شده',
            'failed' => 'ناموفق',
            'returned' => 'برگشت داده شده',
            'unregisteredshipment' => 'در انتظار تکمیل',
        ];
        $key = strtolower( $status );
        return $translations[ $key ] ?? $status;
    }

    /**
     * Translate shipping status meta to Persian.
     */
    protected function translate_shipping_status( $status ): string {
        if ( ! is_string( $status ) || $status === '' ) {
            return __( 'نامشخص', 'bwdk' );
        }
        $translations = [
            'pending' => 'در انتظار',
            'in_transit' => 'در مسیر',
            'out_for_delivery' => 'آماده تحویل',
            'delivered' => 'تحویل شده',
            'cancelled' => 'لغو شده',
            'pickup_failed' => 'بارگیری ناموفق',
            'delivery_failed' => 'تحویل ناموفق',
            'unregisteredshipment' => 'در انتظار تکمیل',
        ];
        $key = strtolower( $status );
        if ( $key === 'canceled' ) {
            return $translations['cancelled'];
        }
        return $translations[ $key ] ?? $status;
    }

    /**
     * Normalize debug value for safe display.
     */
    protected function normalize_debug_value( $value ) {
        if ( is_array( $value ) ) {
            return $value;
        }
        if ( is_object( $value ) && method_exists( $value, 'toArray' ) ) {
            return $value->toArray();
        }
        if ( is_object( $value ) ) {
            return (array) $value;
        }
        if ( is_string( $value ) ) {
            $decoded = json_decode( $value, true );
            if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
                return $decoded;
            }
            return $value;
        }
        return $value;
    }

    /**
     * Render pretty JSON block safely.
     */
    protected function render_pretty_json_block( string $label, $value ): void {
        $normalized = $this->normalize_debug_value( $value );
        echo '<h6>' . esc_html( $label ) . '</h6>';
        if ( is_array( $normalized ) || is_object( $normalized ) ) {
            $json = wp_json_encode( $normalized, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
            if ( ! is_string( $json ) || $json === '' ) {
                $json = print_r( $normalized, true );
            }
            echo '<pre style="max-height:300px; overflow:auto; background:#f6f7f7; padding:12px; border:1px solid #ddd;">' . esc_html( $json ) . '</pre>';
        } else {
            echo '<pre style="max-height:300px; overflow:auto; background:#f6f7f7; padding:12px; border:1px solid #ddd;">' . esc_html( (string) $normalized ) . '</pre>';
        }
    }

    /**
     * Render DigiExpress debug section (collapsible).
     */
    protected function render_dx_debug_section( WC_Order $order, ?array $data ): void {
        echo '<details style="margin-bottom:20px;">';
        echo '<summary style="cursor:pointer; font-weight:bold; padding:10px; background:#f1f1f1; border:1px solid #ddd; border-radius:4px;">' . esc_html__( 'اطلاعات خام / دیباگ', 'bwdk' ) . '</summary>';
        echo '<div style="padding:10px; border:1px solid #ddd; border-top:none; border-radius:0 0 4px 4px; background:#fafafa;">';

        $data = is_array( $data ) ? $data : [];
        $code_from_meta = trim( (string) $order->get_meta( self::META_DX_SHIPMENT_CODE, true ) );
        if ( $code_from_meta !== '' && ( ! isset( $data['shipment_code'] ) || trim( (string) $data['shipment_code'] ) === '' ) ) {
            $data['shipment_code'] = $code_from_meta;
        }

        // Presence debug (requested signals).
        $ssd_exists = ! empty( $data );
        $shipment_id_dbg       = $ssd_exists && isset( $data['shipment_id'] ) ? trim( (string) $data['shipment_id'] ) : '';
        $shipment_code_dbg     = $ssd_exists && isset( $data['shipment_code'] ) ? trim( (string) $data['shipment_code'] ) : '';
        $cargo_ready_dbg = $ssd_exists && isset( $data['cargo_ready_date_time'] ) ? trim( (string) $data['cargo_ready_date_time'] ) : '';
        $cargo_ready_fmt = '';
        if ( $cargo_ready_dbg !== '' && function_exists( 'bwdk_dx_format_shipment_datetime_for_display' ) ) {
            $cargo_ready_fmt = bwdk_dx_format_shipment_datetime_for_display( $cargo_ready_dbg );
        }
        $shipment_status_dbg   = $ssd_exists && isset( $data['shipment_status'] ) ? trim( (string) $data['shipment_status'] ) : '';
        $shipment_id_exists = ( $shipment_id_dbg !== '' );
        echo '<h5 style="margin:0 0 10px;">' . esc_html__( 'DX Presence Debug', 'bwdk' ) . '</h5>';
        echo '<table class="widefat striped" style="max-width:100%; margin:0 0 12px 0;"><tbody>';
        echo '<tr><td style="width:240px;"><code>selected_shipping_data</code></td><td><code>' . esc_html( $ssd_exists ? 'yes' : 'no' ) . '</code></td></tr>';
        echo '<tr><td><code>shipment_id</code></td><td><code>' . esc_html( $shipment_id_exists ? 'yes' : 'no' ) . '</code></td></tr>';
        echo '<tr><td><code>shipment_code</code></td><td><code>' . esc_html( $shipment_code_dbg !== '' ? $shipment_code_dbg : '-' ) . '</code></td></tr>';
        echo '<tr><td><code>cargo_ready_date_time</code></td><td>';
        echo '<code style="display:block;word-break:break-all;">' . esc_html( $cargo_ready_dbg !== '' ? $cargo_ready_dbg : '-' ) . '</code>';
        if ( $cargo_ready_fmt !== '' && $cargo_ready_fmt !== '-' ) {
            echo '<span style="display:block;margin-top:6px;color:#1f2937;">' . esc_html__( 'نمایش (مثل UI):', 'bwdk' ) . ' ' . esc_html( $cargo_ready_fmt ) . '</span>';
        }
        echo '</td></tr>';
        echo '<tr><td><code>shipment_status</code></td><td><code>' . esc_html( $shipment_status_dbg !== '' ? $shipment_status_dbg : '-' ) . '</code></td></tr>';
        echo '</tbody></table>';

        $cargo_ready_at_meta = trim( (string) $order->get_meta( self::META_DX_CARGO_READY_AT, true ) );
        if ( $cargo_ready_at_meta !== '' ) {
            $cargo_parsed_ts = function_exists( 'dx_parse_order_meta_datetime_utc_to_timestamp' )
                ? dx_parse_order_meta_datetime_utc_to_timestamp( $cargo_ready_at_meta )
                : null;
            $cargo_jal = ( $cargo_parsed_ts !== null && function_exists( 'dx_format_jalali_datetime' ) )
                ? dx_format_jalali_datetime( $cargo_parsed_ts )
                : '-';
            $ts_dbg = $cargo_parsed_ts === null ? 'null' : (string) (int) $cargo_parsed_ts;
            echo '<div style="margin:0 0 12px 0;"><strong>' . esc_html__( 'Cargo Ready (raw)', 'bwdk' ) . ':</strong> ';
            echo '<code style="word-break:break-all;">' . esc_html( $cargo_ready_at_meta ) . '</code></div>';
            echo '<div style="margin:0 0 12px 0;"><strong>' . esc_html__( 'Cargo Ready (parsed timestamp)', 'bwdk' ) . ':</strong> ';
            echo esc_html( $ts_dbg ) . '</div>';
            echo '<div style="margin:0 0 12px 0;"><strong>' . esc_html__( 'Cargo Ready (jalali)', 'bwdk' ) . ':</strong> ';
            echo esc_html( $cargo_jal !== '' ? $cargo_jal : '-' ) . '</div>';
        }

        // A) Technical details — full widefat (Shipment Code + Cargo Ready + …) like original admin table.
        if ( ! empty( $data ) ) {
            echo '<h5 style="margin:0 0 10px;">' . esc_html__( 'جزئیات فنی', 'bwdk' ) . '</h5>';
            $this->render_dx_table( $data, $order, false );
        }

        // Shipping methods debug (keep inside debug section only).
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
            $resolved = $this->get_available_wc_shipping_methods_for_order( $order );
            $source   = $resolved['source'] ?? 'none';
            $zone     = $resolved['zone'] ?? null;
            $options  = $resolved['options'] ?? [];
            $zid      = is_array( $zone ) && isset( $zone['id'] ) ? (int) $zone['id'] : null;
            $zname    = is_array( $zone ) && isset( $zone['name'] ) ? (string) $zone['name'] : '';
            $count    = is_array( $options ) ? count( $options ) : 0;
            $dbg = '[DX shipping methods] source=' . (string) $source . ' methods=' . (string) $count;
            if ( $zid !== null ) {
                $dbg .= ' zone_id=' . (string) $zid;
            }
            if ( $zname !== '' ) {
                $dbg .= ' zone=' . $zname;
            }
            echo '<p class="description" style="margin:0 0 10px; color:#646970; font-size:12px;"><code>' . esc_html( $dbg ) . '</code></p>';
        }

        // B) Pretty JSON blocks
        echo '<h5>' . esc_html__( 'داده‌های JSON', 'bwdk' ) . '</h5>';

        // _bwdk_dx_detail
        $dx_detail = $order->get_meta( self::META_DX_DETAIL, true );
        $this->render_pretty_json_block( '_bwdk_dx_detail', $dx_detail );

        // _bwdk_dx_history
        $dx_history = $order->get_meta( self::META_DX_HISTORY, true );
        $this->render_pretty_json_block( '_bwdk_dx_history', $dx_history );

        // _bwdk_dx_last_webhook_raw (if exists)
        $last_webhook_raw = $order->get_meta( '_bwdk_dx_last_webhook_raw', true );
        if ( $last_webhook_raw !== null && $last_webhook_raw !== '' ) {
            $this->render_pretty_json_block( '_bwdk_dx_last_webhook_raw', $last_webhook_raw );
        }

        echo '</div>';
        echo '</details>';
    }

    /**
     * Print label: capability + nonce, redirect to stored PDF URL.
     */
    public function handle_print_label(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            wp_die( esc_html__( 'You do not have permission.', 'bwdk' ), '', [ 'response' => 403 ] );
        }
        $order_id = isset( $_GET['order_id'] ) ? absint( $_GET['order_id'] ) : 0;
        $nonce    = isset( $_GET['nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : '';
        if ( $order_id <= 0 || ! wp_verify_nonce( $nonce, self::PRINT_LABEL_NONCE . $order_id ) ) {
            wp_die( esc_html__( 'Invalid request.', 'bwdk' ), '', [ 'response' => 400 ] );
        }
        $order = wc_get_order( $order_id );
        if ( ! $order || ! $order instanceof WC_Order ) {
            wp_die( esc_html__( 'Order not found.', 'bwdk' ), '', [ 'response' => 404 ] );
        }
        $order_uuid = $order->get_meta( '_bwdk_order_uuid', true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            wp_die( esc_html__( 'Not a BWDK order.', 'bwdk' ), '', [ 'response' => 400 ] );
        }
        $redirect = $this->get_redirect_to_order_edit( $order, $order_id );
        $pdf_label = (string) $order->get_meta( self::META_DX_PDF_LABEL, true );
        $pdf_label = trim( $pdf_label );

        if ( $pdf_label !== '' ) {
            wp_safe_redirect( $pdf_label );
            exit;
        }

        $msg = __( 'لیبل PDF هنوز آماده نیست.', 'bwdk' );
        set_transient(
            self::TRANSIENT_REFRESH_NOTICE . $order_id,
            [ 'type' => 'warning', 'message' => $msg ],
            120
        );
        $order->add_order_note( $msg );
        wp_safe_redirect( $redirect );
        exit;
    }

    public function handle_print_label_nopriv(): void {
        wp_die( esc_html__( 'You must be logged in.', 'bwdk' ), '', [ 'response' => 403 ] );
    }

    /**
     * Handle refresh DigiExpress data request.
     */
    public function handle_refresh_data(): void {
        // Keep log string stable for debugging ("DX REFRESH HANDLER ENTERED" exact).
        error_log('DX REFRESH HANDLER ENTERED');
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            wp_die( esc_html__( 'Unauthorized', 'bwdk' ), '', [ 'response' => 403 ] );
        }

        $order_id = isset( $_REQUEST['order_id'] ) ? absint( $_REQUEST['order_id'] ) : 0;
        $nonce    = isset( $_REQUEST['nonce'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ) : '';
        if ( $nonce === '' && isset( $_REQUEST['_wpnonce'] ) ) {
            $nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
        }

        $fail_prefix = __( 'بروزرسانی اطلاعات دیجی‌اکسپرس انجام نشد.', 'bwdk' );
        $referer_ok  = wp_get_referer() ? wp_validate_redirect( wp_get_referer(), admin_url( 'edit.php?post_type=shop_order' ) ) : admin_url( 'edit.php?post_type=shop_order' );

        $nonce_action_legacy = self::REFRESH_DATA_NONCE . $order_id;
        $nonce_action_new    = 'bwdk_dx_refresh_' . $order_id;
        $nonce_ok = wp_verify_nonce( $nonce, $nonce_action_legacy ) || wp_verify_nonce( $nonce, $nonce_action_new );

        if ( $order_id <= 0 || ! $nonce_ok ) {
            set_transient(
                self::TRANSIENT_REFRESH_NOTICE . 'u' . get_current_user_id(),
                [
                    'type'    => 'error',
                    'message' => $fail_prefix . ' (' . __( 'درخواست نامعتبر', 'bwdk' ) . ')',
                ],
                120
            );
            wp_safe_redirect( $referer_ok );
            exit;
        }

        $order = wc_get_order( $order_id );
        if ( ! $order || ! $order instanceof WC_Order ) {
            set_transient(
                self::TRANSIENT_REFRESH_NOTICE . 'u' . get_current_user_id(),
                [
                    'type'    => 'error',
                    'message' => $fail_prefix . ' (' . __( 'سفارش یافت نشد', 'bwdk' ) . ')',
                ],
                120
            );
            wp_safe_redirect( admin_url( 'edit.php?post_type=shop_order' ) );
            exit;
        }

        $redirect = $this->get_redirect_to_order_edit( $order, $order_id );

        $order_uuid = $order->get_meta( '_bwdk_order_uuid', true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            $raw = __( 'شناسه سفارش BWDK یافت نشد', 'bwdk' );
            set_transient(
                self::TRANSIENT_REFRESH_NOTICE . $order_id,
                [
                    'type'    => 'error',
                    'message' => $fail_prefix . ' (' . $raw . ')',
                ],
                120
            );
            $order->add_order_note( $fail_prefix . ' (' . $raw . ')' );
            wp_safe_redirect( $redirect );
            exit;
        }

        $api_result = $this->fetch_fresh_order_details( $order_uuid );
        if ( isset( $api_result['error'] ) ) {
            $raw = is_string( $api_result['error'] ) ? $api_result['error'] : wp_json_encode( $api_result['error'] );
            set_transient(
                self::TRANSIENT_REFRESH_NOTICE . $order_id,
                [
                    'type'    => 'error',
                    'message' => $fail_prefix . ' (' . $raw . ')',
                ],
                120
            );
            $order->add_order_note( $fail_prefix . ' (' . $raw . ')' );
            wp_safe_redirect( $redirect );
            exit;
        }

        $selected_shipping_data = $api_result['selected_shipping_data'] ?? null;
        if ( ! is_array( $selected_shipping_data ) ) {
            $raw = __( 'پاسخ سرویس فاقد اطلاعات ارسال دیجی‌اکسپرس است', 'bwdk' );
            set_transient(
                self::TRANSIENT_REFRESH_NOTICE . $order_id,
                [
                    'type'    => 'error',
                    'message' => $fail_prefix . ' (' . $raw . ')',
                ],
                120
            );
            $order->add_order_note( $fail_prefix . ' (' . $raw . ')' );
            wp_safe_redirect( $redirect );
            exit;
        }

        $this->sync_dx_meta_from_bwdk_order_response( $order, $api_result, $order_uuid );

        $ok_msg = __( 'اطلاعات دیجی‌اکسپرس با موفقیت بروزرسانی شد.', 'bwdk' );
        set_transient(
            self::TRANSIENT_REFRESH_NOTICE . $order_id,
            [
                'type'    => 'success',
                'message' => $ok_msg,
            ],
            120
        );

        wp_safe_redirect( $redirect );
        exit;
    }

    /**
     * Raw error text from BWDK API result arrays (cancel/change handlers).
     *
     * @param array $api Result from request_*_api helpers.
     */
    protected function dx_api_result_raw_for_notice( array $api ): string {
        if ( isset( $api['raw_error'] ) && is_string( $api['raw_error'] ) && trim( $api['raw_error'] ) !== '' ) {
            return trim( $api['raw_error'] );
        }
        if ( isset( $api['error'] ) && is_string( $api['error'] ) && trim( $api['error'] ) !== '' ) {
            return trim( $api['error'] );
        }
        return __( 'خطای نامشخص', 'bwdk' );
    }

    /**
     * Safe redirect target for order edit (HPOS-compatible).
     */
    protected function get_redirect_to_order_edit( WC_Order $order, int $order_id ): string {
        $redirect = '';
        if ( is_callable( [ $order, 'get_edit_order_url' ] ) ) {
            $redirect = (string) $order->get_edit_order_url();
        }
        if ( $redirect === '' ) {
            $redirect = (string) get_edit_post_link( $order_id, '' );
        }
        if ( $redirect === '' ) {
            $redirect = admin_url( 'post.php?post=' . $order_id . '&action=edit' );
        }
        return $redirect;
    }

    /**
     * Resolve the backend DigiExpress/BWDK order UUID from WooCommerce order meta (HPOS-safe).
     *
     * IMPORTANT:
     * - This must be the BWDK manager order UUID used in endpoints like:
     *   /orders/api/v1/manager/{order_uuid}/...
     * - Do not log the UUID value; only log selected meta key name when debugging.
     *
     * @return array{ok:bool, uuid?:string, meta_key?:string, raw_error?:string}
     */
    protected function resolve_dx_order_uuid_from_meta( WC_Order $order ): array {
        $candidates = [
            \BWDK\Support\Constant::META_BWDK_UUID,
            '_bwdk_order_uuid',
            '_bwdk_dx_order_uuid',
            '_digiexpress_order_uuid',
            '_dx_order_uuid',
        ];

        foreach ( $candidates as $meta_key ) {
            $val = $order->get_meta( $meta_key, true );
            if ( is_string( $val ) ) {
                $val = trim( $val );
                if ( $val !== '' ) {
                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                        error_log( '[BWDK DX CANCEL] resolved order_uuid meta_key=' . (string) $meta_key );
                    }
                    return [ 'ok' => true, 'uuid' => $val, 'meta_key' => (string) $meta_key ];
                }
            }
        }

        return [
            'ok'        => false,
            'raw_error' => 'meta_not_found keys_checked=' . implode( ',', $candidates ),
        ];
    }

    /**
     * Unconditionally persist cancel-success meta (mandatory block + WC/HPOS). No API/body checks.
     *
     * @param WC_Order $order Order after successful cancel API call.
     */
    protected function write_cancel_shipment_success_meta( WC_Order $order ): void {
        $order_id = $order->get_id();

        update_post_meta( $order_id, '_bwdk_dx_shipping_status', 'cancelled' );

        update_post_meta( $order_id, '_bwdk_dx_last_message', 'سفارش با موفقیت لغو شد.' );

        update_post_meta( $order_id, '_bwdk_dx_last_status', 3 );

        update_post_meta( $order_id, '_bwdk_dx_last_event_id', 8 );

        update_post_meta( $order_id, '_bwdk_dx_event_id', 8 );

        update_post_meta( $order_id, '_bwdk_dx_panel_message', [
            'title'   => 'ارسال لغو شد',
            'message' => 'سفارش با موفقیت لغو شد.',
            'status'  => 3,
        ] );

        $panel_cancel = [
            'title'   => 'ارسال لغو شد',
            'message' => 'سفارش با موفقیت لغو شد.',
            'status'  => 3,
        ];
        $order->update_meta_data( self::META_DX_SHIPPING_STATUS, 'cancelled' );
        $order->update_meta_data( self::META_DX_LAST_MESSAGE, 'سفارش با موفقیت لغو شد.' );
        $order->update_meta_data( self::META_DX_LAST_STATUS, 3 );
        $order->update_meta_data( self::META_DX_LAST_EVENT_ID, 8 );
        $order->update_meta_data( self::META_DX_EVENT_ID, 8 );
        $order->update_meta_data( self::META_DX_PANEL_MESSAGE, $panel_cancel );
        $order->save();

        if ( function_exists( 'wc_update_order_meta' ) ) {
            wc_update_order_meta( $order_id, self::META_DX_SHIPPING_STATUS, 'cancelled' );
            wc_update_order_meta( $order_id, self::META_DX_LAST_MESSAGE, 'سفارش با موفقیت لغو شد.' );
            wc_update_order_meta( $order_id, self::META_DX_LAST_STATUS, 3 );
            wc_update_order_meta( $order_id, self::META_DX_LAST_EVENT_ID, 8 );
            wc_update_order_meta( $order_id, self::META_DX_EVENT_ID, 8 );
            wc_update_order_meta( $order_id, self::META_DX_PANEL_MESSAGE, $panel_cancel );
        }
    }

    /**
     * POST handler: cancel DigiExpress shipment via BWDK API (server-side cancellable check).
     */
    public function handle_cancel_shipment(): void {
        $fallback_redirect = wp_get_referer();
        if ( ! is_string( $fallback_redirect ) || $fallback_redirect === '' ) {
            $fallback_redirect = admin_url( 'edit.php?post_type=shop_order' );
        }

        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            wp_safe_redirect( $fallback_redirect );
            exit;
        }

        $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
        if ( $order_id <= 0 ) {
            wp_safe_redirect( $fallback_redirect );
            exit;
        }

        check_admin_referer( self::CANCEL_SHIPMENT_NONCE_PREFIX . $order_id );

        $order = wc_get_order( $order_id );
        if ( ! $order || ! $order instanceof WC_Order ) {
            wp_safe_redirect( $fallback_redirect );
            exit;
        }

        $redirect = $this->get_redirect_to_order_edit( $order, $order_id );

        $uuid_res = $this->resolve_dx_order_uuid_from_meta( $order );
        if ( empty( $uuid_res['ok'] ) || empty( $uuid_res['uuid'] ) || ! is_string( $uuid_res['uuid'] ) ) {
            $raw = isset( $uuid_res['raw_error'] ) && is_string( $uuid_res['raw_error'] ) ? $uuid_res['raw_error'] : __( 'شناسه یافت نشد', 'bwdk' );
            $msg = 'شناسه سفارش دیجی‌اکسپرس برای لغو ارسال پیدا نشد. (' . $raw . ')';
            set_transient( self::TRANSIENT_CANCEL_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            $order->add_order_note( $msg );
            wp_safe_redirect( $redirect );
            exit;
        }
        $order_uuid = trim( (string) $uuid_res['uuid'] );

        // Do NOT block cancel based on local state. Backend is the source of truth.
        $api = $this->request_cancel_dx_shipment_api( $order_uuid );
        if ( empty( $api['ok'] ) ) {
            $raw = $this->dx_api_result_raw_for_notice( $api );
            $msg = __( 'لغو ارسال دیجی‌اکسپرس انجام نشد.', 'bwdk' ) . ' (' . $raw . ')';
            set_transient( self::TRANSIENT_CANCEL_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            $order->add_order_note( $msg );
            wp_safe_redirect( $redirect );
            exit;
        }

        if ( function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CANCEL] order_id=' . (string) $order_id . ' uuid_present=1' );
        }

        // Refresh/sync after successful cancel (do not override title/message locally).
        $key = self::TRANSIENT_PREFIX . sanitize_key( $order_uuid );
        delete_transient( $key );

        $fresh = $this->fetch_fresh_order_details( $order_uuid );
        if ( is_array( $fresh ) && ! isset( $fresh['error'] ) ) {
            $this->sync_dx_meta_from_bwdk_order_response( $order, $fresh, $order_uuid );
            $order->add_order_note( __( 'لغو ارسال دیجی‌اکسپرس با موفقیت انجام شد.', 'bwdk' ) );
            set_transient(
                self::TRANSIENT_CANCEL_NOTICE . $order_id,
                [ 'type' => 'success', 'message' => __( 'لغو ارسال دیجی‌اکسپرس با موفقیت انجام شد.', 'bwdk' ) ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }

        $raw = is_array( $fresh ) && isset( $fresh['error'] ) ? ( is_string( $fresh['error'] ) ? $fresh['error'] : wp_json_encode( $fresh['error'] ) ) : __( 'خطا در دریافت وضعیت از بک‌اند', 'bwdk' );
        $msg = 'لغو ارسال با موفقیت ثبت شد، اما بروزرسانی وضعیت از دیجی‌اکسپرس ناموفق بود. لطفاً بروزرسانی وضعیت را بزنید. (' . $raw . ')';
        $order->add_order_note( $msg );
        set_transient( self::TRANSIENT_CANCEL_NOTICE . $order_id, [ 'type' => 'warning', 'message' => $msg ], 120 );
        wp_safe_redirect( $redirect );
        exit;
    }

    /**
     * POST handler: revive DigiExpress shipment via BWDK API (server-side permission check).
     */
    public function handle_revive_shipment(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            wp_die( esc_html__( 'Unauthorized', 'bwdk' ), '', [ 'response' => 403 ] );
        }

        $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
        if ( $order_id <= 0 ) {
            wp_die( esc_html__( 'Invalid request.', 'bwdk' ), '', [ 'response' => 400 ] );
        }

        check_admin_referer( self::REVIVE_SHIPMENT_NONCE_PREFIX . $order_id );

        $order = wc_get_order( $order_id );
        if ( ! $order || ! $order instanceof WC_Order ) {
            wp_die( esc_html__( 'Order not found.', 'bwdk' ), '', [ 'response' => 404 ] );
        }

        $redirect = $this->get_redirect_to_order_edit( $order, $order_id );

        $raw_prep = $_POST['preparation_time'] ?? null;
        $prep_raw_str = is_string( $raw_prep ) ? (string) wp_unslash( $raw_prep ) : ( is_numeric( $raw_prep ) ? (string) $raw_prep : '' );
        $prep_raw_str = trim( $prep_raw_str );
        if ( $prep_raw_str === '' ) {
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (preparation_time الزامی است)';
            $order->add_order_note( $msg );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }
        if ( ! preg_match( '/^\d+$/', $prep_raw_str ) ) {
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (preparation_time نامعتبر است)';
            $order->add_order_note( $msg );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }
        $preparation_time = (int) $prep_raw_str;
        if ( $preparation_time < 0 ) {
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (preparation_time نامعتبر است)';
            $order->add_order_note( $msg );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        $order_uuid = $order->get_meta( Constant::META_BWDK_UUID, true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            $raw = __( 'شناسه سفارش BWDK یافت نشد', 'bwdk' );
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        // Server-side permission: fetch fresh details and re-check capability (do not trust UI).
        $fresh = $this->fetch_fresh_order_details( $order_uuid );
        if ( isset( $fresh['error'] ) ) {
            $raw = is_string( $fresh['error'] ) ? $fresh['error'] : wp_json_encode( $fresh['error'] );
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }
        $selected_shipping_data = $fresh['selected_shipping_data'] ?? null;
        $selected_shipping_method = $fresh['selected_shipping_method'] ?? null;
        $shipment = is_array( $selected_shipping_data ) ? $selected_shipping_data : [];
        if ( is_array( $selected_shipping_method ) ) {
            $shipment['selected_shipping_method'] = $selected_shipping_method;
        }
        // Enrich from order meta (webhook-normalized state), not UI.
        $shipment['bwdk_dx_shipping_status'] = is_string( $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) ) ? (string) $order->get_meta( self::META_DX_SHIPPING_STATUS, true ) : '';
        $shipment['bwdk_dx_event_id'] = is_numeric( $order->get_meta( self::META_DX_EVENT_ID, true ) ) ? (int) $order->get_meta( self::META_DX_EVENT_ID, true ) : null;
        $progress_detail = $order->get_meta( self::META_DX_DETAIL, true );
        if ( is_array( $progress_detail ) && isset( $progress_detail['event_type']['progress_type'] ) && is_string( $progress_detail['event_type']['progress_type'] ) ) {
            $shipment['progress_type'] = $progress_detail['event_type']['progress_type'];
        }

        $svc = class_exists( '\BWDK_DigiExpress_Service', true ) ? new \BWDK_DigiExpress_Service() : null;
        $reason = $preparation_time > 0 ? 'preparation_time_changed' : null;
        $action = is_object( $svc ) ? $svc->decide_digiexpress_action( $shipment, $reason ) : null;

        if ( ! is_array( $selected_shipping_data ) || empty( $selected_shipping_data ) ) {
            $msg_notice = 'در این مرحله امکان فعال‌سازی مجدد ارسال وجود ندارد.';
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX REVIVE] blocked: empty selected_shipping_data order_id=' . (string) $order_id );
            }
            $order->add_order_note( $msg_notice );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'warning', 'message' => $msg_notice ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        if ( $action === \BWDK_DigiExpress_Service::ACTION_CREATE_NEW_SHIPMENT ) {
            $msg_notice = 'به دلیل شکست تحویل، امکان revive وجود ندارد و باید درخواست ارسال جدید ثبت شود.';
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX REVIVE] blocked(create_new_shipment): ' . wp_json_encode( [
                    'order_id' => $order_id,
                    'shipment_id' => $shipment['shipment_id'] ?? null,
                    'status' => $shipment['bwdk_dx_shipping_status'] ?? null,
                    'event_id' => $shipment['bwdk_dx_event_id'] ?? null,
                ], JSON_UNESCAPED_UNICODE ) );
            }
            $order->add_order_note( $msg_notice );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'warning', 'message' => $msg_notice ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        if ( $action !== \BWDK_DigiExpress_Service::ACTION_REVIVE ) {
            $msg_notice = 'در این مرحله امکان فعال‌سازی مجدد ارسال وجود ندارد.';
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX REVIVE] blocked(no_action): ' . wp_json_encode( [
                    'order_id' => $order_id,
                    'shipment_id' => $shipment['shipment_id'] ?? null,
                    'reason' => $reason,
                    'status' => $shipment['bwdk_dx_shipping_status'] ?? null,
                    'event_id' => $shipment['bwdk_dx_event_id'] ?? null,
                ], JSON_UNESCAPED_UNICODE ) );
            }
            $order->add_order_note( $msg_notice );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'warning', 'message' => $msg_notice ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        // Idempotency: prevent duplicate revive clicks for same state/reason.
        $shipment_id = isset( $shipment['shipment_id'] ) ? trim( (string) $shipment['shipment_id'] ) : '';
        $current_status = isset( $shipment['bwdk_dx_shipping_status'] ) ? trim( (string) $shipment['bwdk_dx_shipping_status'] ) : '';
        $revive_hash = md5( $shipment_id . '|' . (string) $reason . '|' . $current_status );
        $last_hash = $order->get_meta( self::META_DX_LAST_REVIVE_HASH, true );
        if ( is_string( $last_hash ) && $last_hash !== '' && hash_equals( $last_hash, $revive_hash ) ) {
            $msg_notice = 'درخواست revive قبلاً برای همین وضعیت ثبت شده است.';
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX REVIVE] idempotent hit order_id=' . (string) $order_id . ' hash=' . $revive_hash );
            }
            $order->add_order_note( $msg_notice );
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'info', 'message' => $msg_notice ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        $api = $this->request_revive_dx_shipment_api( $order_uuid, $preparation_time );
        if ( empty( $api['ok'] ) ) {
            $raw = $this->dx_api_result_raw_for_notice( $api );
            $msg = 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس انجام نشد. (' . $raw . ')';
            $order->add_order_note( $msg );
            // Do not change shipping status on failure; only log/notice.
            $order->update_meta_data( self::META_DX_LAST_REVIVE_AT, current_time( 'mysql' ) );
            $order->update_meta_data( self::META_DX_LAST_REVIVE_REASON, (string) $reason );
            $order->update_meta_data( self::META_DX_LAST_REVIVE_RESPONSE, $api );
            $order->save();
            set_transient( self::TRANSIENT_REVIVE_NOTICE . $order_id, [ 'type' => 'error', 'message' => $msg ], 120 );
            wp_safe_redirect( $redirect );
            exit;
        }

        // Mark idempotency + audit meta on success (before refresh).
        $order->update_meta_data( self::META_DX_LAST_REVIVE_HASH, $revive_hash );
        $order->update_meta_data( self::META_DX_LAST_REVIVE_AT, current_time( 'mysql' ) );
        $order->update_meta_data( self::META_DX_LAST_REVIVE_REASON, (string) $reason );
        $order->update_meta_data( self::META_DX_LAST_REVIVE_RESPONSE, $api );
        // Best-effort local status hint; webhook/API may overwrite later.
        $order->update_meta_data( self::META_DX_SHIPPING_STATUS, 'awaiting_pickup' );
        $order->save();

        // Best-effort refresh/sync after success.
        delete_transient( self::TRANSIENT_PREFIX . sanitize_key( trim( $order_uuid ) ) );
        $fresh_after = $this->fetch_fresh_order_details( $order_uuid );
        if ( is_array( $fresh_after ) && ! isset( $fresh_after['error'] ) ) {
            $this->sync_dx_meta_from_bwdk_order_response( $order, $fresh_after, $order_uuid );
        }

        $order->add_order_note(
            'درخواست فعال‌سازی مجدد ارسال دیجی‌اکسپرس با موفقیت ثبت شد. (تعداد روز اضافه: ' . (string) $preparation_time . ')'
        );
        set_transient(
            self::TRANSIENT_REVIVE_NOTICE . $order_id,
            [ 'type' => 'success', 'message' => 'فعال‌سازی مجدد ارسال دیجی‌اکسپرس با موفقیت انجام شد.' ],
            120
        );
        wp_safe_redirect( $redirect );
        exit;
    }

    /**
     * PUT change shipping method on BWDK (no Woo method id required).
     *
     * @return array{ok:bool, error?:string}
     */
    protected function request_change_shipping_method_api( string $order_uuid, int $updated_shipping_type ): array {
        $order_uuid = trim( $order_uuid );
        if ( $order_uuid === '' ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] change API: empty uuid' );
            }
            return [ 'ok' => false, 'error' => 'empty_uuid', 'raw_error' => 'Empty order UUID' ];
        }
        if ( ! in_array( $updated_shipping_type, [ 1, 2 ], true ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] change API: invalid updated_shipping_type=' . (string) $updated_shipping_type );
            }
            return [ 'ok' => false, 'error' => 'invalid_type', 'raw_error' => 'Invalid updated_shipping_type' ];
        }

        try {
            $opts = get_option( Constant::BWDK_OPTION_NAME, [] );
            if ( ! is_array( $opts ) || empty( $opts['api']['endpoint'] ) || empty( $opts['api']['api-key'] ) ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] change API: missing endpoint/api-key option' );
                }
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Missing API endpoint/api-key option' ];
            }
            $base    = rtrim( (string) $opts['api']['endpoint'], '/' );
            $url     = $base . '/orders/api/v1/manager/' . rawurlencode( $order_uuid ) . '/change-shipping-method/';
            $api_key = trim( (string) $opts['api']['api-key'] );
            if ( $api_key === '' ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] change API: empty api-key' );
                }
                return [ 'ok' => false, 'error' => 'no_api', 'raw_error' => 'Empty api-key option' ];
            }

            $args = [
                'timeout'   => 10,
                'sslverify' => true,
                'headers'   => [
                    'Authorization' => 'Api-Key ' . $api_key,
                    'Content-Type'  => 'application/json; charset=utf-8',
                    'Accept'        => 'application/json',
                    'User-Agent'    => 'BWDK/WP',
                ],
                'method'    => 'PUT',
                'body'      => wp_json_encode( [ 'updated_shipping_type' => $updated_shipping_type ] ),
            ];
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] change API request url=' . $url . ' updated_shipping_type=' . (string) $updated_shipping_type );
            }
            $response = wp_remote_request( $url, $args );
            if ( is_wp_error( $response ) ) {
                $error_message = $response->get_error_message();
                $this->log_api_error( $url, 'PUT', 0, $response->get_error_code(), $error_message, '' );
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    error_log( '[BWDK DX CHANGE SHIPPING] change API WP_Error code=' . $response->get_error_code() . ' message=' . $error_message );
                }
                return [ 'ok' => false, 'error' => 'transport', 'raw_error' => $this->get_bwdk_error_message_from_response( $response ) ];
            }
            $code = (int) wp_remote_retrieve_response_code( $response );
            $body = wp_remote_retrieve_body( $response );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $snippet = is_string( $body ) ? substr( $body, 0, 300 ) : '';
                error_log( '[BWDK DX CHANGE SHIPPING] change API result ok=' . ( ( $code >= 200 && $code < 300 ) ? '1' : '0' ) . ' code=' . (string) $code . ' body_snippet=' . $snippet );
            }
            if ( $code >= 200 && $code < 300 ) {
                return [ 'ok' => true ];
            }
            $this->log_api_error( $url, 'PUT', $code, '', '', $body );
            return [
                'ok'        => false,
                'error'     => 'http_' . (string) $code,
                'raw_error' => $this->get_bwdk_error_message_from_response( $response ),
            ];
        } catch ( \Throwable $e ) {
            if ( function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX Meta Box] change shipping API: ' . $e->getMessage() );
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] change API exception: ' . $e->getMessage() );
            }
            return [ 'ok' => false, 'error' => 'exception', 'raw_error' => $e->getMessage() ];
        }
    }

    /**
     * POST handler: change shipping method from DigiExpress to WooCommerce method chosen by admin.
     */
    public function handle_change_shipping(): void {
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] handler entered' );
        }
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: unauthorized' );
            }
            wp_die( esc_html__( 'Unauthorized', 'bwdk' ), '', [ 'response' => 403 ] );
        }

        $order_id = isset( $_POST['order_id'] ) ? absint( $_POST['order_id'] ) : 0;
        if ( $order_id <= 0 ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: invalid order_id' );
            }
            wp_die( esc_html__( 'Invalid request.', 'bwdk' ), '', [ 'response' => 400 ] );
        }
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] order_id=' . (string) $order_id );
        }

        check_admin_referer( self::CHANGE_SHIPPING_NONCE_PREFIX . $order_id );

        $order = wc_get_order( $order_id );
        if ( ! $order || ! $order instanceof WC_Order ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: order not found' );
            }
            wp_die( esc_html__( 'Order not found.', 'bwdk' ), '', [ 'response' => 404 ] );
        }

        $redirect = $this->get_redirect_to_order_edit( $order, $order_id );

        $order_uuid = $order->get_meta( Constant::META_BWDK_UUID, true );
        if ( empty( $order_uuid ) || ! is_string( $order_uuid ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] order_uuid missing/invalid' );
            }
            $raw = 'Missing order UUID';
            $msg = __( 'امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ) . ' (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient(
                self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                [ 'type' => 'error', 'message' => $msg ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] order_uuid=' . (string) $order_uuid );
        }

        // Ensure this is a DigiExpress BWDK order (backend state).
        $details = $this->get_bwdk_details_for_dx_detection( $order );
        if ( ! is_array( $details ) || empty( $details ) || ! $this->is_digi_express( $details ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: not digiexpress order' );
            }
            $raw = 'Selected shipping method is not DigiExpress';
            $msg = __( 'امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ) . ' (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient(
                self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                [ 'type' => 'error', 'message' => $msg ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }

        $dx_data = $this->get_dx_data_for_display( $order );
        if ( ! is_array( $dx_data ) || empty( $dx_data ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: dx_data missing/empty' );
            }
            $raw = 'Invalid DigiExpress state (dx_data missing)';
            $msg = __( 'امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ) . ' (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient(
                self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                [ 'type' => 'error', 'message' => $msg ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }

        if ( ! bwdk_can_change_dx_shipping( $dx_data ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: bwdk_can_change_dx_shipping=false' );
            }
            $msg = __( 'تغییر روش ارسال از دیجی‌اکسپرس در این مرحله مجاز نیست.', 'bwdk' );
            $order->add_order_note( $msg );
            set_transient(
                self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                [ 'type' => 'warning', 'message' => __( 'در این مرحله امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ) ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }

        // Optional local WooCommerce selection (backend only supports "other" vs "digiexpress").
        $raw_selection = isset( $_POST['new_shipping_method'] ) ? (string) wp_unslash( $_POST['new_shipping_method'] ) : '';
        $selection_value = $raw_selection !== '' ? sanitize_text_field( $raw_selection ) : '';
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] selected_method_raw=' . $raw_selection );
        }
        $sel_method_id = '';
        $sel_instance_id = 0;
        $sel_title = '';
        $available = null;
        if ( $selection_value !== '' ) {
            $available = $this->get_available_wc_shipping_methods_for_order( $order );
            $meta_map = $available['meta'] ?? [];
            $options_map = $available['options'] ?? [];
            if ( is_array( $meta_map ) && isset( $meta_map[ $selection_value ] ) && is_array( $meta_map[ $selection_value ] ) ) {
                $sel_method_id = isset( $meta_map[ $selection_value ]['method_id'] ) ? (string) $meta_map[ $selection_value ]['method_id'] : '';
                $sel_instance_id = isset( $meta_map[ $selection_value ]['instance_id'] ) ? (int) $meta_map[ $selection_value ]['instance_id'] : 0;
                $sel_title = isset( $meta_map[ $selection_value ]['title'] ) ? (string) $meta_map[ $selection_value ]['title'] : '';
                if ( $sel_title === '' && is_array( $options_map ) && isset( $options_map[ $selection_value ] ) ) {
                    $sel_title = (string) $options_map[ $selection_value ];
                }
            }
        }

        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] resolved_selected_method_id=' . (string) $sel_method_id );
            error_log( '[BWDK DX CHANGE SHIPPING] resolved_selected_instance_id=' . (string) $sel_instance_id );
            error_log( '[BWDK DX CHANGE SHIPPING] resolved_selected_method_title=' . (string) $sel_title );
        }

        $dx_status = (string) $order->get_meta( self::META_DX_SHIPPING_STATUS, true );
        $dx_active = ! in_array( $dx_status, [ 'cancelled', 'switched_to_other' ], true );

        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] before cancel step' );
        }

        // If shipment is still active locally, cancel it first.
        if ( $dx_active ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] dx_cancel_triggered=1' );
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] before cancel API call' );
            }
            $cancel = $this->request_cancel_dx_shipment_api( $order_uuid );
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $ok = ! empty( $cancel['ok'] ) ? '1' : '0';
                $err = isset( $cancel['error'] ) ? (string) $cancel['error'] : '';
                error_log( '[BWDK DX CHANGE SHIPPING] after cancel API call ok=' . $ok . ( $err !== '' ? ( ' error=' . $err ) : '' ) );
            }
            if ( empty( $cancel['ok'] ) ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                    $err = isset( $cancel['error'] ) ? (string) $cancel['error'] : '';
                    error_log( '[BWDK DX CHANGE SHIPPING] early exit: dx_cancel_failed error=' . $err );
                }
                $raw = $this->dx_api_result_raw_for_notice( $cancel );
                $note_msg  = __( 'لغو ارسال دیجی‌اکسپرس پیش از تغییر روش ارسال انجام نشد.', 'bwdk' ) . ' (' . $raw . ')';
                $admin_msg = __( 'ابتدا لغو ارسال دیجی‌اکسپرس ناموفق بود و تغییر انجام نشد.', 'bwdk' ) . ' (' . $raw . ')';
                $order->add_order_note( $note_msg );
                set_transient(
                    self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                    [ 'type' => 'error', 'message' => $admin_msg ],
                    120
                );
                wp_safe_redirect( $redirect );
                exit;
            }
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] dx_cancel_succeeded=1' );
            }
        } else {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] dx_cancel_triggered=0' );
            }
        }

        // Change shipping on backend: 1 => other (WooCommerce shipping), 2 => digiexpress.
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] before change API' );
        }
        $chg = $this->request_change_shipping_method_api( $order_uuid, 1 );
        if ( empty( $chg['ok'] ) ) {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $err = isset( $chg['error'] ) ? (string) $chg['error'] : '';
                error_log( '[BWDK DX CHANGE SHIPPING] early exit: change_shipping_api_failed error=' . $err );
            }
            $raw = $this->dx_api_result_raw_for_notice( $chg );
            $msg = __( 'تغییر روش ارسال از دیجی‌اکسپرس انجام نشد.', 'bwdk' ) . ' (' . $raw . ')';
            $order->add_order_note( $msg );
            set_transient(
                self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
                [ 'type' => 'error', 'message' => $msg ],
                120
            );
            wp_safe_redirect( $redirect );
            exit;
        }
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] change_shipping_api_succeeded=1' );
        }

        // Local UI state: backend only knows "other", WC method is stored locally for admin UX.
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] before write_switched_to_other_success_meta' );
        }
        $this->write_switched_to_other_success_meta( $order );
        if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
            error_log( '[BWDK DX CHANGE SHIPPING] after write_switched_to_other_success_meta' );
        }

        // Optional: best-effort update of WC shipping item label + meta for UI.
        // IMPORTANT: Updating only meta is NOT enough; the WooCommerce order items table reads from the shipping line item.
        if ( $sel_method_id !== '' ) {
            // If title is missing, derive from existing shipping line or fall back to selection value.
            if ( $sel_title === '' ) {
                $existing_shipping_items = $order->get_items( 'shipping' );
                if ( is_array( $existing_shipping_items ) && ! empty( $existing_shipping_items ) ) {
                    $first_item = reset( $existing_shipping_items );
                    if ( $first_item instanceof \WC_Order_Item_Shipping ) {
                        $existing_name = is_callable( [ $first_item, 'get_name' ] ) ? (string) $first_item->get_name() : '';
                        $existing_method_title = is_callable( [ $first_item, 'get_method_title' ] ) ? (string) $first_item->get_method_title() : '';
                        $sel_title = $existing_method_title !== '' ? $existing_method_title : $existing_name;
                    }
                }
                if ( $sel_title === '' ) {
                    $sel_title = $selection_value !== '' ? $selection_value : $sel_method_id;
                }
            }

            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] before shipping item update' );
            }
            $this->apply_woocommerce_shipping_method_to_order( $order, $sel_method_id, $sel_title, $sel_instance_id );
            $order->update_meta_data( self::META_DX_WC_SHIPPING_METHOD_ID, $sel_method_id );
            $order->update_meta_data( self::META_DX_WC_SHIPPING_METHOD_INSTANCE_ID, $sel_instance_id );
            $order->update_meta_data( self::META_DX_WC_SHIPPING_METHOD_TITLE, $sel_title );
            $order->save();
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] after_order_save (meta persisted)' );
            }
        } else {
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] shipping item update skipped: empty selected_method_id' );
            }
        }

        $ok_msg = __( 'روش ارسال از دیجی‌اکسپرس با موفقیت تغییر یافت.', 'bwdk' );
        $order->add_order_note( $ok_msg );
        set_transient(
            self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id,
            [ 'type' => 'success', 'message' => $ok_msg ],
            120
        );
        wp_safe_redirect( $redirect );
        exit;
    }

    /**
     * Local fallback/UI state after successful change-shipping-method to "other".
     *
     * IMPORTANT: BWDK backend only knows "other" vs "digiexpress". WC method id/title (if any) is local-only.
     */
    protected function write_switched_to_other_success_meta( WC_Order $order ): void {
        $order_id = (int) $order->get_id();
        if ( $order_id <= 0 ) {
            return;
        }

        $title = __( 'روش ارسال تغییر یافت', 'bwdk' );
        $msg   = __( 'روش ارسال از دیجی‌اکسپرس به حالت عادی تغییر یافت.', 'bwdk' );
        $panel = [
            'title'   => $title,
            'message' => $msg,
            'status'  => 3,
        ];

        // Persist to post meta (legacy) and order meta (HPOS).
        update_post_meta( $order_id, self::META_DX_SHIPPING_STATUS, 'switched_to_other' );
        update_post_meta( $order_id, self::META_DX_LAST_MESSAGE, $msg );
        update_post_meta( $order_id, self::META_DX_LAST_STATUS, 3 );
        update_post_meta( $order_id, self::META_DX_LAST_EVENT_ID, 9 );
        update_post_meta( $order_id, self::META_DX_EVENT_ID, 9 );
        update_post_meta( $order_id, self::META_DX_PANEL_MESSAGE, $panel );

        $order->update_meta_data( self::META_DX_SHIPPING_STATUS, 'switched_to_other' );
        $order->update_meta_data( self::META_DX_LAST_MESSAGE, $msg );
        $order->update_meta_data( self::META_DX_LAST_STATUS, 3 );
        $order->update_meta_data( self::META_DX_LAST_EVENT_ID, 9 );
        $order->update_meta_data( self::META_DX_EVENT_ID, 9 );
        $order->update_meta_data( self::META_DX_PANEL_MESSAGE, $panel );

        $history = $order->get_meta( self::META_DX_HISTORY, true );
        if ( ! is_array( $history ) ) {
            $history = [];
        }
        $history[] = [
            'event_id'    => 9,
            'title'       => $title,
            'message'     => $msg,
            'status'      => 3,
            'timestamp'   => current_time( 'mysql' ),
            'bwdk_source' => 'admin_change_shipping',
        ];
        $order->update_meta_data( self::META_DX_HISTORY, $history );

        $order->save();

        if ( function_exists( 'wc_update_order_meta' ) ) {
            wc_update_order_meta( $order_id, self::META_DX_SHIPPING_STATUS, 'switched_to_other' );
            wc_update_order_meta( $order_id, self::META_DX_LAST_MESSAGE, $msg );
            wc_update_order_meta( $order_id, self::META_DX_LAST_STATUS, 3 );
            wc_update_order_meta( $order_id, self::META_DX_LAST_EVENT_ID, 9 );
            wc_update_order_meta( $order_id, self::META_DX_EVENT_ID, 9 );
            wc_update_order_meta( $order_id, self::META_DX_PANEL_MESSAGE, $panel );
            wc_update_order_meta( $order_id, self::META_DX_HISTORY, $history );
        }
    }

    /**
     * Apply selected WooCommerce shipping method to the order shipping item (best-effort).
     */
    protected function apply_woocommerce_shipping_method_to_order( WC_Order $order, string $method_id, string $method_title, int $instance_id = 0 ): void {
        try {
            $items = $order->get_items( 'shipping' );

            $normalized_method_id = $method_id;
            $normalized_instance_id = $instance_id;
            // Accept values like "flat_rate:3" and normalize to method_id + instance_id.
            if ( strpos( $normalized_method_id, ':' ) !== false ) {
                $parts = explode( ':', $normalized_method_id );
                $maybe_method = isset( $parts[0] ) ? (string) $parts[0] : '';
                $maybe_instance = isset( $parts[1] ) ? (int) $parts[1] : 0;
                if ( $maybe_method !== '' ) {
                    $normalized_method_id = $maybe_method;
                }
                if ( $normalized_instance_id <= 0 && $maybe_instance > 0 ) {
                    $normalized_instance_id = $maybe_instance;
                }
            }

            $updated_any = false;
            $had_items = is_array( $items ) && ! empty( $items );

            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                $count = $had_items ? count( $items ) : 0;
                error_log( '[BWDK DX CHANGE SHIPPING] shipping_items_count=' . (string) $count );
            }

            if ( $had_items ) {
                foreach ( $items as $ship_item ) {
                    if ( ! ( $ship_item instanceof \WC_Order_Item_Shipping ) ) {
                        continue;
                    }

                    $before = [
                        'method_id'     => is_callable( [ $ship_item, 'get_method_id' ] ) ? (string) $ship_item->get_method_id() : '',
                        'instance_id'   => is_callable( [ $ship_item, 'get_instance_id' ] ) ? (int) $ship_item->get_instance_id() : 0,
                        'method_title'  => is_callable( [ $ship_item, 'get_method_title' ] ) ? (string) $ship_item->get_method_title() : '',
                        'name'          => is_callable( [ $ship_item, 'get_name' ] ) ? (string) $ship_item->get_name() : '',
                        'total'         => is_callable( [ $ship_item, 'get_total' ] ) ? $ship_item->get_total() : null,
                        'item_id'       => is_callable( [ $ship_item, 'get_id' ] ) ? (int) $ship_item->get_id() : 0,
                    ];

                    $current_total = $before['total'];
                    $ship_item->set_method_id( $normalized_method_id );
                    if ( is_callable( [ $ship_item, 'set_instance_id' ] ) ) {
                        $ship_item->set_instance_id( (int) $normalized_instance_id );
                    }
                    if ( is_callable( [ $ship_item, 'set_method_title' ] ) ) {
                        $ship_item->set_method_title( $method_title );
                    }
                    if ( is_callable( [ $ship_item, 'set_name' ] ) ) {
                        $ship_item->set_name( $method_title );
                    }
                    // Preserve existing shipping total; only change method identity/label.
                    if ( $current_total !== null && is_callable( [ $ship_item, 'set_total' ] ) ) {
                        $ship_item->set_total( $current_total );
                    }

                    $ship_item->save();
                    $updated_any = true;

                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                        $after_method_id = is_callable( [ $ship_item, 'get_method_id' ] ) ? (string) $ship_item->get_method_id() : '';
                        $after_instance_id = is_callable( [ $ship_item, 'get_instance_id' ] ) ? (int) $ship_item->get_instance_id() : 0;
                        $after_method_title = is_callable( [ $ship_item, 'get_method_title' ] ) ? (string) $ship_item->get_method_title() : '';
                        $after_name = is_callable( [ $ship_item, 'get_name' ] ) ? (string) $ship_item->get_name() : '';
                        error_log( '[BWDK DX CHANGE SHIPPING] shipping_item_before item_id=' . (string) $before['item_id'] . ' method_id=' . $before['method_id'] . ' method_title=' . $before['method_title'] . ' name=' . $before['name'] );
                        error_log( '[BWDK DX CHANGE SHIPPING] shipping_item_after item_id=' . (string) $before['item_id'] . ' method_id=' . $after_method_id . ' method_title=' . $after_method_title . ' name=' . $after_name );
                        error_log( '[BWDK DX CHANGE SHIPPING] shipping item saved item_id=' . (string) $before['item_id'] );
                    }
                }
            }

            if ( ! $updated_any && class_exists( '\WC_Order_Item_Shipping' ) ) {
                $ship_item = new \WC_Order_Item_Shipping();
                if ( is_callable( [ $ship_item, 'set_name' ] ) ) {
                    $ship_item->set_name( $method_title );
                }
                $ship_item->set_method_id( $normalized_method_id );
                if ( is_callable( [ $ship_item, 'set_instance_id' ] ) ) {
                    $ship_item->set_instance_id( (int) $normalized_instance_id );
                }
                $ship_item->set_method_title( $method_title );
                // Best-effort: keep existing shipping total if any, otherwise 0.
                $ship_item->set_total( is_callable( [ $order, 'get_shipping_total' ] ) ? $order->get_shipping_total() : 0 );
                $order->add_item( $ship_item );
            }
            $order->save();
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] after_order_save (shipping item persisted)' );
            }
        } catch ( \Throwable $e ) {
            // Best effort only; do not break admin flow.
            if ( defined( 'WP_DEBUG' ) && WP_DEBUG && function_exists( 'error_log' ) ) {
                error_log( '[BWDK DX CHANGE SHIPPING] exception in apply_woocommerce_shipping_method_to_order: ' . $e->getMessage() );
            }
        }
    }

    /**
     * One-time admin notice after cancel shipment redirect (order edit screens only).
     */
    public function render_dx_cancel_admin_notice(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen || ! isset( $screen->id ) ) {
            return;
        }
        $sid = (string) $screen->id;
        if ( strpos( $sid, 'shop_order' ) === false && strpos( $sid, 'woocommerce_page_wc-orders' ) === false ) {
            return;
        }

        $order_id = 0;
        if ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        } elseif ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        }

        if ( $order_id <= 0 ) {
            return;
        }

        $tkey    = self::TRANSIENT_CANCEL_NOTICE . $order_id;
        $payload = get_transient( $tkey );
        if ( ( ! is_array( $payload ) || empty( $payload ) ) && ( ! is_string( $payload ) || $payload === '' ) ) {
            return;
        }
        delete_transient( $tkey );

        if ( is_array( $payload ) ) {
            $notice_type = isset( $payload['type'] ) && is_string( $payload['type'] ) ? strtolower( trim( (string) $payload['type'] ) ) : 'info';
            if ( ! in_array( $notice_type, [ 'success', 'error', 'warning', 'info' ], true ) ) {
                $notice_type = 'info';
            }
            $message = isset( $payload['message'] ) && is_string( $payload['message'] ) ? trim( (string) $payload['message'] ) : '';
            if ( $message === '' ) {
                return;
            }
            printf(
                '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
                esc_attr( $notice_type ),
                esc_html( $message )
            );
            return;
        }

        $code = (string) $payload;
        $messages = [
            'cancel_ok'      => __( 'لغو ارسال دیجی‌اکسپرس با موفقیت انجام شد.', 'bwdk' ),
            'cancel_fail'    => __( 'لغو ارسال دیجی‌اکسپرس انجام نشد.', 'bwdk' ),
            'cancel_denied'  => __( 'در این مرحله امکان لغو ارسال دیجی‌اکسپرس وجود ندارد.', 'bwdk' ),
        ];
        if ( ! isset( $messages[ $code ] ) ) {
            return;
        }

        $notice_type = 'error';
        if ( $code === 'cancel_ok' ) {
            $notice_type = 'success';
        } elseif ( $code === 'cancel_denied' ) {
            $notice_type = 'warning';
        }
        printf(
            '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
            esc_attr( $notice_type ),
            esc_html( $messages[ $code ] )
        );
    }

    /**
     * One-time admin notice after revive shipment redirect (order edit screens only).
     */
    public function render_dx_revive_admin_notice(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen || ! isset( $screen->id ) ) {
            return;
        }
        $sid = (string) $screen->id;
        if ( strpos( $sid, 'shop_order' ) === false && strpos( $sid, 'woocommerce_page_wc-orders' ) === false ) {
            return;
        }

        $order_id = 0;
        if ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        } elseif ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        }

        if ( $order_id <= 0 ) {
            return;
        }

        $tkey    = self::TRANSIENT_REVIVE_NOTICE . $order_id;
        $payload = get_transient( $tkey );
        if ( ( ! is_array( $payload ) || empty( $payload ) ) && ( ! is_string( $payload ) || $payload === '' ) ) {
            return;
        }
        delete_transient( $tkey );

        if ( is_array( $payload ) ) {
            $notice_type = isset( $payload['type'] ) && is_string( $payload['type'] ) ? strtolower( trim( (string) $payload['type'] ) ) : 'info';
            if ( ! in_array( $notice_type, [ 'success', 'error', 'warning', 'info' ], true ) ) {
                $notice_type = 'info';
            }
            $message = isset( $payload['message'] ) && is_string( $payload['message'] ) ? trim( (string) $payload['message'] ) : '';
            if ( $message === '' ) {
                return;
            }
            printf(
                '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
                esc_attr( $notice_type ),
                esc_html( $message )
            );
            return;
        }
    }

    /**
     * One-time admin notice after DigiExpress refresh redirect.
     */
    public function render_dx_refresh_admin_notice(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen || ! isset( $screen->id ) ) {
            return;
        }
        $sid = (string) $screen->id;
        if ( strpos( $sid, 'shop_order' ) === false && strpos( $sid, 'woocommerce_page_wc-orders' ) === false ) {
            return;
        }

        $order_id = 0;
        if ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        } elseif ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        }

        $t_order = $order_id > 0 ? self::TRANSIENT_REFRESH_NOTICE . $order_id : '';
        $t_user  = self::TRANSIENT_REFRESH_NOTICE . 'u' . get_current_user_id();

        $payload = null;
        if ( $t_order !== '' ) {
            $payload = get_transient( $t_order );
        }
        if ( ! is_array( $payload ) || empty( $payload['message'] ) || ! is_string( $payload['message'] ) ) {
            $payload = get_transient( $t_user );
            if ( is_array( $payload ) && ! empty( $payload['message'] ) && is_string( $payload['message'] ) ) {
                delete_transient( $t_user );
            } else {
                return;
            }
        } elseif ( $t_order !== '' ) {
            delete_transient( $t_order );
        }

        $notice_type = isset( $payload['type'] ) && is_string( $payload['type'] ) ? strtolower( trim( (string) $payload['type'] ) ) : 'info';
        if ( ! in_array( $notice_type, [ 'success', 'error', 'warning', 'info' ], true ) ) {
            $notice_type = 'info';
        }
        $message = trim( (string) $payload['message'] );
        if ( $message === '' ) {
            return;
        }
        printf(
            '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
            esc_attr( $notice_type ),
            esc_html( $message )
        );
    }

    /**
     * One-time admin notice after "change shipping method" redirect.
     */
    public function render_dx_change_shipping_admin_notice(): void {
        if ( ! current_user_can( 'manage_woocommerce' ) ) {
            return;
        }

        $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
        if ( ! $screen || ! isset( $screen->id ) ) {
            return;
        }
        $sid = (string) $screen->id;
        if ( strpos( $sid, 'shop_order' ) === false && strpos( $sid, 'woocommerce_page_wc-orders' ) === false ) {
            return;
        }

        $order_id = 0;
        if ( isset( $_GET['post'] ) ) {
            $order_id = absint( $_GET['post'] );
        } elseif ( isset( $_GET['id'] ) ) {
            $order_id = absint( $_GET['id'] );
        }
        if ( $order_id <= 0 ) {
            return;
        }

        $tkey = self::TRANSIENT_CHANGE_SHIPPING_NOTICE . $order_id;
        $payload = get_transient( $tkey );
        if ( ( ! is_array( $payload ) || empty( $payload ) ) && ( ! is_string( $payload ) || $payload === '' ) ) {
            return;
        }
        delete_transient( $tkey );

        // New format: structured payload.
        if ( is_array( $payload ) ) {
            $notice_type = isset( $payload['type'] ) && is_string( $payload['type'] ) ? strtolower( trim( (string) $payload['type'] ) ) : 'info';
            if ( ! in_array( $notice_type, [ 'success', 'error', 'warning', 'info' ], true ) ) {
                $notice_type = 'info';
            }
            $message = isset( $payload['message'] ) && is_string( $payload['message'] ) ? trim( (string) $payload['message'] ) : '';
            if ( $message === '' ) {
                return;
            }
            printf(
                '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
                esc_attr( $notice_type ),
                esc_html( $message )
            );
            return;
        }

        // Back-compat: legacy transient codes.
        $code = (string) $payload;
        $messages = [
            'change_ok'      => __( 'روش ارسال با موفقیت تغییر کرد.', 'bwdk' ),
            'change_fail'    => __( 'تغییر روش ارسال انجام نشد.', 'bwdk' ),
            'change_invalid' => __( 'روش ارسال انتخاب‌شده معتبر نیست.', 'bwdk' ),
            'cancel_fail'    => __( 'ابتدا لغو ارسال دیجی‌اکسپرس ناموفق بود و تغییر انجام نشد.', 'bwdk' ),
            'change_denied'  => __( 'در این مرحله امکان تغییر روش ارسال وجود ندارد.', 'bwdk' ),
        ];
        if ( ! isset( $messages[ $code ] ) ) {
            return;
        }
        $notice_type = 'error';
        if ( $code === 'change_ok' ) {
            $notice_type = 'success';
        } elseif ( $code === 'change_invalid' || $code === 'change_denied' ) {
            $notice_type = 'warning';
        }
        printf(
            '<div class="notice notice-%1$s is-dismissible"><p>%2$s</p></div>',
            esc_attr( $notice_type ),
            esc_html( $messages[ $code ] )
        );
    }

    /**
     * Map status int to string for panel message.
     */
    protected function map_status_int_to_string( int $status ): string {
        $map = [
            1 => 'warning',
            2 => 'error',
            3 => 'info',
            4 => 'success',
        ];
        return $map[ $status ] ?? 'info';
    }

    /**
     * Map event ID to shipping status string.
     */
    protected function map_event_id_to_shipping_status( int $event_id ): ?string {
        if ( in_array( $event_id, [10, 11, 12, 13, 28, 29, 30], true ) ) {
            return 'in_transit';
        } elseif ( in_array( $event_id, [14, 15, 16, 17], true ) ) {
            return 'out_for_delivery';
        } elseif ( in_array( $event_id, [18, 19], true ) ) {
            return 'delivered';
        } elseif ( in_array( $event_id, [8, 9], true ) ) {
            return 'cancelled';
        } elseif ( in_array( $event_id, [6, 7], true ) ) {
            return 'pickup_failed';
        } elseif ( in_array( $event_id, [20, 21, 22, 23, 24, 25], true ) ) {
            return 'delivery_failed';
        } else {
            return 'pending';
        }
    }

    /**
     * Extract backend error message from API response body.
     */
    protected function extract_api_error_message( string $body_raw ): ?string {
        $body = json_decode( $body_raw, true );
        if ( ! is_array( $body ) ) {
            return null;
        }
        $keys = ['detail', 'message', 'error'];
        foreach ( $keys as $key ) {
            if ( isset( $body[$key] ) && is_string( $body[$key] ) && trim( $body[$key] ) !== '' ) {
                return trim( $body[$key] );
            }
        }
        return null;
    }

    /**
     * Safely extract a readable raw error message from WP HTTP responses / WP_Error.
     *
     * Rules:
     * - WP_Error => get_error_message()
     * - HTTP body JSON => first meaningful field: error/detail/message
     * - fallback => body snippet (first 300 chars)
     * - final fallback => generic string
     *
     * @param mixed $response WP_Error or wp_remote_* response array.
     */
    protected function get_bwdk_error_message_from_response( $response ): string {
        if ( is_wp_error( $response ) ) {
            $m = $response->get_error_message();
            $m = is_string( $m ) ? trim( $m ) : '';
            return $m !== '' ? $m : 'Unknown transport error';
        }

        $body = wp_remote_retrieve_body( $response );
        if ( is_string( $body ) ) {
            $body_trim = trim( $body );
            if ( $body_trim !== '' ) {
                $json_msg = $this->extract_api_error_message( $body_trim );
                if ( is_string( $json_msg ) && trim( $json_msg ) !== '' ) {
                    return trim( $json_msg );
                }
                $snippet = mb_substr( $body_trim, 0, 300 );
                return $snippet !== '' ? $snippet : 'Unknown backend error';
            }
        }

        $code = (int) wp_remote_retrieve_response_code( $response );
        if ( $code > 0 ) {
            return 'HTTP ' . (string) $code;
        }

        return 'Unknown backend error';
    }

    /**
     * Map transport errors to user-friendly messages.
     */
    protected function map_transport_error( string $error_message ): string {
        $msg = strtolower( $error_message );
        if ( strpos( $msg, 'timed out' ) !== false || strpos( $msg, 'timeout' ) !== false ) {
            return 'اتصال به سرویس خرید با دیجی‌کالا برقرار نشد. سرور سایت در زمان مناسب پاسخی دریافت نکرد (Timeout). احتمال اختلال در هاست، فایروال یا اینترنت سرور وجود دارد.';
        }
        if ( strpos( $msg, 'ssl' ) !== false || strpos( $msg, 'certificate' ) !== false ) {
            return 'اتصال امن به سرویس خرید با دیجی‌کالا برقرار نشد. گواهی SSL یا تنظیمات cURL/OpenSSL سرور را بررسی کنید.';
        }
        if ( strpos( $msg, 'could not resolve host' ) !== false || strpos( $msg, 'name lookup timed out' ) !== false ) {
            return 'سرور سایت نتوانست آدرس سرویس خرید با دیجی‌کالا را resolve کند. DNS یا محدودیت شبکه هاست را بررسی کنید.';
        }
        if ( strpos( $msg, 'failed to connect' ) !== false || strpos( $msg, 'connection refused' ) !== false ) {
            return 'سرور سایت نتوانست به سرویس خرید با دیجی‌کالا وصل شود. احتمال مسدود بودن اتصال خروجی روی هاست وجود دارد.';
        }
        return 'اتصال به سرویس خرید با دیجی‌کالا برقرار نشد. لطفاً لاگ خطا را بررسی کنید.';
    }

    /**
     * Log API errors safely.
     */
    protected function log_api_error( string $endpoint, string $method, int $status_code, string $wp_error_code, string $wp_error_message, string $response_body ): void {
        $logger = function_exists( 'wc_get_logger' ) ? wc_get_logger() : null;
        if ( ! $logger ) {
            return;
        }
        $log_data = [
            'endpoint' => $endpoint,
            'method' => $method,
            'status_code' => $status_code,
            'wp_error_code' => $wp_error_code,
            'wp_error_message' => $wp_error_message,
            'response_body_length' => strlen( $response_body ),
        ];
        $logger->error( 'BWDK API Error: ' . wp_json_encode( $log_data, JSON_UNESCAPED_UNICODE ), ['source' => 'bwdk_api'] );
    }
}
