<?php
/**
 * ZERTI shop order detail — AJAX actions (status, print, email).
 */

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

/**
 * WooCommerce order status choices for ZERTI UI (slug => localized label).
 *
 * @return array<string, string>
 */
function zerti_wc_order_status_choices(): array
{
    if (!function_exists('wc_get_order_statuses')) {
        return [];
    }

    $choices = [];
    foreach (wc_get_order_statuses() as $status_key => $wc_label) {
        $slug = (string) str_replace('wc-', '', (string) $status_key);
        if ($slug === '') {
            continue;
        }
        $choices[$slug] = function_exists('zerti_wc_order_status_label_fa')
            ? zerti_wc_order_status_label_fa($slug)
            : (string) $wc_label;
    }

    return $choices;
}

/**
 * Verify AJAX request for shop order actions.
 */
function zerti_shop_order_action_verify_request(): int
{
    if (!check_ajax_referer('zerti_shop_order', 'nonce', false)) {
        wp_send_json_error(['message' => __('Invalid request.', 'zerti')], 403);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- verified via check_ajax_referer above.
    $order_id = isset($_POST['order_id']) ? absint(wp_unslash((string) $_POST['order_id'])) : 0;
    if ($order_id < 1 || !function_exists('wc_get_order')) {
        wp_send_json_error(['message' => __('Invalid order.', 'zerti')], 400);
    }

    if (!zerti_user_can_view_shop_order($order_id)) {
        wp_send_json_error(['message' => __('You do not have permission to perform this action.', 'zerti')], 403);
    }

    return $order_id;
}

/**
 * Update WooCommerce order status from ZERTI admin.
 */
function zerti_ajax_shop_order_update_status(): void
{
    $order_id = zerti_shop_order_action_verify_request();

    if (!current_user_can('edit_shop_order', $order_id)) {
        wp_send_json_error(['message' => __('You do not have permission to change status.', 'zerti')], 403);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in zerti_shop_order_action_verify_request().
    $status = isset($_POST['status']) ? sanitize_key(wp_unslash((string) $_POST['status'])) : '';
    if ($status === '') {
        wp_send_json_error(['message' => __('No status selected.', 'zerti')], 400);
    }

    $allowed = zerti_wc_order_status_choices();
    if (!isset($allowed[$status])) {
        wp_send_json_error(['message' => __('Selected status is not allowed.', 'zerti')], 400);
    }

    $order = wc_get_order($order_id);
    if (!is_a($order, 'WC_Order')) {
        wp_send_json_error(['message' => __('Order not found.', 'zerti')], 404);
    }

    if ($order->get_status() === $status) {
        wp_send_json_success([
            'message' => __('Order status unchanged.', 'zerti'),
            'status' => $status,
            'statusLabel' => zerti_wc_order_status_label_fa($status),
        ]);
    }

    $order->update_status($status, __('Status changed from ZERTI panel', 'zerti'), true);

    if (function_exists('zerti_invalidate_sidebar_badge_counts')) {
        zerti_invalidate_sidebar_badge_counts();
    }

    wp_send_json_success([
        'message' => __('Order status updated.', 'zerti'),
        'status' => $status,
        'statusLabel' => zerti_wc_order_status_label_fa($status),
    ]);
}

/**
 * Normalize a WooCommerce order note object for ZERTI detail UI.
 *
 * @param object $note WC_Order_Note or legacy note object.
 * @return array{id:int,content:string,date_label:string,author:string,is_customer:bool}
 */
function zerti_wc_order_note_detail_row(object $note): array
{
    $content = '';
    if (method_exists($note, 'get_content')) {
        $content = trim((string) $note->get_content());
    } elseif (isset($note->content)) {
        $content = trim((string) $note->content);
    }

    $ts = 0;
    if (method_exists($note, 'get_date_created')) {
        $created = $note->get_date_created();
        if ($created && is_a($created, 'WC_DateTime')) {
            $ts = (int) $created->getTimestamp();
        }
    } elseif (isset($note->date_created) && is_a($note->date_created, 'WC_DateTime')) {
        $ts = (int) $note->date_created->getTimestamp();
    }

    $date_label = '—';
    if ($ts > 0 && function_exists('zerti_format_datetime')) {
        $date_label = zerti_format_datetime($ts);
    } elseif ($ts > 0) {
        $date_label = wp_date('Y-m-d H:i', $ts);
    }

    $author = __('System', 'zerti');
    if (method_exists($note, 'get_added_by')) {
        $added_by = trim((string) $note->get_added_by());
        if ($added_by !== '') {
            $author = $added_by;
        }
    } elseif (isset($note->added_by) && trim((string) $note->added_by) !== '') {
        $author = trim((string) $note->added_by);
    }

    $is_customer = false;
    if (method_exists($note, 'is_customer_note')) {
        $is_customer = (bool) $note->is_customer_note();
    } elseif (isset($note->customer_note)) {
        $is_customer = (bool) $note->customer_note;
    }

    $id = 0;
    if (method_exists($note, 'get_id')) {
        $id = (int) $note->get_id();
    } elseif (isset($note->id)) {
        $id = (int) $note->id;
    }

    return [
        'id' => $id,
        'content' => $content,
        'date_label' => $date_label,
        'author' => $author,
        'is_customer' => $is_customer,
    ];
}

/**
 * Order notes for ZERTI order detail (newest first).
 *
 * @return list<array{id:int,content:string,date_label:string,author:string,is_customer:bool}>
 */
function zerti_wc_order_detail_notes(WC_Order $order): array
{
    if (!function_exists('wc_get_order_notes')) {
        return [];
    }

    $raw = wc_get_order_notes([
        'order_id' => $order->get_id(),
        'order' => 'DESC',
        'type' => 'any',
        'limit' => 100,
    ]);

    if (!is_array($raw)) {
        return [];
    }

    $rows = [];
    foreach ($raw as $note) {
        if (!is_object($note)) {
            continue;
        }
        $row = zerti_wc_order_note_detail_row($note);
        if ($row['content'] === '') {
            continue;
        }
        $rows[] = $row;
    }

    return $rows;
}

/**
 * Echo order notes list + add form (WooCommerce order notes metabox equivalent).
 */
function zerti_shop_order_detail_render_notes_section(WC_Order $order, bool $can_add_note): void
{
    $order_id = (int) $order->get_id();
    $notes = zerti_wc_order_detail_notes($order);
    ?>
    <div
        class="card zerti-shop-order-notes-card"
        data-zerti-order-id="<?php echo esc_attr((string) $order_id); ?>"
        aria-labelledby="zerti-shop-order-notes-heading-<?php echo esc_attr((string) $order_id); ?>"
    >
        <div class="card-header">
            <h3 id="zerti-shop-order-notes-heading-<?php echo esc_attr((string) $order_id); ?>" class="card-title h6 mb-0">
                <?php esc_html_e('Order notes', 'zerti'); ?>
            </h3>
        </div>
        <div class="card-body">
            <section class="zerti-shop-order-notes" aria-label="<?php esc_attr_e('Order notes', 'zerti'); ?>">
        <div class="zerti-shop-order-notes__list-wrap zerti-scrollbar-thin" role="region" aria-live="polite">
            <ul class="zerti-shop-order-notes__list list-unstyled mb-0">
                <?php if ($notes === []) : ?>
                    <li class="zerti-shop-order-notes__empty text-body-secondary">
                        <?php esc_html_e('No notes yet.', 'zerti'); ?>
                    </li>
                <?php else : ?>
                    <?php foreach ($notes as $note_row) : ?>
                        <?php
                        $note_class = $note_row['is_customer']
                            ? 'zerti-shop-order-notes__item zerti-shop-order-notes__item--customer'
                            : 'zerti-shop-order-notes__item zerti-shop-order-notes__item--private';
                        $type_label = $note_row['is_customer']
                            ? __('Customer note', 'zerti')
                            : __('Private note', 'zerti');
                        ?>
                        <li class="<?php echo esc_attr($note_class); ?>" data-note-id="<?php echo esc_attr((string) $note_row['id']); ?>">
                            <p class="zerti-shop-order-notes__meta mb-1">
                                <time datetime="<?php echo esc_attr((string) $note_row['date_label']); ?>">
                                    <?php echo esc_html(zerti_to_persian_digits($note_row['date_label'])); ?>
                                </time>
                                <span class="zerti-shop-order-notes__meta-sep" aria-hidden="true">·</span>
                                <span class="zerti-shop-order-notes__author"><?php echo esc_html($note_row['author']); ?></span>
                                <span class="zerti-shop-order-notes__meta-sep" aria-hidden="true">·</span>
                                <span class="zerti-shop-order-notes__type badge rounded-pill"><?php echo esc_html($type_label); ?></span>
                            </p>
                            <div class="zerti-shop-order-notes__content" dir="<?php echo esc_attr(function_exists('zerti_get_text_direction') ? zerti_get_text_direction() : 'rtl'); ?>">
                                <?php echo wp_kses_post(wpautop(wp_kses_post($note_row['content']))); ?>
                            </div>
                        </li>
                    <?php endforeach; ?>
                <?php endif; ?>
            </ul>
        </div>

        <?php if ($can_add_note) : ?>
            <div class="zerti-shop-order-notes__add">
                <label class="form-label visually-hidden" for="zerti-shop-order-note-<?php echo esc_attr((string) $order_id); ?>">
                    <?php esc_html_e('Add note', 'zerti'); ?>
                </label>
                <textarea
                    id="zerti-shop-order-note-<?php echo esc_attr((string) $order_id); ?>"
                    class="form-control form-control-sm zerti-shop-order-note-input"
                    rows="3"
                    dir="rtl"
                    placeholder="<?php esc_attr_e('Write order note…', 'zerti'); ?>"
                    data-zerti-order-id="<?php echo esc_attr((string) $order_id); ?>"
                ></textarea>
                <div class="zerti-shop-order-notes__add-actions">
                    <div class="form-check zerti-shop-order-notes__notify">
                        <input
                            class="form-check-input zerti-shop-order-note-notify"
                            type="checkbox"
                            value="1"
                            id="zerti-shop-order-note-notify-<?php echo esc_attr((string) $order_id); ?>"
                            data-zerti-order-id="<?php echo esc_attr((string) $order_id); ?>"
                        />
                        <label class="form-check-label" for="zerti-shop-order-note-notify-<?php echo esc_attr((string) $order_id); ?>">
                            <?php esc_html_e('Notify customer', 'zerti'); ?>
                        </label>
                    </div>
                    <button
                        type="button"
                        class="btn btn-sm btn-primary zerti-shop-order-note-add-btn"
                        data-zerti-order-id="<?php echo esc_attr((string) $order_id); ?>"
                    >
                        <?php esc_html_e('Add note', 'zerti'); ?>
                    </button>
                </div>
            </div>
        <?php endif; ?>
            </section>
        </div>
    </div>
    <?php
}

/**
 * Add a WooCommerce order note from ZERTI admin.
 */
function zerti_ajax_shop_order_add_note(): void
{
    $order_id = zerti_shop_order_action_verify_request();

    if (!current_user_can('edit_shop_order', $order_id)) {
        wp_send_json_error(['message' => __('You do not have permission to add notes.', 'zerti')], 403);
    }

    // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in zerti_shop_order_action_verify_request().
    $note_text = isset($_POST['note']) ? trim(wp_kses_post(wp_unslash((string) $_POST['note']))) : '';
    $notify_customer = !empty($_POST['notify_customer']);
    if ($note_text === '') {
        // phpcs:enable WordPress.Security.NonceVerification.Missing
        wp_send_json_error(['message' => __('Note text is empty.', 'zerti')], 400);
    }

    $order = wc_get_order($order_id);
    if (!is_a($order, 'WC_Order')) {
        // phpcs:enable WordPress.Security.NonceVerification.Missing
        wp_send_json_error(['message' => __('Order not found.', 'zerti')], 404);
    }

    // phpcs:enable WordPress.Security.NonceVerification.Missing
    $order->add_order_note($note_text, $notify_customer, true);

    wp_send_json_success([
        'message' => $notify_customer
            ? __('Note added and customer notified.', 'zerti')
            : __('Note added.', 'zerti'),
    ]);
}

/**
 * Build URL to open a printable invoice/shipping document.
 */
function zerti_shop_order_print_document_url(int $order_id, string $doc_type): string
{
    $doc_type = $doc_type === 'shipping' ? 'shipping' : 'invoice';

    return add_query_arg(
        [
            'action' => 'zerti_shop_order_print',
            'order_id' => $order_id,
            'doc' => $doc_type,
            'nonce' => wp_create_nonce('zerti_shop_order_print_' . $order_id),
        ],
        admin_url('admin-ajax.php')
    );
}

/**
 * Order statuses that may open the ZERTI invoice view or PDF.
 *
 * @return list<string>
 */
function zerti_shop_order_invoice_allowed_statuses(): array
{
    return apply_filters(
        'zerti_shop_order_invoice_allowed_statuses',
        ['completed', 'processing', 'pending']
    );
}

/**
 * Whether an order status may open the invoice view / PDF.
 *
 * @param WC_Order|string $order_or_status Order object or status slug.
 */
function zerti_shop_order_can_open_invoice($order_or_status): bool
{
    if (is_a($order_or_status, 'WC_Order')) {
        $status = $order_or_status->get_status();
    } else {
        $status = sanitize_key(str_replace('wc-', '', (string) $order_or_status));
    }

    return in_array($status, zerti_shop_order_invoice_allowed_statuses(), true);
}

/**
 * Order statuses that show PDF download actions in the orders list.
 *
 * @return list<string>
 */
function zerti_shop_order_grid_pdf_download_statuses(): array
{
    return apply_filters(
        'zerti_shop_order_grid_pdf_download_statuses',
        ['completed', 'processing']
    );
}

/**
 * Font Awesome icon classes for order PDF document types (invoice, postal label, warehouse sheet).
 *
 * @param string $doc_type `invoice`, `postal_label`, or `warehouse_sheet`.
 */
function zerti_shop_order_pdf_doc_icon_classes(string $doc_type): string
{
    switch (sanitize_key($doc_type)) {
        case 'postal_label':
            $icon = 'fa-envelope';
            break;
        case 'warehouse_sheet':
            $icon = 'fa-warehouse';
            break;
        default:
            $icon = 'fa-file-invoice';
            break;
    }

    return 'fa-solid ' . $icon;
}

/**
 * AG Grid cell HTML: icon links to invoice / postal / warehouse PDFs (completed & processing).
 */
function zerti_shop_order_grid_pdf_download_html(int $order_id, string $status_slug): string
{
    $order_id = absint($order_id);
    $status_slug = sanitize_key(str_replace('wc-', '', $status_slug));
    if (
        $order_id < 1
        || !in_array($status_slug, zerti_shop_order_grid_pdf_download_statuses(), true)
        || !function_exists('zerti_shop_order_pdf_is_enabled')
        || !zerti_shop_order_pdf_is_enabled()
    ) {
        return '';
    }

    $docs = [
        'invoice' => __('Download sales invoice', 'zerti'),
        'postal_label' => __('Download postal label', 'zerti'),
        'warehouse_sheet' => __('Download warehouse sheet', 'zerti'),
    ];
    $links = [];
    foreach ($docs as $doc_type => $label) {
        $url = zerti_shop_order_pdf_document_url($order_id, $doc_type);
        if ($url === '') {
            continue;
        }
        $doc_slug = sanitize_key($doc_type);
        $links[] = sprintf(
            '<a href="%1$s" class="zerti-shop-orders-grid-download-btn zerti-shop-orders-grid-download-btn--%3$s" target="_blank" rel="noopener noreferrer" title="%2$s" aria-label="%2$s"><i class="%4$s" aria-hidden="true"></i></a>',
            esc_url($url),
            esc_attr($label),
            esc_attr($doc_slug),
            esc_attr(zerti_shop_order_pdf_doc_icon_classes($doc_type))
        );
    }

    if ($links === []) {
        return '';
    }

    return sprintf(
        '<div class="zerti-shop-orders-grid-downloads" role="group" aria-label="%s">%s</div>',
        esc_attr__('Order PDF downloads', 'zerti'),
        implode('', $links)
    );
}

/**
 * Allowed server-generated shop order PDF document types.
 *
 * @return list<string>
 */
function zerti_shop_order_pdf_document_types(): array
{
    return ['invoice', 'postal_label', 'warehouse_sheet'];
}

/**
 * Normalize a shop order PDF document type slug.
 */
function zerti_shop_order_pdf_normalize_doc_type(string $doc_type): string
{
    $doc_type = sanitize_key($doc_type);
    if (!in_array($doc_type, zerti_shop_order_pdf_document_types(), true)) {
        return 'invoice';
    }

    return $doc_type;
}

/**
 * Build URL to download a shop order PDF generated on the server.
 *
 * @param string $doc_type `invoice`, `postal_label`, or `warehouse_sheet`.
 */
function zerti_shop_order_pdf_document_url(int $order_id, string $doc_type = 'invoice'): string
{
    $doc_type = zerti_shop_order_pdf_normalize_doc_type($doc_type);

    // admin-post.php + zerti_* query args — avoids admin-ajax.php hooks used by other invoice PDF plugins.
    $url = add_query_arg(
        [
            'action' => 'zerti_download_order_pdf',
            'zerti_oid' => $order_id,
            'zerti_doc' => $doc_type,
            'zerti_nonce' => wp_create_nonce('zerti_shop_order_pdf_' . $doc_type . '_' . $order_id),
        ],
        admin_url('admin-post.php')
    );

    return (string) apply_filters('zerti_shop_order_pdf_document_url', $url, $order_id, $doc_type);
}

/**
 * Download filename for a shop order PDF document.
 */
function zerti_shop_order_pdf_filename(int $order_id, string $doc_type): string
{
    $doc_type = zerti_shop_order_pdf_normalize_doc_type($doc_type);
    $slug = $doc_type === 'postal_label'
        ? 'postal-label'
        : ($doc_type === 'warehouse_sheet' ? 'warehouse-sheet' : 'invoice');

    return sprintf('zerti-%s-%d.pdf', $slug, $order_id);
}

/**
 * AJAX: return print document URL.
 */
function zerti_ajax_shop_order_print_link(): void
{
    $order_id = zerti_shop_order_action_verify_request();

    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified in zerti_shop_order_action_verify_request().
    $doc = isset($_POST['doc']) ? sanitize_key(wp_unslash((string) $_POST['doc'])) : 'invoice';
    if ($doc !== 'shipping') {
        $doc = 'invoice';
    }

    wp_send_json_success([
        'url' => zerti_shop_order_print_document_url($order_id, $doc),
    ]);
}

/**
 * Output printable HTML and trigger browser print dialog.
 */
function zerti_ajax_shop_order_print_output(): void
{
    if (!is_user_logged_in()) {
        wp_die(esc_html__('Unauthorized.', 'zerti'), '', ['response' => 403]);
    }

    if (!function_exists('zerti_user_can_access_admin') || !zerti_user_can_access_admin()) {
        wp_die(esc_html__('Unauthorized.', 'zerti'), '', ['response' => 403]);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verified before further $_GET reads.
    $order_id = isset($_GET['order_id']) ? absint(wp_unslash((string) $_GET['order_id'])) : 0;
    $nonce = isset($_GET['nonce']) ? sanitize_text_field(wp_unslash((string) $_GET['nonce'])) : '';

    if ($order_id < 1 || $nonce === '' || !wp_verify_nonce($nonce, 'zerti_shop_order_print_' . $order_id)) {
        wp_die(esc_html__('Invalid request.', 'zerti'), '', ['response' => 403]);
    }

    if (!function_exists('zerti_user_can_view_shop_order') || !zerti_user_can_view_shop_order($order_id)) {
        wp_die(esc_html__('You do not have permission to view this order.', 'zerti'), '', ['response' => 403]);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce verified above.
    $doc = isset($_GET['doc']) ? sanitize_key(wp_unslash((string) $_GET['doc'])) : 'invoice';
    $doc = ($doc === 'shipping') ? 'shipping' : 'invoice';

    $order = function_exists('wc_get_order') ? wc_get_order($order_id) : null;
    if (!is_a($order, 'WC_Order')) {
        wp_die(esc_html__('Order not found.', 'zerti'), '', ['response' => 404]);
    }

    if (!function_exists('zerti_shop_order_can_open_invoice') || !zerti_shop_order_can_open_invoice($order)) {
        wp_die(esc_html__('Invoice is only available for completed, processing, or pending payment orders.', 'zerti'), '', ['response' => 403]);
    }

    $path = $doc === 'shipping'
        ? plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/shop-order-print-shipping.php'
        : plugin_dir_path(ZERTI_PLUGIN_FILE) . 'includes/components/shop-order-print-invoice.php';

    if (!is_readable($path)) {
        wp_die(esc_html__('Print template is not available.', 'zerti'), '', ['response' => 500]);
    }

    if (function_exists('zerti_enqueue_shop_order_print_assets')) {
        zerti_enqueue_shop_order_print_assets($doc);
    }

    nocache_headers();
    status_header(200);
    header('Content-Type: text/html; charset=utf-8');
    /** @var WC_Order $order */
    include $path;
    exit;
}

/**
 * Writable temp directory for the bundled PDF engine on shared hosting.
 */
function zerti_shop_order_pdf_temp_dir(): string
{
    $upload_dir = wp_upload_dir(null, false);
    $base_dir = is_array($upload_dir) && empty($upload_dir['error']) && !empty($upload_dir['basedir'])
        ? (string) $upload_dir['basedir']
        : sys_get_temp_dir();
    $dir = trailingslashit($base_dir) . 'zerti-pdf';

    if (!is_dir($dir)) {
        wp_mkdir_p($dir);
    }

    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- mPDF needs a writable cache directory.
    return is_dir($dir) && is_writable($dir) ? $dir : sys_get_temp_dir();
}

/**
 * Scale natural image dimensions to fit a max box while preserving aspect ratio.
 *
 * @return array{width:int,height:int}|null
 */
function zerti_shop_order_pdf_fit_image_dimensions(int $natural_width, int $natural_height, int $max_width, int $max_height): ?array
{
    if ($natural_width < 1 || $natural_height < 1 || $max_width < 1 || $max_height < 1) {
        return null;
    }

    $scale = min($max_width / $natural_width, $max_height / $natural_height, 1.0);

    return [
        'width' => max(1, (int) round($natural_width * $scale)),
        'height' => max(1, (int) round($natural_height * $scale)),
    ];
}

/**
 * Pixel size of a media attachment image.
 *
 * @return array{width:int,height:int}|null
 */
function zerti_shop_order_pdf_attachment_image_size(int $attachment_id): ?array
{
    if ($attachment_id < 1) {
        return null;
    }

    $meta = wp_get_attachment_metadata($attachment_id);
    if (is_array($meta)) {
        $width = isset($meta['width']) ? (int) $meta['width'] : 0;
        $height = isset($meta['height']) ? (int) $meta['height'] : 0;
        if ($width > 0 && $height > 0) {
            return ['width' => $width, 'height' => $height];
        }
    }

    $file = get_attached_file($attachment_id);
    if (!is_string($file) || $file === '' || !is_readable($file)) {
        return null;
    }

    $size = wp_getimagesize($file);
    if (!is_array($size) || empty($size[0]) || empty($size[1])) {
        return null;
    }

    return ['width' => (int) $size[0], 'height' => (int) $size[1]];
}

/**
 * Display size for a PDF logo (capped at 100×100, original aspect ratio).
 *
 * @return array{width:int,height:int}|null
 */
function zerti_shop_order_pdf_logo_display_size(int $attachment_id, int $max_width = 100, int $max_height = 100): ?array
{
    $natural = zerti_shop_order_pdf_attachment_image_size($attachment_id);
    if ($natural === null) {
        return null;
    }

    return zerti_shop_order_pdf_fit_image_dimensions(
        $natural['width'],
        $natural['height'],
        $max_width,
        $max_height
    );
}

/**
 * Attachment ID for the logo shown on PDFs (invoice identity, then theme, then site icon).
 */
function zerti_shop_order_pdf_resolve_logo_attachment_id(array $invoice_identity = []): int
{
    $logo_id = absint($invoice_identity['logo_id'] ?? 0);
    if ($logo_id > 0) {
        return $logo_id;
    }

    $theme_logo = (int) get_theme_mod('custom_logo');
    if ($theme_logo > 0) {
        return $theme_logo;
    }

    return (int) get_option('site_icon');
}

/**
 * Logo markup for PDF output (explicit width/height so mPDF/Dompdf keep aspect ratio).
 */
function zerti_shop_order_pdf_logo_img_html(string $logo_url, int $attachment_id = 0): string
{
    if ($logo_url === '') {
        return '';
    }

    if ($attachment_id < 1 && function_exists('attachment_url_to_postid')) {
        $attachment_id = (int) attachment_url_to_postid($logo_url);
    }

    $size_attrs = '';
    if ($attachment_id > 0) {
        $display = zerti_shop_order_pdf_logo_display_size($attachment_id);
        if (is_array($display)) {
            $size_attrs = ' width="' . (int) $display['width'] . '" height="' . (int) $display['height'] . '"';
        }
    }

    return '<img class="zpdf-logo" src="' . esc_url($logo_url) . '" alt="' . esc_attr__('Logo', 'zerti') . '"' . $size_attrs . ' />';
}

/**
 * Text direction for PDF documents (follows Regional Settings → text direction).
 */
function zerti_shop_order_pdf_direction(): string
{
    return function_exists('zerti_get_text_direction') ? zerti_get_text_direction() : 'rtl';
}

/**
 * Whether PDF output uses RTL layout.
 */
function zerti_shop_order_pdf_is_rtl(): bool
{
    return zerti_shop_order_pdf_direction() === 'rtl';
}

/**
 * Opposite direction for column-flip layout tables (header meta side, totals side).
 */
function zerti_shop_order_pdf_layout_flip_direction(): string
{
    return zerti_shop_order_pdf_is_rtl() ? 'ltr' : 'rtl';
}

/**
 * BCP 47 lang attribute for the PDF HTML shell.
 */
function zerti_shop_order_pdf_html_lang(): string
{
    if (function_exists('zerti_get_plugin_settings')) {
        $settings = zerti_get_plugin_settings();
        $locale = isset($settings['general']['admin_locale']) ? trim((string) $settings['general']['admin_locale']) : '';
        if ($locale !== '') {
            return str_replace('_', '-', $locale);
        }
    }

    $locale = function_exists('determine_locale') ? determine_locale() : get_locale();

    return str_replace('_', '-', (string) $locale);
}

/**
 * Minimal PDF-safe CSS for the invoice card.
 */
function zerti_shop_order_pdf_css(): string
{
    $base = zerti_read_plugin_file('assets/css/zerti-pdf-document.css');

    return $base . zerti_shop_order_pdf_postal_label_css();
}

/**
 * Dynamic CSS for postal label PDF (seller/recipient font size and weight).
 */
function zerti_shop_order_pdf_postal_label_css(): string
{
    if (!function_exists('zerti_postal_pdf_settings')) {
        return '';
    }

    $postal = zerti_postal_pdf_settings();
    $seller_size = esc_attr($postal['seller_font_size']);
    $seller_weight = esc_attr(
        function_exists('zerti_postal_pdf_font_weight_css_value')
            ? zerti_postal_pdf_font_weight_css_value((string) $postal['seller_font_weight'])
            : (string) $postal['seller_font_weight']
    );
    $customer_size = esc_attr($postal['customer_font_size']);
    $customer_weight = esc_attr(
        function_exists('zerti_postal_pdf_font_weight_css_value')
            ? zerti_postal_pdf_font_weight_css_value((string) $postal['customer_font_weight'])
            : (string) $postal['customer_font_weight']
    );

    return ''
        . "\n\n"
        . ".zpdf-page--postal-label .zpdf-party--sender .zpdf-party-body,\n"
        . ".zpdf-page--postal-label .zpdf-party--sender .zpdf-party-label,\n"
        . ".zpdf-page--postal-label .zpdf-party--sender .zpdf-party-value,\n"
        . ".zpdf-page--postal-label .zpdf-party--sender .zpdf-party-cell,\n"
        . ".zpdf-page--postal-label .zpdf-party--sender .zpdf-party-grid {\n"
        . '    font-size: ' . $seller_size . "pt !important;\n"
        . '    font-weight: ' . $seller_weight . " !important;\n"
        . "}\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-body,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-label,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-value,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-cell,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-grid,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-grid--postal-recipient,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-grid--postal-recipient .zpdf-party-cell,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-grid--postal-recipient .zpdf-party-label,\n"
        . ".zpdf-page--postal-label .zpdf-party--recipient .zpdf-party-grid--postal-recipient .zpdf-party-value {\n"
        . '    font-size: ' . $customer_size . "pt !important;\n"
        . '    font-weight: ' . $customer_weight . " !important;\n"
        . "}\n"
        . ".zpdf-party-grid--postal-recipient {\n"
        . "    width: 100%;\n"
        . "    border-collapse: collapse;\n"
        . "    table-layout: fixed;\n"
        . "}\n"
        . ".zpdf-party-grid--postal-recipient .zpdf-party-cell--postal-name {\n"
        . "    width: 100%;\n"
        . "    padding: 0 0 5px;\n"
        . "    vertical-align: top;\n"
        . "    white-space: nowrap;\n"
        . "    line-height: 1.45;\n"
        . "}\n"
        . ".zpdf-party-grid--postal-recipient .zpdf-party-cell--postal-rest {\n"
        . "    width: 100%;\n"
        . "    padding: 0;\n"
        . "    vertical-align: top;\n"
        . "    white-space: normal;\n"
        . "    line-height: 1.45;\n"
        . "    word-wrap: break-word;\n"
        . "}\n"
        . ".zpdf-party-grid--postal-recipient .zpdf-party-sep {\n"
        . "    display: inline;\n"
        . "    margin-inline: 0.25em;\n"
        . "    color: #9ca3af;\n"
        . "}\n"
        . ".zpdf-postal-maps {\n"
        . "    margin-top: 10px;\n"
        . "    padding: 8px 10px;\n"
        . "    border: 1px solid #e5e7eb;\n"
        . "    border-radius: 10px;\n"
        . "    background: #f8fafc;\n"
        . "    text-align: center;\n"
        . "}\n"
        . ".zpdf-postal-maps-title {\n"
        . "    margin: 0 0 6px;\n"
        . "    font-size: 8pt;\n"
        . "    font-weight: 700;\n"
        . "    color: #6b7280;\n"
        . "}\n"
        . ".zpdf-postal-maps-qr {\n"
        . "    display: block;\n"
        . "    margin: 0 auto;\n"
        . "    width: 72px;\n"
        . "    height: 72px;\n"
        . "}\n";
}

/**
 * Short field key for postal label party rows (billing_/shipping_/order_ stripped).
 */
function zerti_shop_order_pdf_postal_field_short_key(string $field_key): string
{
    $field_key = sanitize_key($field_key);
    if (preg_match('/^(?:billing|shipping|order)_(.+)$/', $field_key, $matches)) {
        return sanitize_key((string) $matches[1]);
    }

    return $field_key;
}

/**
 * Layout bucket for postal recipient fields.
 */
function zerti_shop_order_pdf_postal_field_bucket(string $short_key, bool $wide): string
{
    $short_key = strtolower($short_key);
    if ($wide || in_array($short_key, ['address_1', 'address_2'], true)) {
        return 'wide';
    }
    if (in_array($short_key, ['first_name', 'firstname', 'last_name', 'lastname', 'family'], true)) {
        return 'name';
    }
    if (in_array($short_key, ['state', 'city', 'postcode', 'country', 'company'], true)) {
        return 'address';
    }
    if (in_array($short_key, ['phone', 'mobile'], true)) {
        return 'contact';
    }

    return 'other';
}

/**
 * Sort customer rows for postal label: name first, then state/city/address, then phones.
 *
 * @param list<array{label:string,value:string,key:string,wide?:bool,html?:string}> $rows
 * @return list<array{label:string,value:string,key:string,wide?:bool,html?:string}>
 */
function zerti_shop_order_pdf_sort_postal_customer_rows(array $rows): array
{
    $buckets = [
        'name' => [],
        'address' => [],
        'wide' => [],
        'other' => [],
        'contact' => [],
    ];

    foreach ($rows as $row) {
        $short_key = zerti_shop_order_pdf_postal_field_short_key((string) ($row['key'] ?? ''));
        $wide = !empty($row['wide']) || in_array($short_key, ['address_1', 'address_2'], true);
        $bucket = zerti_shop_order_pdf_postal_field_bucket($short_key, $wide);
        $buckets[$bucket][] = $row;
    }

    $name_rank = [
        'first_name' => 0,
        'firstname' => 1,
        'last_name' => 2,
        'lastname' => 3,
        'family' => 4,
    ];
    usort(
        $buckets['name'],
        static function (array $a, array $b) use ($name_rank): int {
            $ka = zerti_shop_order_pdf_postal_field_short_key((string) ($a['key'] ?? ''));
            $kb = zerti_shop_order_pdf_postal_field_short_key((string) ($b['key'] ?? ''));
            $ra = $name_rank[$ka] ?? 99;
            $rb = $name_rank[$kb] ?? 99;
            if ($ra === $rb) {
                return 0;
            }

            return $ra < $rb ? -1 : 1;
        }
    );

    $address_rank = [
        'state' => 0,
        'city' => 1,
        'postcode' => 2,
        'country' => 3,
        'company' => 4,
    ];
    usort(
        $buckets['address'],
        static function (array $a, array $b) use ($address_rank): int {
            $ka = zerti_shop_order_pdf_postal_field_short_key((string) ($a['key'] ?? ''));
            $kb = zerti_shop_order_pdf_postal_field_short_key((string) ($b['key'] ?? ''));
            $ra = $address_rank[$ka] ?? 99;
            $rb = $address_rank[$kb] ?? 99;
            if ($ra === $rb) {
                return 0;
            }

            return $ra < $rb ? -1 : 1;
        }
    );

    return array_merge(
        $buckets['name'],
        $buckets['address'],
        $buckets['wide'],
        $buckets['other'],
        $buckets['contact']
    );
}

/**
 * @param list<array{label:string,value:string,wide:bool,short_key?:string,html?:string}> $items
 */
function zerti_shop_order_pdf_render_party_fields_grid_html(
    array $items,
    string $party_inline_style,
    callable $field_html
): string {
    $html = '';
    $count = count($items);
    $index = 0;

    while ($index < $count) {
        $item = $items[$index];

        if ($item['wide']) {
            if ($index + 1 < $count && !$items[$index + 1]['wide']) {
                $html .= '<tr>'
                    . '<td colspan="2" class="zpdf-party-cell zpdf-party-cell--wide"' . $party_inline_style . '>' . $field_html($item) . '</td>'
                    . '<td class="zpdf-party-cell"' . $party_inline_style . '>' . $field_html($items[$index + 1]) . '</td>'
                    . '</tr>';
                $index += 2;
                continue;
            }
            $html .= '<tr><td colspan="3" class="zpdf-party-cell zpdf-party-cell--wide"' . $party_inline_style . '>' . $field_html($item) . '</td></tr>';
            ++$index;
            continue;
        }

        $html .= '<tr>';
        $placed = 0;
        while ($placed < 3 && $index < $count && !$items[$index]['wide']) {
            $html .= '<td class="zpdf-party-cell" width="33%"' . $party_inline_style . '>' . $field_html($items[$index]) . '</td>';
            ++$index;
            ++$placed;
        }
        while ($placed < 3) {
            $html .= '<td class="zpdf-party-cell"' . $party_inline_style . '></td>';
            ++$placed;
        }
        $html .= '</tr>';
    }

    return $html;
}

/**
 * Whether a postal recipient field is first or family name (top line only).
 */
function zerti_shop_order_pdf_postal_is_name_field(string $short_key): bool
{
    $short_key = strtolower(sanitize_key($short_key));

    return in_array($short_key, ['first_name', 'firstname', 'last_name', 'lastname', 'family'], true);
}

/**
 * Build inline HTML for a list of postal recipient field parts.
 *
 * @param list<string> $parts
 */
function zerti_shop_order_pdf_render_postal_recipient_parts_html(array $parts, string $separator): string
{
    if ($parts === []) {
        return '';
    }

    return implode($separator, $parts);
}

/**
 * Postal recipient name row content: first + family (labels, space between, no comma).
 *
 * @param list<array{label:string,value:string,wide:bool,short_key?:string,html?:string}> $name_items
 */
function zerti_shop_order_pdf_render_postal_recipient_name_row_content(
    array $name_items,
    callable $field_html
): string {
    $parts = [];
    foreach ($name_items as $item) {
        if (trim((string) ($item['value'] ?? '')) === '') {
            continue;
        }
        $parts[] = $field_html($item);
    }

    $separator = '<span class="zpdf-party-sep zpdf-party-sep--space" aria-hidden="true"> </span>';

    return zerti_shop_order_pdf_render_postal_recipient_parts_html($parts, $separator);
}

/**
 * Postal recipient second row: all fields after name/family (label: value, separated by ،).
 *
 * @param list<array{label:string,value:string,wide:bool,short_key?:string,html?:string}> $items
 */
function zerti_shop_order_pdf_render_postal_recipient_rest_row_content(
    array $items,
    callable $field_html
): string {
    $parts = [];
    foreach ($items as $item) {
        if (trim((string) ($item['value'] ?? '')) === '') {
            continue;
        }
        $parts[] = $field_html($item);
    }

    $separator = '<span class="zpdf-party-sep" aria-hidden="true">،</span>';

    return zerti_shop_order_pdf_render_postal_recipient_parts_html($parts, $separator);
}

/**
 * Postal recipient: row 1 = name + family; row 2 = everything else (PDF-safe table layout).
 *
 * @param list<array{label:string,value:string,wide:bool,short_key?:string,html?:string}> $items
 */
function zerti_shop_order_pdf_render_postal_recipient_party_fields_inline(
    array $items,
    string $party_inline_style,
    callable $field_html
): string {
    if ($items === []) {
        return '<p style="margin:0;color:#6b7280;">—</p>';
    }

    $name_items = [];
    $rest_items = [];

    foreach ($items as $item) {
        $short_key = (string) ($item['short_key'] ?? '');
        if (zerti_shop_order_pdf_postal_is_name_field($short_key)) {
            $name_items[] = $item;
            continue;
        }
        $rest_items[] = $item;
    }

    $name_content = zerti_shop_order_pdf_render_postal_recipient_name_row_content($name_items, $field_html);
    $rest_content = zerti_shop_order_pdf_render_postal_recipient_rest_row_content($rest_items, $field_html);

    if ($name_content === '' && $rest_content === '') {
        return '<p style="margin:0;color:#6b7280;">—</p>';
    }

    $dir = esc_attr(zerti_shop_order_pdf_direction());
    $html = '<table class="zpdf-party-grid zpdf-party-grid--postal-recipient" dir="'
        . $dir
        . '" width="100%" cellpadding="0" cellspacing="0" role="presentation">';

    if ($name_content !== '') {
        $html .= '<tr><td class="zpdf-party-cell zpdf-party-cell--postal-name"'
            . $party_inline_style
            . '>'
            . $name_content
            . '</td></tr>';
    }

    if ($rest_content !== '') {
        $html .= '<tr><td class="zpdf-party-cell zpdf-party-cell--postal-rest"'
            . $party_inline_style
            . '>'
            . $rest_content
            . '</td></tr>';
    }

    $html .= '</table>';

    return $html;
}

/**
 * Multi-column party fields for PDF (label: value, RTL, up to 3 per row).
 *
 * @param list<array{label:string,value:string,key?:string}> $rows
 */
function zerti_shop_order_pdf_render_party_fields(array $rows, string $party_modifier = ''): string
{
    $party_inline_style = zerti_shop_order_pdf_postal_party_inline_style($party_modifier);
    $is_postal_recipient = $party_modifier === 'zpdf-party--recipient';

    $items = [];
    foreach ($rows as $row) {
        $label = trim((string) ($row['label'] ?? ''));
        $value = trim((string) ($row['value'] ?? ''));
        if ($value === '' || $label === '') {
            continue;
        }
        if (function_exists('zerti_to_persian_digits')) {
            $label = zerti_to_persian_digits($label);
            $value = zerti_to_persian_digits($value);
        }
        $field_key = isset($row['key']) ? (string) $row['key'] : '';
        $short_key = zerti_shop_order_pdf_postal_field_short_key($field_key);
        $wide = !empty($row['wide']) || in_array($short_key, ['address_1', 'address_2', 'company'], true);
        $item = [
            'label' => $label,
            'value' => $value,
            'wide' => $wide,
            'short_key' => $short_key,
        ];
        if (!empty($row['html'])) {
            $item['html'] = (string) $row['html'];
        }
        $items[] = $item;
    }

    if ($items === []) {
        return '<p style="margin:0;color:#6b7280;">—</p>';
    }

    $field_html = static function (array $item) use ($party_inline_style): string {
        $value_html = !empty($item['html'])
            ? wp_kses(
                (string) $item['html'],
                [
                    'a' => [
                        'href' => true,
                        'class' => true,
                        'target' => true,
                        'rel' => true,
                    ],
                ]
            )
            : esc_html((string) ($item['value'] ?? ''));

        return '<span class="zpdf-party-label"' . $party_inline_style . '>' . esc_html($item['label']) . ':</span> '
            . '<span class="zpdf-party-value"' . $party_inline_style . '>' . $value_html . '</span>';
    };

    if ($is_postal_recipient) {
        return zerti_shop_order_pdf_render_postal_recipient_party_fields_inline($items, $party_inline_style, $field_html);
    }

    return '<table class="zpdf-party-grid" dir="' . esc_attr(zerti_shop_order_pdf_direction()) . '" width="100%" role="presentation"' . $party_inline_style . '>'
        . zerti_shop_order_pdf_render_party_fields_grid_html($items, $party_inline_style, $field_html)
        . '</table>';
}

/**
 * Inner HTML for a shop order PDF document (before document wrapper).
 */
function zerti_shop_order_pdf_body_html(WC_Order $order, string $doc_type = 'invoice'): string
{
    $doc_type = zerti_shop_order_pdf_normalize_doc_type($doc_type);

    switch ($doc_type) {
        case 'postal_label':
            return zerti_shop_order_pdf_postal_label_html($order);
        case 'warehouse_sheet':
            return zerti_shop_order_pdf_warehouse_sheet_html($order);
        default:
            return zerti_shop_order_pdf_formal_invoice_html($order);
    }
}

/**
 * Seller field rows for PDF documents (from plugin identity settings).
 *
 * @return list<array{label:string,value:string,key:string}>
 */
function zerti_shop_order_pdf_collect_seller_rows(WC_Order $order): array
{
    $seller_rows = [];
    $zerti_invoice_settings = function_exists('zerti_get_plugin_settings') ? (array) zerti_get_plugin_settings() : [];
    $zerti_invoice_identity = isset($zerti_invoice_settings['identity']) && is_array($zerti_invoice_settings['identity'])
        ? $zerti_invoice_settings['identity']
        : [];

    if ($zerti_invoice_settings === []) {
        return $seller_rows;
    }

    $zerti_seller_name = trim((string) ($zerti_invoice_identity['name'] ?? ''));
    $show_custom_field_in_invoice = !empty($zerti_invoice_settings['general']['show_custom_field_in_invoice']);

    if ($zerti_seller_name !== '') {
        $seller_rows[] = [
            'label' => __('Seller', 'zerti'),
            'value' => function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($zerti_seller_name) : $zerti_seller_name,
            'key' => 'company',
        ];
    }

    $maybe_add = static function (array &$rows, string $label, string $value, string $key): void {
        $value = trim($value);
        if ($value === '') {
            return;
        }
        $rows[] = [
            'label' => function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($label) : $label,
            'value' => function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($value) : $value,
            'key' => $key,
        ];
    };

    $zerti_seller_custom_field_label = trim((string) ($zerti_invoice_identity['custom_field_label'] ?? ''));
    $zerti_seller_custom_field_value = trim((string) ($zerti_invoice_identity['custom_field_value'] ?? ''));
    if ($show_custom_field_in_invoice && $zerti_seller_custom_field_label !== '' && $zerti_seller_custom_field_value !== '') {
        $maybe_add($seller_rows, $zerti_seller_custom_field_label, $zerti_seller_custom_field_value, 'seller_custom_field');
    }

    $maybe_add($seller_rows, __('Registration number', 'zerti'), (string) ($zerti_invoice_identity['registration_number'] ?? ''), 'seller_registration_number');
    $maybe_add($seller_rows, __('Economic code', 'zerti'), (string) ($zerti_invoice_identity['economic_code'] ?? ''), 'seller_economic_code');
    $maybe_add($seller_rows, __('National ID', 'zerti'), (string) ($zerti_invoice_identity['national_id'] ?? ''), 'seller_national_id');
    $maybe_add($seller_rows, __('Address', 'zerti'), (string) ($zerti_invoice_identity['postal_address'] ?? ''), 'address_1');
    $maybe_add($seller_rows, __('Postal code', 'zerti'), (string) ($zerti_invoice_identity['po_box'] ?? ''), 'seller_po_box');

    $website = trim((string) ($zerti_invoice_identity['website'] ?? ''));
    if ($website !== '') {
        $seller_rows[] = ['label' => __('Website URL', 'zerti'), 'value' => $website, 'key' => 'seller_website'];
    }
    $email = trim((string) ($zerti_invoice_identity['email'] ?? ''));
    if ($email !== '') {
        $seller_rows[] = ['label' => __('Email', 'zerti'), 'value' => $email, 'key' => 'email'];
    }
    $maybe_add($seller_rows, __('Phone number', 'zerti'), (string) ($zerti_invoice_identity['phone'] ?? ''), 'seller_phone');

    return $seller_rows;
}

/**
 * Render one titled party block for PDF output.
 */
function zerti_shop_order_pdf_postal_party_inline_style(string $party_modifier): string
{
    if (!function_exists('zerti_postal_pdf_settings')) {
        return '';
    }

    $postal = zerti_postal_pdf_settings();
    if ($party_modifier === 'zpdf-party--sender') {
        $size = $postal['seller_font_size'];
        $weight = $postal['seller_font_weight'];
    } elseif ($party_modifier === 'zpdf-party--recipient') {
        $size = $postal['customer_font_size'];
        $weight = $postal['customer_font_weight'];
    } else {
        return '';
    }

    $weight_css = function_exists('zerti_postal_pdf_font_weight_css_value')
        ? zerti_postal_pdf_font_weight_css_value((string) $weight)
        : $weight;

    return ' style="font-size:' . esc_attr($size) . 'pt;font-weight:' . esc_attr($weight_css) . ';"';
}

function zerti_shop_order_pdf_render_party_section(string $title, array $rows, string $party_modifier = ''): string
{
    $party_class = 'zpdf-party';
    if (in_array($party_modifier, ['zpdf-party--sender', 'zpdf-party--recipient'], true)) {
        $party_class .= ' ' . $party_modifier;
    }

    $body_style = zerti_shop_order_pdf_postal_party_inline_style($party_modifier);

    return '<div class="' . esc_attr($party_class) . '">'
        . '<div class="zpdf-block-title">' . esc_html($title) . '</div>'
        . '<div class="zpdf-party-body"' . $body_style . '>' . zerti_shop_order_pdf_render_party_fields($rows, $party_modifier) . '</div>'
        . '</div>';
}

/**
 * Whether a customer field row is an email address (hidden on postal label PDFs).
 *
 * @param array{label?:string,value?:string,key?:string} $row
 */
function zerti_shop_order_pdf_postal_customer_row_is_email(array $row): bool
{
    $key = isset($row['key']) ? sanitize_key((string) $row['key']) : '';

    return $key === 'email'
        || $key === 'billing_email'
        || $key === 'shipping_email'
        || strpos($key, 'email') !== false;
}

/**
 * Customer rows for postal label PDF (no email; optional location QR when geolocation exists).
 *
 * @return list<array{label:string,value:string,key:string,wide?:bool,html?:string}>
 */
function zerti_shop_order_pdf_postal_customer_rows(WC_Order $order): array
{
    if (!function_exists('zerti_wc_order_invoice_display_customer_fields')) {
        return [];
    }

    $rows = array_values(array_filter(
        zerti_wc_order_invoice_display_customer_fields($order),
        static function (array $row): bool {
            return !zerti_shop_order_pdf_postal_customer_row_is_email($row);
        }
    ));

    return zerti_shop_order_pdf_sort_postal_customer_rows($rows);
}

/**
 * Optional QR block for customer geolocation on postal label PDFs (no visible map URL).
 */
function zerti_shop_order_pdf_postal_maps_block_html(WC_Order $order): string
{
    if (!function_exists('zerti_postal_pdf_settings') || !zerti_postal_pdf_settings()['show_google_maps']) {
        return '';
    }

    if (!function_exists('zerti_wc_order_collect_display_customer_fields')
        || !function_exists('zerti_wc_order_customer_field_is_location')
        || !function_exists('zerti_wc_order_parse_lat_lng_coordinates')
        || !function_exists('zerti_wc_order_google_maps_url')
    ) {
        return '';
    }

    $maps_url = '';
    foreach (zerti_wc_order_collect_display_customer_fields($order) as $row) {
        if (!zerti_wc_order_customer_field_is_location($row)) {
            continue;
        }
        $coords = zerti_wc_order_parse_lat_lng_coordinates((string) ($row['value'] ?? ''));
        if ($coords === null) {
            continue;
        }
        $maps_url = zerti_wc_order_google_maps_url($coords['lat'], $coords['lng']);
        break;
    }

    if ($maps_url === '') {
        return '';
    }

    $qr_uri = zerti_shop_order_pdf_qrcode_png_data_uri($maps_url, 72);
    if ($qr_uri === '') {
        return '';
    }

    return '<div class="zpdf-postal-maps">'
        . '<p class="zpdf-postal-maps-title">' . esc_html(__('Customer location', 'zerti')) . '</p>'
        . '<img class="zpdf-postal-maps-qr" src="' . esc_attr($qr_uri) . '" alt="" width="72" height="72" />'
        . '</div>';
}

/**
 * Postal label PDF: seller and buyer information only.
 */
function zerti_shop_order_pdf_postal_label_html(WC_Order $order): string
{
    $postal_seller_hidden_keys = [
        'seller_registration_number',
        'seller_economic_code',
        'seller_national_id',
        'seller_custom_field',
    ];
    $seller_rows = array_values(array_filter(
        zerti_shop_order_pdf_collect_seller_rows($order),
        static function (array $row) use ($postal_seller_hidden_keys): bool {
            $key = isset($row['key']) ? (string) $row['key'] : '';

            return !in_array($key, $postal_seller_hidden_keys, true);
        }
    ));

    $customer_rows = zerti_shop_order_pdf_postal_customer_rows($order);

    $html = '<div class="zpdf-page zpdf-page--postal-label">'
        . zerti_shop_order_pdf_render_party_section(__('Sender:', 'zerti'), $seller_rows, 'zpdf-party--sender')
        . zerti_shop_order_pdf_render_party_section(__('Recipient:', 'zerti'), $customer_rows, 'zpdf-party--recipient')
        . zerti_shop_order_pdf_postal_maps_block_html($order)
        . '</div>';

    /**
     * Filters postal-label PDF inner HTML.
     *
     * @param string   $html  Postal label HTML.
     * @param WC_Order $order Order object.
     */
    return (string) apply_filters('zerti_shop_order_pdf_postal_label_html', $html, $order);
}

/**
 * Warehouse sheet PDF body (defaults to invoice layout; customize later).
 */
function zerti_shop_order_pdf_warehouse_sheet_html(WC_Order $order): string
{
    $html = zerti_shop_order_pdf_formal_invoice_html(
        $order,
        __('Warehouse sheet', 'zerti'),
        __('Download permission', 'zerti'),
        false,
        true
    );

    /**
     * Filters warehouse-sheet PDF inner HTML.
     *
     * @param string   $html  Default HTML (invoice layout).
     * @param WC_Order $order Order object.
     */
    return (string) apply_filters('zerti_shop_order_pdf_warehouse_sheet_html', $html, $order);
}

/**
 * Write combined PDF stylesheet to a temp file (for Dompdf file:// loading).
 */
function zerti_shop_order_pdf_write_stylesheet_file(string $directory): string
{
    $css = zerti_shop_order_pdf_css();
    $path = trailingslashit($directory) . 'zerti-pdf-styles-' . md5($css) . '.css';
    if (!is_readable($path)) {
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- PDF engine temp stylesheet.
        file_put_contents($path, $css);
    }

    return $path;
}

/**
 * Render shop order PDF content as a standalone HTML document (body only; no embedded CSS).
 *
 * Styles are loaded via mPDF HEADER_CSS mode or Dompdf Stylesheet::load_css_file().
 * This markup is consumed only by the PDF engine, not output in wp-admin.
 */
function zerti_shop_order_pdf_html_document(WC_Order $order, string $doc_type = 'invoice'): string
{
    $dir = zerti_shop_order_pdf_direction();
    $lang = zerti_shop_order_pdf_html_lang();

    return '<!DOCTYPE html><html dir="' . esc_attr($dir) . '" lang="' . esc_attr($lang) . '"><head><meta charset="utf-8"></head><body>'
        . zerti_shop_order_pdf_body_html($order, $doc_type)
        . '</body></html>';
}

/**
 * Write PDF HTML/CSS into an mPDF instance (no inline &lt;style&gt; in PHP output).
 */
function zerti_shop_order_pdf_write_mpdf(\Mpdf\Mpdf $mpdf, WC_Order $order, string $doc_type = 'invoice'): void
{
    $mpdf->WriteHTML(zerti_shop_order_pdf_css(), \Mpdf\HTMLParserMode::HEADER_CSS);
    $dir = zerti_shop_order_pdf_direction();
    $lang = zerti_shop_order_pdf_html_lang();
    $body = '<html dir="' . esc_attr($dir) . '" lang="' . esc_attr($lang) . '"><body>'
        . zerti_shop_order_pdf_body_html($order, $doc_type)
        . '</body></html>';
    $mpdf->WriteHTML($body, \Mpdf\HTMLParserMode::HTML_BODY);
}

/**
 * Render a Code 128 barcode as a PNG data URI (GD + mPDF barcode tables).
 */
function zerti_shop_order_pdf_barcode_png_data_uri(string $code, string $type = 'C128B', float $module_scale = 1.4, int $bar_height_px = 28): string
{
    static $cache = [];

    $cache_key = $type . '|' . $code . '|' . $module_scale . '|' . $bar_height_px;
    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }

    if ($code === '' || !extension_loaded('gd') || !function_exists('imagecreatetruecolor')) {
        $cache[$cache_key] = '';

        return '';
    }

    if (!zerti_shop_order_pdf_load_libraries() || !class_exists(\Mpdf\Barcode::class)) {
        $cache[$cache_key] = '';

        return '';
    }

    $barcode = new \Mpdf\Barcode();
    $arrcode = $barcode->getBarcodeArray($code, $type);
    if (!is_array($arrcode) || empty($arrcode['bcode']) || !isset($arrcode['maxw'], $arrcode['nom-X'])) {
        $cache[$cache_key] = '';

        return '';
    }

    $nom_x = (float) $arrcode['nom-X'];
    $xres = $nom_x * $module_scale;
    $lightm_l = (float) ($arrcode['lightmL'] ?? 10) * $xres;
    $lightm_r = (float) ($arrcode['lightmR'] ?? 10) * $xres;
    $bar_width = (float) $arrcode['maxw'] * $xres;
    $img_w = (int) ceil($lightm_l + $bar_width + $lightm_r);
    $pad_top = 4;
    $pad_bottom = 2;
    $img_h = $pad_top + $bar_height_px + $pad_bottom;

    if ($img_w < 1 || $img_h < 1) {
        $cache[$cache_key] = '';

        return '';
    }

    $im = imagecreatetruecolor($img_w, $img_h);
    if ($im === false) {
        $cache[$cache_key] = '';

        return '';
    }

    $white = imagecolorallocate($im, 255, 255, 255);
    $black = imagecolorallocate($im, 0, 0, 0);
    imagefilledrectangle($im, 0, 0, $img_w, $img_h, $white);

    $xpos = (int) round($lightm_l);
    $maxh = (float) ($arrcode['maxh'] ?? 1);
    if ($maxh <= 0) {
        $maxh = 1;
    }

    foreach ($arrcode['bcode'] as $v) {
        $bw = max(1, (int) round((float) ($v['w'] ?? 0) * $xres));
        if (!empty($v['t'])) {
            $bar_top = $pad_top + (int) round($bar_height_px * (float) ($v['p'] ?? 0) / $maxh);
            $bar_h = max(1, (int) round($bar_height_px * (float) ($v['h'] ?? 1) / $maxh));
            imagefilledrectangle($im, $xpos, $bar_top, $xpos + $bw - 1, $bar_top + $bar_h - 1, $black);
        }
        $xpos += $bw;
    }

    ob_start();
    imagepng($im);
    imagedestroy($im);
    $png = ob_get_clean();
    if ($png === false || $png === '') {
        $cache[$cache_key] = '';

        return '';
    }

    $cache[$cache_key] = 'data:image/png;base64,' . base64_encode($png);

    return $cache[$cache_key];
}

/**
 * Render a QR code as a PNG data URI (mpdf/qrcode).
 */
function zerti_shop_order_pdf_qrcode_png_data_uri(string $code, int $size_px = 72): string
{
    static $cache = [];

    $cache_key = $code . '|' . $size_px;
    if (isset($cache[$cache_key])) {
        return $cache[$cache_key];
    }

    if ($code === '' || !extension_loaded('gd')) {
        $cache[$cache_key] = '';

        return '';
    }

    if (!zerti_shop_order_pdf_load_libraries() || !class_exists(\Mpdf\QrCode\QrCode::class) || !class_exists(\Mpdf\QrCode\Output\Png::class)) {
        $cache[$cache_key] = '';

        return '';
    }

    try {
        $qr_code = new \Mpdf\QrCode\QrCode($code);
        $output = new \Mpdf\QrCode\Output\Png();
        $png = $output->output($qr_code, max(48, min(160, $size_px)), [255, 255, 255], [0, 0, 0]);
        if (!is_string($png) || $png === '') {
            $cache[$cache_key] = '';

            return '';
        }
        $cache[$cache_key] = 'data:image/png;base64,' . base64_encode($png);
    } catch (\Throwable $e) {
        if (defined('WP_DEBUG') && WP_DEBUG && function_exists('error_log')) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
            error_log('ZERTI warehouse QR: ' . $e->getMessage());
        }
        $cache[$cache_key] = '';
    }

    return $cache[$cache_key];
}

/**
 * Warehouse sheet code cell (barcode or QR) for a single value.
 */
function zerti_shop_order_pdf_warehouse_code_cell_html(string $code, string $code_type = 'barcode'): string
{
    $code = trim($code);
    if ($code === '') {
        return '—';
    }

    if ($code_type === 'qrcode') {
        $uri = zerti_shop_order_pdf_qrcode_png_data_uri($code);
        if ($uri !== '') {
            return '<div class="zpdf-barcode-wrap zpdf-qrcode-wrap"><img src="' . esc_attr($uri) . '" alt="" /></div>';
        }

        return '<div class="zpdf-barcode-wrap zpdf-qrcode-wrap"><barcode code="' . esc_attr($code) . '" type="QR" size="0.65" /></div>';
    }

    return zerti_shop_order_pdf_barcode_cell_html($code);
}

/**
 * Barcode table cell HTML (Code 128).
 */
function zerti_shop_order_pdf_barcode_cell_html(string $code): string
{
    $code = trim($code);
    if ($code === '') {
        return '—';
    }

    $uri = zerti_shop_order_pdf_barcode_png_data_uri($code);
    if ($uri !== '') {
        return '<div class="zpdf-barcode-wrap"><img src="' . esc_attr($uri) . '" alt="" /></div>';
    }

    return '<div class="zpdf-barcode-wrap"><barcode code="' . esc_attr($code) . '" type="C128B" height="0.35" size="0.65" text="1" /></div>';
}

/**
 * Barcode table cell for warehouse sheet (encodes WooCommerce product ID).
 */
function zerti_shop_order_pdf_product_id_barcode_cell_html(int $product_id, ?array $warehouse_code_settings = null): string
{
    if ($product_id <= 0) {
        return '—';
    }

    $settings = is_array($warehouse_code_settings)
        ? $warehouse_code_settings
        : (function_exists('zerti_warehouse_pdf_code_settings') ? zerti_warehouse_pdf_code_settings() : [
            'show_product_id_code' => true,
            'show_sku_code' => true,
            'code_type' => 'barcode',
        ]);
    if (empty($settings['show_product_id_code'])) {
        return '';
    }

    return zerti_shop_order_pdf_warehouse_code_cell_html((string) $product_id, (string) ($settings['code_type'] ?? 'barcode'));
}

/**
 * Barcode table cell for warehouse sheet (encodes product SKU).
 */
function zerti_shop_order_pdf_sku_barcode_cell_html(string $sku, ?array $warehouse_code_settings = null): string
{
    $settings = is_array($warehouse_code_settings)
        ? $warehouse_code_settings
        : (function_exists('zerti_warehouse_pdf_code_settings') ? zerti_warehouse_pdf_code_settings() : [
            'show_product_id_code' => true,
            'show_sku_code' => true,
            'code_type' => 'barcode',
        ]);
    if (empty($settings['show_sku_code'])) {
        return '';
    }

    return zerti_shop_order_pdf_warehouse_code_cell_html($sku, (string) ($settings['code_type'] ?? 'barcode'));
}

/**
 * Formal invoice HTML for PDF output (mPDF-friendly layout).
 *
 * @param string|null $title_override           Optional centered document title (e.g. warehouse sheet).
 * @param string|null $subtitle_override        Optional subtitle below the title.
 * @param bool        $include_party_sections   Include seller/buyer blocks.
 * @param bool        $warehouse_items_columns  Use product ID and SKU instead of unit price and discount.
 */
function zerti_shop_order_pdf_formal_invoice_html(WC_Order $order, ?string $title_override = null, ?string $subtitle_override = null, bool $include_party_sections = true, bool $warehouse_items_columns = false): string
{
    $oid = (int) $order->get_id();
    $status = method_exists($order, 'get_status') ? (string) $order->get_status() : '';
    $is_paid = in_array($status, ['completed', 'processing'], true);

    $invoice_title = $title_override !== null && $title_override !== ''
        ? $title_override
        : ($is_paid ? __('Sales invoice', 'zerti') : __('Proforma invoice', 'zerti'));
    $status_label = $is_paid ? __('Paid', 'zerti') : __('Pending payment', 'zerti');
    $gateway_title = method_exists($order, 'get_payment_method_title') ? trim((string) $order->get_payment_method_title()) : '';

    // Date (prefer Jalali helper used in UI).
    $created = method_exists($order, 'get_date_created') ? $order->get_date_created() : null;
    $ts = ($created && is_a($created, 'WC_DateTime')) ? (int) $created->getTimestamp() : 0;
    $date_label = $ts > 0 && function_exists('zerti_format_datetime')
        ? zerti_format_datetime($ts)
        : ($created ? (string) $created->date_i18n('Y/m/d H:i') : '—');
    $date_label = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($date_label) : $date_label;

    // Invoice identity/settings (same source as invoice view).
    $shop_name = function_exists('zerti_site_display_name') ? (string) zerti_site_display_name() : '';
    $brand_name = $shop_name !== '' ? $shop_name : (function_exists('get_bloginfo') ? (string) get_bloginfo('name') : '');
    $brand_name = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($brand_name) : $brand_name;

    $logo_url = function_exists('zerti_site_logo_url') ? (string) zerti_site_logo_url() : '';
    $invoice_custom_text = '';
    $show_invoice_custom_text = false;

    $zerti_invoice_settings = function_exists('zerti_get_plugin_settings') ? (array) zerti_get_plugin_settings() : [];
    $zerti_invoice_identity = isset($zerti_invoice_settings['identity']) && is_array($zerti_invoice_settings['identity'])
        ? $zerti_invoice_settings['identity']
        : [];
    $zerti_seller_name = '';
    if ($zerti_invoice_identity !== []) {
        $zerti_invoice_logo_id = absint($zerti_invoice_identity['logo_id'] ?? 0);
        if ($zerti_invoice_logo_id > 0 && function_exists('wp_get_attachment_image_url')) {
            $zerti_invoice_logo_url = wp_get_attachment_image_url($zerti_invoice_logo_id, 'medium');
            if (is_string($zerti_invoice_logo_url) && $zerti_invoice_logo_url !== '') {
                $logo_url = $zerti_invoice_logo_url;
            }
        }
        $zerti_seller_name = trim((string) ($zerti_invoice_identity['name'] ?? ''));
        if ($zerti_seller_name !== '') {
            $brand_name = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($zerti_seller_name) : $zerti_seller_name;
        }
    }

    if ($zerti_invoice_settings !== []) {
        if ($status === 'pending') {
            $show_invoice_custom_text = !empty($zerti_invoice_settings['general']['show_proforma_invoice_custom_text']);
            $invoice_custom_text = trim((string) ($zerti_invoice_identity['proforma_invoice_custom_text'] ?? ''));
        } elseif ($status === 'completed') {
            $show_invoice_custom_text = !empty($zerti_invoice_settings['general']['show_invoice_custom_text']);
            $invoice_custom_text = trim((string) ($zerti_invoice_identity['invoice_custom_text'] ?? ''));
        }
    }

    $seller_rows = [];
    $customer_rows = [];
    if ($include_party_sections) {
        $seller_rows = zerti_shop_order_pdf_collect_seller_rows($order);
        $customer_rows = function_exists('zerti_wc_order_invoice_display_customer_fields')
            ? zerti_wc_order_invoice_display_customer_fields($order)
            : [];
    }

    // Items
    $rows_html = '';
    $idx = 0;
    $warehouse_code_settings = null;
    $show_warehouse_product_id = false;
    $show_warehouse_sku = false;
    $show_warehouse_product_id_barcode = false;
    $show_warehouse_sku_barcode = false;
    $warehouse_code_type = 'barcode';
    if ($warehouse_items_columns && function_exists('zerti_warehouse_pdf_code_settings')) {
        $warehouse_code_settings = zerti_warehouse_pdf_code_settings();
        $show_warehouse_product_id = !empty($warehouse_code_settings['show_product_id']);
        $show_warehouse_sku = !empty($warehouse_code_settings['show_sku']);
        $show_warehouse_product_id_barcode = !empty($warehouse_code_settings['show_product_id_code']);
        $show_warehouse_sku_barcode = !empty($warehouse_code_settings['show_sku_code']);
        $warehouse_code_type = (string) ($warehouse_code_settings['code_type'] ?? 'barcode');
    }
    $items_table_colspan = $warehouse_items_columns
        ? 3
            + ($show_warehouse_product_id ? 1 : 0)
            + ($show_warehouse_product_id_barcode ? 1 : 0)
            + ($show_warehouse_sku ? 1 : 0)
            + ($show_warehouse_sku_barcode ? 1 : 0)
        : 7;
    foreach ($order->get_items('line_item') as $item) {
        $idx++;
        $line = function_exists('zerti_wc_order_parse_line_item_row')
            ? zerti_wc_order_parse_line_item_row($item, $idx)
            : null;
        if (!is_array($line)) {
            continue;
        }

        $name = (string) ($line['name'] ?? '');
        $qty = (string) ($line['qty'] ?? '');
        $unit = (float) ($line['unit_price'] ?? 0.0);
        $tax = (float) ($line['tax'] ?? 0.0);
        $disc = (float) ($line['discount'] ?? 0.0);
        $total = (float) ($line['line_total'] ?? 0.0);

        $name = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($name) : $name;
        $qty = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($qty) : $qty;
        $row_num = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits((string) $idx) : (string) $idx;

        if ($warehouse_items_columns) {
            $product_id = (int) ($line['product_id'] ?? 0);
            $sku = (string) ($line['sku'] ?? '');
            $product_id_cell = $product_id > 0
                ? (function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits((string) $product_id) : (string) $product_id)
                : '—';
            $sku_cell = $sku !== ''
                ? (function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($sku) : $sku)
                : '—';
            $col4_cell = esc_html($product_id_cell);
            $col5_cell = esc_html($sku_cell);
        } else {
            $col4_cell = function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($unit) : esc_html((string) $unit);
            $col5_cell = function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($disc) : esc_html((string) $disc);
            $col6_cell = function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($tax) : esc_html((string) $tax);
            $col7_cell = function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($total) : esc_html((string) $total);
        }

        $barcode_td_class = $warehouse_items_columns ? 't-center zpdf-barcode-cell' : 't-center';
        $rows_html .= '<tr>'
            . '<td class="t-center" align="center">' . esc_html($row_num) . '</td>'
            . '<td class="t-center" align="center">' . esc_html($name) . '</td>'
            . '<td class="t-center" align="center">' . esc_html($qty) . '</td>';
        if (!$warehouse_items_columns) {
            $rows_html .= '<td class="t-center" align="center">' . $col4_cell . '</td>'
                . '<td class="t-center" align="center">' . $col5_cell . '</td>'
                . '<td class="t-center" align="center">' . $col6_cell . '</td>'
                . '<td class="t-center" align="center">' . $col7_cell . '</td>';
        } else {
            if ($show_warehouse_product_id) {
                $rows_html .= '<td class="t-center" align="center">' . $col4_cell . '</td>';
            }
            if ($show_warehouse_product_id_barcode) {
                $rows_html .= '<td class="' . esc_attr($barcode_td_class) . '" align="center">'
                    . zerti_shop_order_pdf_product_id_barcode_cell_html($product_id, $warehouse_code_settings)
                    . '</td>';
            }
            if ($show_warehouse_sku) {
                $rows_html .= '<td class="t-center" align="center">' . $col5_cell . '</td>';
            }
            if ($show_warehouse_sku_barcode) {
                $rows_html .= '<td class="' . esc_attr($barcode_td_class) . '" align="center">'
                    . zerti_shop_order_pdf_sku_barcode_cell_html($sku, $warehouse_code_settings)
                    . '</td>';
            }
        }
        $rows_html .= '</tr>';
    }
    if ($rows_html === '') {
        $rows_html = '<tr><td colspan="' . (int) $items_table_colspan . '" class="t-center" style="color:#6b7280;">—</td></tr>';
    }

    $subtotal = method_exists($order, 'get_subtotal') ? (float) $order->get_subtotal() : 0.0;
    $discount_total = method_exists($order, 'get_discount_total') ? (float) $order->get_discount_total() : 0.0;
    $shipping_total = (float) (method_exists($order, 'get_shipping_total') ? $order->get_shipping_total() : 0.0)
        + (float) (method_exists($order, 'get_shipping_tax') ? $order->get_shipping_tax() : 0.0);
    $tax_total = method_exists($order, 'get_total_tax') ? (float) $order->get_total_tax() : 0.0;
    $grand = method_exists($order, 'get_total') ? (float) $order->get_total() : 0.0;

    $custom_text = $show_invoice_custom_text ? trim($invoice_custom_text) : '';
    $custom_text = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($custom_text) : $custom_text;

    $status_badge_class = $is_paid ? 'zpdf-badge' : 'zpdf-badge zpdf-badge--unpaid';

    $oid_display = function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits((string) $oid) : (string) $oid;
    $gateway_display = $gateway_title !== ''
        ? (function_exists('zerti_to_persian_digits') ? zerti_to_persian_digits($gateway_title) : $gateway_title)
        : '—';

    $pdf_dir = zerti_shop_order_pdf_direction();
    $pdf_layout_dir = zerti_shop_order_pdf_layout_flip_direction();
    $pdf_align_start = zerti_shop_order_pdf_is_rtl() ? 'right' : 'left';
    $pdf_meta_cell_align = zerti_shop_order_pdf_is_rtl() ? 'left' : 'right';

    $pdf_meta_row = static function (string $label, string $value_html) use ($pdf_dir, $pdf_align_start): string {
        return '<tr><td class="zpdf-meta-row" dir="' . esc_attr($pdf_dir) . '" align="' . esc_attr($pdf_align_start) . '">'
            . '<span class="zpdf-meta-label">' . esc_html($label) . ':</span> '
            . '<span class="zpdf-meta-value">' . $value_html . '</span>'
            . '</td></tr>';
    };

    $html = '<div class="zpdf-page">'
        . '<div class="zpdf-header">'
        . '<table class="zpdf-header-layout" dir="' . esc_attr($pdf_layout_dir) . '" role="presentation"><tr valign="middle">'
        . '<td class="zpdf-header-meta-cell" width="200" align="' . esc_attr($pdf_meta_cell_align) . '" valign="middle">'
        . '<div class="zpdf-meta" dir="' . esc_attr($pdf_dir) . '"><table role="presentation" dir="' . esc_attr($pdf_dir) . '">'
        . $pdf_meta_row(__('Order number', 'zerti'), esc_html($oid_display))
        . $pdf_meta_row(__('Issue date', 'zerti'), esc_html($date_label !== '' ? $date_label : '—'))
        . $pdf_meta_row(
            __('Status', 'zerti'),
            '<span class="' . esc_attr($status_badge_class) . '">' . esc_html($status_label) . '</span>'
        );
    if (!$warehouse_items_columns) {
        $html .= $pdf_meta_row(__('Payment method', 'zerti'), esc_html($gateway_display));
    }
    $html .= '</table></div>'
        . '</td>'
        . '<td class="zpdf-header-center-cell" valign="middle" align="center">'
        . '<div class="zpdf-title-stack" dir="' . esc_attr($pdf_dir) . '">';
    if ($brand_name !== '') {
        $html .= '<p class="zpdf-brand-name">' . esc_html($brand_name) . '</p>';
    }
    $subtitle = $subtitle_override !== null && $subtitle_override !== ''
        ? $subtitle_override
        : __('Printable version', 'zerti');
    $html .= '<div class="zpdf-title">' . esc_html($invoice_title) . '</div>'
        . '<div class="zpdf-title-sub">' . esc_html($subtitle) . '</div>'
        . '</div>'
        . '</td>'
        . '<td class="zpdf-header-logo-cell" width="200" align="' . esc_attr($pdf_align_start) . '" valign="middle">';

    if ($logo_url !== '') {
        $logo_attachment_id = zerti_shop_order_pdf_resolve_logo_attachment_id($zerti_invoice_identity);
        $html .= zerti_shop_order_pdf_logo_img_html($logo_url, $logo_attachment_id);
    }
    $html .= '</td>'
        . '</tr></table>'
        . '</div>'
        . '<div class="zpdf-sep" role="presentation"></div>';
    if ($include_party_sections) {
        $html .= zerti_shop_order_pdf_render_party_section(__('Seller information', 'zerti'), $seller_rows)
            . zerti_shop_order_pdf_render_party_section(__('Buyer information', 'zerti'), $customer_rows);
    }
    $html .= '<div class="zpdf-party">'
        . '<div class="zpdf-block-title">' . esc_html__('Items', 'zerti') . '</div>'
        . '<div class="zpdf-items-body">'
        . '<table class="zpdf-items" role="table" aria-label="' . esc_attr__('Invoice items', 'zerti') . '">'
        . '<thead><tr>'
        . '<th class="t-center" align="center" style="width:6%;">' . esc_html__('Row', 'zerti') . '</th>'
        . '<th class="t-center" align="center" style="width:34%;">' . esc_html__('Item / service description', 'zerti') . '</th>'
        . '<th class="t-center" align="center" style="width:8%;">' . esc_html__('Quantity', 'zerti') . '</th>';

    if (!$warehouse_items_columns) {
        $html .= '<th class="t-center" align="center" style="width:14%;">' . esc_html__('Unit price', 'zerti') . '</th>'
            . '<th class="t-center" align="center" style="width:12%;">' . esc_html__('Discount', 'zerti') . '</th>'
            . '<th class="t-center" align="center" style="width:12%;">' . esc_html__('Tax', 'zerti') . '</th>'
            . '<th class="t-center" align="center" style="width:14%;">' . esc_html__('Amount', 'zerti') . '</th>';
    } else {
        if ($show_warehouse_product_id) {
            $html .= '<th class="t-center" align="center" style="width:10%;">' . esc_html__('Product code', 'zerti') . '</th>';
        }
        if ($show_warehouse_product_id_barcode) {
            $html .= '<th class="t-center" align="center" style="width:12%;">' . esc_html(
                $warehouse_code_type === 'qrcode' ? __('Product QR', 'zerti') : __('Product barcode', 'zerti')
            ) . '</th>';
        }
        if ($show_warehouse_sku) {
            $html .= '<th class="t-center" align="center" style="width:10%;">' . esc_html__('Warehouse code', 'zerti') . '</th>';
        }
        if ($show_warehouse_sku_barcode) {
            $html .= '<th class="t-center" align="center" style="width:12%;">' . esc_html(
                $warehouse_code_type === 'qrcode' ? __('Warehouse QR', 'zerti') : __('Warehouse barcode', 'zerti')
            ) . '</th>';
        }
    }

    $html .= '</tr></thead><tbody>'
        . $rows_html
        . '</tbody></table>';

    $pdf_totals_rows = '';
    if (!$warehouse_items_columns) {
        $pdf_totals_rows .= '<tr><td class="k">' . esc_html__('Subtotal', 'zerti') . '</td><td class="v">' . (function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($subtotal) : esc_html((string) $subtotal)) . '</td></tr>';
    }
    if ($discount_total > 0) {
        $pdf_totals_rows .= '<tr><td class="k">' . esc_html__('Discount', 'zerti') . '</td><td class="v zpdf-discount">−' . (function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($discount_total) : esc_html((string) $discount_total)) . '</td></tr>';
    }
    if ($shipping_total > 0) {
        $pdf_totals_rows .= '<tr><td class="k">' . esc_html__('Shipping', 'zerti') . '</td><td class="v">' . (function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($shipping_total) : esc_html((string) $shipping_total)) . '</td></tr>';
    }
    if ($tax_total > 0) {
        $pdf_totals_rows .= '<tr><td class="k">' . esc_html__('Tax', 'zerti') . '</td><td class="v">' . (function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($tax_total) : esc_html((string) $tax_total)) . '</td></tr>';
    }
    if (!$warehouse_items_columns) {
        $pdf_totals_rows .= '<tr class="zpdf-grand"><td class="k">' . esc_html__('Amount due', 'zerti') . '</td><td class="v">' . (function_exists('zerti_wc_price_html_persian') ? zerti_wc_price_html_persian($grand) : esc_html((string) $grand)) . '</td></tr>';
    }

    if ($pdf_totals_rows !== '') {
        $totals_cell_align = zerti_shop_order_pdf_is_rtl() ? 'left' : 'right';
        $html .= '<table class="zpdf-totals-layout" dir="' . esc_attr($pdf_layout_dir) . '" width="100%" role="presentation"><tr>'
            . '<td class="zpdf-totals-cell" align="' . esc_attr($totals_cell_align) . '" valign="top">'
            . '<div class="zpdf-totals-card">'
            . '<div class="zpdf-totals" dir="' . esc_attr($pdf_dir) . '"><table role="presentation" dir="' . esc_attr($pdf_dir) . '">'
            . $pdf_totals_rows
            . '</table></div>'
            . '</div>'
            . '</td>'
            . '<td class="zpdf-totals-spacer">&nbsp;</td>'
            . '</tr></table>';
    }

    $html .= '</div>'
        . '</div>';

    if ($custom_text !== '') {
        $html .= '<div class="zpdf-footer-note">' . esc_html($custom_text) . '</div>';
    }

    $html .= '</div>';

    return $html;
}

/**
 * Stream the invoice with mPDF, which supports Persian shaping and RTL layout.
 */
function zerti_shop_order_pdf_stream_mpdf(WC_Order $order, string $filename, string $tmp_dir, string $doc_type = 'invoice'): void
{
    zerti_shop_order_pdf_load_libraries();

    $default_config = (new \Mpdf\Config\ConfigVariables())->getDefaults();
    $default_font_config = (new \Mpdf\Config\FontVariables())->getDefaults();
    $font_dirs = $default_config['fontDir'];
    $font_data = $default_font_config['fontdata'];
    $zerti_font_dir = plugin_dir_path(ZERTI_PLUGIN_FILE) . 'assets/fonts';
    $zerti_font_regular = trailingslashit($zerti_font_dir) . 'Vazirmatn-Regular.ttf';
    $zerti_font_bold = trailingslashit($zerti_font_dir) . 'Vazirmatn-Bold.ttf';
    if (!is_readable($zerti_font_regular) || !is_readable($zerti_font_bold)) {
        wp_die(
            esc_html__('PDF font (Vazirmatn) is not available on the server.', 'zerti'),
            '',
            ['response' => 500]
        );
    }
    $font_dirs[] = $zerti_font_dir;
    // Use a unique font key to avoid stale cached metrics on some hosts.
    $font_key = 'zerti_vazirmatn';
    $font_data[$font_key] = [
        'R' => 'Vazirmatn-Regular.ttf',
        'B' => 'Vazirmatn-Bold.ttf',
        'I' => 'Vazirmatn-Regular.ttf',
        'BI' => 'Vazirmatn-Bold.ttf',
        'useOTL' => 0xFF,
        'useKashida' => 75,
    ];

    $hash_r = sha1_file($zerti_font_regular) ?: '';
    $hash_b = sha1_file($zerti_font_bold) ?: '';
    $hash_tag = substr($hash_r, 0, 10) . '-' . substr($hash_b, 0, 10);
    $tmp_dir_font = trailingslashit($tmp_dir) . 'vazirmatn-' . preg_replace('/[^a-z0-9\-]/i', '', $hash_tag);
    if (!is_dir($tmp_dir_font)) {
        wp_mkdir_p($tmp_dir_font);
    }

    $doc_type = zerti_shop_order_pdf_normalize_doc_type($doc_type);
    $paper_size = function_exists('zerti_get_order_pdf_paper_size')
        ? zerti_get_order_pdf_paper_size($doc_type)
        : 'A4';

    $mpdf = new \Mpdf\Mpdf([
        'mode' => 'utf-8',
        'format' => $paper_size,
        'fontDir' => $font_dirs,
        'fontdata' => $font_data,
        'default_font' => $font_key,
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable -- mPDF tempDir must be writable.
        'tempDir' => is_dir($tmp_dir_font) && is_writable($tmp_dir_font) ? $tmp_dir_font : $tmp_dir,
        'margin_left' => 10,
        'margin_right' => 10,
        'margin_top' => 10,
        'margin_bottom' => 10,
        'autoScriptToLang' => true,
        'autoLangToFont' => true,
        'useSubstitutions' => true,
        'percentSubset' => 0,
        'simpleTables' => true,
    ]);
    $mpdf->SetDirectionality(zerti_shop_order_pdf_direction());
    if (method_exists($mpdf, 'SetDefaultFont')) {
        $mpdf->SetDefaultFont($font_key);
    }
    $mpdf->SetAuthor('ZERTI');
    $mpdf->SetCreator('ZERTI');
    $mpdf->SetTitle(sprintf('ZERTI %s %d', $doc_type, (int) $order->get_id()));
    $mpdf->SetSubject(
        'doc='
        . $doc_type
        . ';engine=mpdf;font='
        . $font_key
        . ';files=assets/fonts/Vazirmatn-*.ttf;sha1='
        . substr($hash_r, 0, 12)
        . ','
        . substr($hash_b, 0, 12)
    );
    zerti_shop_order_pdf_write_mpdf($mpdf, $order, $doc_type);
    $mpdf->Output($filename, 'D');
    exit;
}

/**
 * Stream the invoice with Dompdf as a last-resort fallback.
 */
function zerti_shop_order_pdf_stream_dompdf(WC_Order $order, string $filename, string $tmp_dir, string $doc_type = 'invoice'): void
{
    zerti_shop_order_pdf_load_libraries();

    $options = new \Dompdf\Options();
    $options->set('defaultFont', 'DejaVu Sans');
    $options->set('isRemoteEnabled', true);
    $options->set('isHtml5ParserEnabled', true);
    $options->set('tempDir', $tmp_dir);
    $options->set('fontDir', $tmp_dir);
    $options->set('fontCache', $tmp_dir);
    $options->setChroot([wp_normalize_path($tmp_dir)]);

    $dompdf = new \Dompdf\Dompdf($options);
    $doc_type = zerti_shop_order_pdf_normalize_doc_type($doc_type);
    $paper_size = function_exists('zerti_pdf_paper_size_for_dompdf')
        ? zerti_pdf_paper_size_for_dompdf(
            function_exists('zerti_get_order_pdf_paper_size')
                ? zerti_get_order_pdf_paper_size($doc_type)
                : 'A4'
        )
        : 'a4';
    $css_path = zerti_shop_order_pdf_write_stylesheet_file($tmp_dir);
    $dompdf->loadHtml(zerti_shop_order_pdf_html_document($order, $doc_type), 'UTF-8');
    $dompdf->getCss()->load_css_file($css_path);
    $dompdf->setPaper($paper_size, 'portrait');
    $dompdf->render();
    // Distinguish fallback engine in PDF metadata.
    if (method_exists($dompdf, 'addInfo')) {
        $dompdf->addInfo('Subject', 'doc=' . $doc_type . ';engine=dompdf;font=dejavu');
    }
    $dompdf->stream($filename, ['Attachment' => true]);
    exit;
}

/**
 * Send WooCommerce customer invoice email for an order.
 */
function zerti_ajax_shop_order_email_invoice(): void
{
    $order_id = zerti_shop_order_action_verify_request();

    if (!current_user_can('edit_shop_order', $order_id)) {
        wp_send_json_error(['message' => __('You do not have permission to send email.', 'zerti')], 403);
    }

    if (!function_exists('WC') || !WC()->mailer()) {
        wp_send_json_error(['message' => __('WooCommerce is not available.', 'zerti')], 500);
    }

    $order = wc_get_order($order_id);
    if (!is_a($order, 'WC_Order')) {
        wp_send_json_error(['message' => __('Order not found.', 'zerti')], 404);
    }

    $email_to = trim((string) $order->get_billing_email());
    if ($email_to === '') {
        wp_send_json_error(['message' => __('No customer email is recorded for this order.', 'zerti')], 400);
    }

    $sent = false;
    $mailer = WC()->mailer();
    $emails = $mailer->get_emails();

    if (is_array($emails)) {
        foreach ($emails as $email) {
            if (!is_object($email) || !isset($email->id) || $email->id !== 'customer_invoice') {
                continue;
            }
            if (method_exists($email, 'is_enabled') && !$email->is_enabled()) {
                break;
            }
            if (method_exists($email, 'trigger')) {
                $email->trigger($order_id, $order);
                $sent = true;
            }
            break;
        }
    }

    if (!$sent) {
        wp_send_json_error(['message' => __('Customer invoice email is not enabled in WooCommerce.', 'zerti')], 500);
    }

    wp_send_json_success([
        'message' => sprintf(
            /* translators: %s: customer email address */
            __('Invoice email sent to %s.', 'zerti'),
            $email_to
        ),
    ]);
}

/**
 * Dispatch shop order AJAX actions.
 */
function zerti_ajax_shop_order_action(): void
{
    if (!check_ajax_referer('zerti_shop_order', 'nonce', false)) {
        wp_send_json_error(['message' => __('Invalid request.', 'zerti')], 403);
    }

    if (!function_exists('zerti_user_can_access_admin') || !zerti_user_can_access_admin()) {
        wp_send_json_error(['message' => __('You do not have permission to perform this action.', 'zerti')], 403);
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified above.
    $op = isset($_POST['op']) ? sanitize_key(wp_unslash((string) $_POST['op'])) : '';

    switch ($op) {
        case 'update_status':
            zerti_ajax_shop_order_update_status();
            break;
        case 'print_link':
            zerti_ajax_shop_order_print_link();
            break;
        case 'email_invoice':
            zerti_ajax_shop_order_email_invoice();
            break;
        case 'add_note':
            zerti_ajax_shop_order_add_note();
            break;
        default:
            wp_send_json_error(['message' => __('Invalid action.', 'zerti')], 400);
    }
}

add_action('wp_ajax_zerti_shop_order_action', 'zerti_ajax_shop_order_action');
add_action('wp_ajax_zerti_shop_order_print', 'zerti_ajax_shop_order_print_output');
