<?php
if (!defined('ABSPATH'))
    exit;

/**
 * SVLD_Renderer — generates PDF label sheets using TCPDF.
 *
 * Uses TCPDF bundled in the main ScanVault plugin if available,
 * otherwise falls back to a bundled copy (if present).
 */
class SVLD_Renderer
{

    private array $layout;
    private int $label_width;
    private int $label_height;
    private ?WC_Order $order = null;
    private string $last_error = '';

    public function __construct($template_id = null)
    {
        if ($template_id) {
            $this->load_template($template_id);
        } else {
            $this->layout = get_option('svld_layout', svld_default_layout());
            $this->label_width = max(20, (int) get_option('svld_label_width', 100));
            $this->label_height = max(10, (int) get_option('svld_label_height', 50));
        }
    }

    /**
     * Set the order context for rendering labels with order data
     */
    public function set_order(WC_Order $order): void
    {
        $this->order = $order;
    }

    /**
     * Load a template by ID
     */
    private function load_template($template_id): void
    {
        $templates = get_option('svld_templates', []);

        if (isset($templates[$template_id])) {
            $template = $templates[$template_id];
            $this->layout = $template['layout'] ?? svld_default_layout();
            $this->label_width = max(20, (int) ($template['label_width'] ?? 100));
            $this->label_height = max(10, (int) ($template['label_height'] ?? 50));
        } else {
            // Fallback to default layout if template not found
            $this->layout = get_option('svld_layout', svld_default_layout());
            $this->label_width = max(20, (int) get_option('svld_label_width', 100));
            $this->label_height = max(10, (int) get_option('svld_label_height', 50));
        }
    }

    /**
     * Set a custom layout directly (for test print)
     */
    public function set_custom_layout(array $layout, int $width, int $height): void
    {
        $this->layout = $layout;
        $this->label_width = max(20, $width);
        $this->label_height = max(10, $height);
    }

    /**
     * Generate a PDF file for the given product map.
     *
     * @param array $product_map  [ product_id => quantity ]
     * @param WC_Order|null $order Optional order object for order data sources
     * @return string|false  Public URL of the generated PDF, or false on failure.
     */
    public function generate_label_pdf(array $product_map, ?WC_Order $order = null): string|false
    {
        if (!$this->load_tcpdf()) {
            $this->set_error('TCPDF not found. Cannot generate PDF.');
            return false;
        }

        // Set order context if provided
        if ($order) {
            $this->set_order($order);
        }

        $upload = wp_upload_dir();
        $output_dir = $upload['basedir'] . '/svld-labels/';
        $output_url = $upload['baseurl'] . '/svld-labels/';

        if (!file_exists($output_dir)) {
            wp_mkdir_p($output_dir);
        }

        $filename = 'svld-label-' . time() . '-' . wp_generate_password(6, false) . '.pdf';
        $output_path = $output_dir . $filename;

        // TCPDF page = landscape (width > height typical for labels)
        // TCPDF AddPage expects [width, height] where width is landscape width
        $orientation = ($this->label_width >= $this->label_height) ? 'L' : 'P';
        $page_size = [$this->label_height, $this->label_width]; // TCPDF uses [short, long] internally

        $pdf = new TCPDF($orientation, 'mm', [$this->label_width, $this->label_height], true, 'UTF-8', false);
        $pdf->SetCreator('ScanVault Label Designer');
        $pdf->SetTitle('Product Labels');
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetTopMargin(0);
        $pdf->SetAutoPageBreak(false, 0);
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdf->SetCellPadding(0);

        // If the label is order-level (contains order_items_table or billing/shipping address)
        // render exactly ONE page for the whole order, not one page per product.
        $first = true;
        if ($order && $this->is_order_level_layout()) {
            // Use the first product in the order for any product-specific placeholders
            $first_product = null;
            foreach ($order->get_items() as $item) {
                $first_product = wc_get_product($item->get_product_id());
                if ($first_product)
                    break;
            }
            // Fall back to first product in the supplied map if order has no items
            if (!$first_product) {
                foreach ($product_map as $product_id => $qty) {
                    $first_product = wc_get_product($product_id);
                    if ($first_product)
                        break;
                }
            }
            if ($first_product) {
                $pdf->AddPage();
                $this->render_label_on_pdf($pdf, $first_product);
                $first = false;
            }
        } else {
            foreach ($product_map as $product_id => $qty) {
                $product = wc_get_product($product_id);
                if (!$product)
                    continue;

                $qty = max(1, (int) $qty);
                for ($i = 0; $i < $qty; $i++) {
                    $pdf->AddPage();
                    $first = false;
                    $this->render_label_on_pdf($pdf, $product);
                }
            }
        }

        if ($first) {
            // Nothing was rendered
            return false;
        }

        $pdf->Output($output_path, 'F');

        return $output_url . $filename;
    }

    /**
     * Generate one label page per order (for bulk order-level templates).
     * Each page uses the correct order's customer/address data.
     *
     * @param WC_Order[] $orders
     * @return string|false  Public URL of the generated PDF, or false on failure.
     */
    public function generate_bulk_orders_pdf(array $orders): string|false
    {
        if (!$this->load_tcpdf()) {
            $this->set_error('TCPDF not found. Cannot generate PDF.');
            return false;
        }

        $upload = wp_upload_dir();
        $output_dir = $upload['basedir'] . '/svld-labels/';
        $output_url = $upload['baseurl'] . '/svld-labels/';

        if (!file_exists($output_dir)) {
            wp_mkdir_p($output_dir);
        }

        $filename = 'svld-label-' . time() . '-' . wp_generate_password(6, false) . '.pdf';
        $output_path = $output_dir . $filename;

        $orientation = ($this->label_width >= $this->label_height) ? 'L' : 'P';

        $pdf = new TCPDF($orientation, 'mm', [$this->label_width, $this->label_height], true, 'UTF-8', false);
        $pdf->SetCreator('ScanVault Label Designer');
        $pdf->SetTitle('Order Labels');
        $pdf->SetMargins(0, 0, 0);
        $pdf->SetTopMargin(0);
        $pdf->SetAutoPageBreak(false, 0);
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdf->SetCellPadding(0);

        $rendered = false;
        foreach ($orders as $order) {
            $this->order = $order;

            // Get the first product in the order for product-specific placeholders
            $first_product = null;
            foreach ($order->get_items() as $item) {
                $first_product = wc_get_product($item->get_product_id());
                if ($first_product)
                    break;
            }

            if (!$first_product)
                continue;

            $pdf->AddPage();
            $this->render_label_on_pdf($pdf, $first_product);
            $rendered = true;
        }

        if (!$rendered) {
            return false;
        }

        $pdf->Output($output_path, 'F');

        return $output_url . $filename;
    }

    /**
     * Render one label for $product onto the current TCPDF page.
     */
    private function render_label_on_pdf(TCPDF $pdf, WC_Product $product): void
    {
        $site_url = home_url();
        $host = wp_parse_url($site_url, PHP_URL_HOST);

        foreach ($this->layout as $el) {
            if (empty($el['visible'])) {
                continue;
            }

            $x = (float) ($el['x'] ?? 0);
            $y = (float) ($el['y'] ?? 0);
            $w = (float) ($el['width'] ?? 30);
            $h = (float) ($el['height'] ?? 10);
            $fs = max(6, min(48, (int) ($el['fontSize'] ?? 10)));
            $bold = !empty($el['bold']);
            $italic = !empty($el['italic']);
            $underline = !empty($el['underline']);
            $align = strtoupper(substr($el['align'] ?? 'L', 0, 1));

            // Build font style string for TCPDF (supports B, I, U, and combinations like BI, BU, IU, BIU)
            $font_style = '';
            if ($bold)
                $font_style .= 'B';
            if ($italic)
                $font_style .= 'I';
            if ($underline)
                $font_style .= 'U';

            switch ($el['type']) {

                case 'text':
                    $text = $this->get_text_value($product, $el, $site_url, $host);
                    $multi_line = !empty($el['multiLine']);

                    // Auto-scale font size if "Fit to Width" is enabled
                    if (!empty($el['fitWidth'])) {
                        if ($multi_line) {
                            // Multi-line mode: calculate font size to fit both width and height
                            $avg_char_width = $fs * 0.15;
                            $char_count = mb_strlen($text);
                            $estimated_text_width = $char_count * $avg_char_width;

                            // Estimate number of lines needed
                            $max_chars_per_line = max(1, floor($w / $avg_char_width));
                            $estimated_lines = max(1, ceil($char_count / $max_chars_per_line));
                            $line_height = $fs * 0.35; // Approximate line height in mm

                            // Scale font size if text exceeds element height
                            if ($estimated_lines * $line_height > $h) {
                                $scale = $h / ($estimated_lines * $line_height);
                                $min_fs = max(1, floor(min($w, $h) / 8));
                                $fs = max($min_fs, (int) ($fs * $scale));
                            }
                        } else {
                            // Single-line mode: only fit to width
                            $char_count = mb_strlen($text);
                            $avg_char_width = $fs * 0.15;
                            $estimated_text_width = $char_count * $avg_char_width;

                            if ($estimated_text_width > $w) {
                                $scale = $w / $estimated_text_width;
                                $min_fs = max(1, floor($w / 10));
                                $fs = max($min_fs, (int) ($fs * $scale));
                            }
                        }
                    }

                    // Use configured label font (supports Unicode scripts: Arabic, Cyrillic, CJK, etc.)
                    $pdf->SetFont(svld_get_label_font(), $font_style, $fs);
                    $pdf->SetTextColor(0, 0, 0);
                    $pdf->SetXY($x, $y);

                    // order_items_table and billing/shipping address need MultiCell
                    $data_src = $el['dataSource'] ?? '';
                    $force_multi = in_array($data_src, ['order_items_table', 'billing_address', 'shipping_address'], true);

                    // Special handling for order_invoice_full - render as proper TCPDF table
                    if ($data_src === 'order_invoice_full') {
                        // Build invoice data directly from element and order
                        $show_image = !empty($el['showItemImage']);
                        $show_qty = !isset($el['showItemQty']) || !empty($el['showItemQty']);
                        $show_price = !isset($el['showItemPrice']) || !empty($el['showItemPrice']);
                        $show_subtotal = !isset($el['showSubtotal']) || !empty($el['showSubtotal']);
                        $show_tax = !isset($el['showTax']) || !empty($el['showTax']);
                        $show_shipping = !isset($el['showShipping']) || !empty($el['showShipping']);
                        $show_discount = !empty($el['showDiscount']);
                        $show_total = !isset($el['showTotal']) || !empty($el['showTotal']);
                        $show_payment = !empty($el['showPayment']);
                        $show_shipping_via = !empty($el['showShippingVia']);

                        if ($this->order) {
                            $currency = $this->order->get_currency();
                            $invoice_data = [
                                'show_image' => $show_image,
                                'show_qty' => $show_qty,
                                'show_price' => $show_price,
                                'show_subtotal' => $show_subtotal,
                                'show_tax' => $show_tax,
                                'show_shipping' => $show_shipping,
                                'show_discount' => $show_discount,
                                'show_total' => $show_total,
                                'show_payment' => $show_payment,
                                'show_shipping_via' => $show_shipping_via,
                                'items' => [],
                                'totals' => [],
                            ];

                            // Build items data
                            foreach ($this->order->get_items() as $item) {
                                $item_data = [
                                    'name' => mb_strimwidth($item->get_name(), 0, 40, '..'),
                                    'qty' => $item->get_quantity(),
                                ];
                                if ($show_price) {
                                    $unit_price = $item->get_subtotal() / max(1, $item->get_quantity());
                                    $line_subtotal = $item->get_subtotal();
                                    $line_total = $item->get_total();

                                    $item_data['price'] = $this->format_price($unit_price, $currency);
                                    $item_data['total'] = $this->format_price($line_total, $currency);
                                    $item_data['unit_price'] = $item_data['price'];

                                    // Calculate line item discount if any
                                    $line_discount = $line_subtotal - $line_total;
                                    if ($line_discount > 0.01 && $show_discount) {
                                        $item_data['discount'] = $this->format_price($line_discount, $currency);
                                    }
                                }
                                if ($show_image) {
                                    $product_for_image = $item->get_product();
                                    if ($product_for_image && $product_for_image->get_image_id()) {
                                        $image_id = $product_for_image->get_image_id();
                                        $item_data['image_id'] = $image_id;
                                        $item_data['image_url'] = wp_get_attachment_image_url($image_id, 'thumbnail');
                                    }
                                }
                                $invoice_data['items'][] = $item_data;
                            }

                            // Build totals data
                            if ($show_subtotal) {
                                $invoice_data['totals'][] = ['label' => 'Subtotal', 'value' => $this->format_price($this->order->get_subtotal(), $currency)];
                            }
                            if ($show_tax) {
                                $invoice_data['totals'][] = ['label' => 'Tax', 'value' => $this->format_price($this->order->get_total_tax(), $currency)];
                            }
                            if ($show_shipping) {
                                $invoice_data['totals'][] = ['label' => 'Shipping', 'value' => $this->format_price($this->order->get_shipping_total(), $currency)];
                            }

                            // Calculate discount from the difference between expected total and actual total
                            // Expected = Subtotal + Tax + Shipping
                            // Actual = Order Total
                            // Discount = Expected - Actual (if positive)
                            if ($show_discount) {
                                $expected_total = $this->order->get_subtotal() + $this->order->get_total_tax() + $this->order->get_shipping_total();
                                $actual_total = $this->order->get_total();
                                $calculated_discount = $expected_total - $actual_total;

                                // Also check WooCommerce discount
                                $woo_discount = $this->order->get_total_discount();
                                $total_discount = max($calculated_discount, $woo_discount);

                                if ($total_discount > 0.01) {
                                    $invoice_data['totals'][] = ['label' => '', 'value' => '-' . $this->format_price($total_discount, $currency), 'is_discount' => true];
                                }
                            }

                            if ($show_total) {
                                $invoice_data['totals'][] = ['label' => 'TOTAL', 'value' => $this->format_price($this->order->get_total(), $currency), 'is_total' => true];
                            }
                            if ($show_payment) {
                                $invoice_data['payment_method'] = $this->order->get_payment_method_title();
                            }
                            if ($show_shipping_via) {
                                $invoice_data['shipping_method'] = $this->order->get_shipping_method();
                            }

                            $text_direction = sanitize_text_field($el['textDirection'] ?? 'ltr');
                            $fit_invoice = !empty($el['fitWidth']);
                            $this->render_invoice_table($pdf, $invoice_data, $x, $y, $w, $h, $fs, $align, $text_direction, $fit_invoice);
                            // Skip normal text rendering for invoice - already rendered as table
                            continue 2;
                        } else {
                            // Preview fallback - render a sample invoice table so the designer can see the layout
                            $sample_data = [
                                'show_image' => false,
                                'show_qty' => $show_qty,
                                'show_price' => $show_price,
                                'show_subtotal' => $show_subtotal,
                                'show_tax' => $show_tax,
                                'show_shipping' => false,
                                'show_discount' => false,
                                'show_total' => $show_total,
                                'show_payment' => false,
                                'show_shipping_via' => false,
                                'items' => [
                                    ['name' => 'Sample Product A', 'qty' => 2, 'price' => '25.00', 'total' => '50.00'],
                                    ['name' => 'Sample Product B', 'qty' => 1, 'price' => '15.00', 'total' => '15.00'],
                                ],
                                'totals' => [],
                            ];
                            if ($show_subtotal) {
                                $sample_data['totals'][] = ['label' => 'Subtotal', 'value' => '65.00'];
                            }
                            if ($show_tax) {
                                $sample_data['totals'][] = ['label' => 'Tax', 'value' => '6.50'];
                            }
                            if ($show_total) {
                                $sample_data['totals'][] = ['label' => 'TOTAL', 'value' => '71.50', 'is_total' => true];
                            }
                            $fit_invoice = !empty($el['fitWidth']);
                            $text_direction = sanitize_text_field($el['textDirection'] ?? 'ltr');
                            $this->render_invoice_table($pdf, $sample_data, $x, $y, $w, $h, $fs, $align, $text_direction, $fit_invoice);
                            // Skip normal text rendering for invoice preview
                            continue 2;
                        }
                    }
                    // Special handling for order_items_table with images
                    if ($data_src === 'order_items_table' && !empty($el['showItemImage']) && $this->order) {
                        $line_height = max(3.0, round($fs * 0.352778 * 1.4, 2));
                        $image_size = min($line_height * 2, 10); // Image size in mm
                        $image_offset = 2; // Offset from left edge

                        // Get the items from the order
                        $items = $this->order->get_items();
                        $current_y = $y;
                        $row_height = $line_height;

                        // Parse the text to get table structure
                        $lines = explode("\n", $text);
                        $item_index = 0;

                        foreach ($lines as $line) {
                            // Check if this is a data row (contains image placeholder)
                            if (strpos($line, '[IMG]') !== false && $item_index < count($items)) {
                                $item = $items[$item_index];
                                $product_for_image = $item->get_product();

                                if ($product_for_image && $product_for_image->get_image_id()) {
                                    $image_id = $product_for_image->get_image_id();
                                    $image_url = wp_get_attachment_image_url($image_id, 'thumbnail');

                                    if ($image_url) {
                                        $image_path = $this->url_to_path($image_url);
                                        if ($image_path && file_exists($image_path)) {
                                            // Render image at the beginning of the row
                                            $img_x = $x + $image_offset;
                                            $img_y = $current_y + ($row_height - $image_size) / 2;
                                            $pdf->Image($image_path, $img_x, $img_y, $image_size, $image_size, '', '', '', false, 150, '', false, false, 0, 'T');

                                            // Render text after the image (skip [IMG] placeholder)
                                            $text_without_img = str_replace('[IMG]', '', $line);
                                            $text_x = $x + $image_offset + $image_size + 2;
                                            $pdf->SetXY($text_x, $current_y);
                                            $pdf->Cell($w - $image_offset - $image_size - 2, $row_height, $text_without_img, 0, 0, 'L');
                                            $current_y += $row_height;
                                            $item_index++;
                                            continue;
                                        }
                                    }
                                }
                            }

                            // Regular text row or no image available
                            $pdf->SetXY($x, $current_y);
                            $pdf->Cell($w, $row_height, $line, 0, 0, 'L');
                            $current_y += $row_height;
                        }
                    } else {
                        // Use MultiCell with explicit absolute x/y (not cursor-position-dependent like Cell())
                        // so text always renders at the correct position regardless of prior barcode/QR state.
                        $line_height = max(3.0, round($fs * 0.352778 * 1.4, 2));
                        $pdf->MultiCell($w, $line_height, $text, 0, $align, false, 0, $x, $y, true, 0, false, true, $h, 'T', false);
                    }
                    break;

                case 'barcode':
                    $barcode_value = $this->get_text_value($product, $el, $site_url, $host);
                    if (!empty($barcode_value)) {
                        $show_text = !empty($el['showText']);
                        $bar_h = $show_text ? $h * 0.75 : $h;
                        $barcode_type = $el['barcodeType'] ?? 'C128';
                        $pdf->write1DBarcode(
                            $barcode_value,
                            $barcode_type,
                            $x,
                            $y,
                            $w,
                            $bar_h,
                            0.4,
                            ['stretchorfitBaseLine' => false, 'text' => $show_text ? 1 : 0],
                            'N'
                        );
                    }
                    break;

                case 'qr':
                    $qr_value = $this->get_text_value($product, $el, $site_url, $host);
                    if (!empty($qr_value)) {
                        // Ensure QR code is square (use minimum dimension)
                        $qr_size = max(min($w, $h), 10); // Minimum 10mm
                        // Center the QR code in the available space
                        $qr_x = $x + ($w - $qr_size) / 2;
                        $qr_y = $y + ($h - $qr_size) / 2;

                        $qr_type = $el['qrType'] ?? 'QRCODE,H';
                        $pdf->write2DBarcode(
                            $qr_value,
                            $qr_type,
                            $qr_x,
                            $qr_y,
                            $qr_size,
                            $qr_size,
                            [
                                'border' => false,
                                'vpadding' => 'auto',
                                'hpadding' => 'auto',
                                'fgcolor' => [0, 0, 0],
                                'bgcolor' => false, // transparent
                            ],
                            'N',
                            false // preserve aspect ratio
                        );
                    }
                    break;

                case 'image':
                    $image_data_source = $el['imageDataSource'] ?? 'custom';
                    $image_url = $el['imageUrl'] ?? $el['customText'] ?? '';

                    // If image data source is product_image, fetch from product
                    if ($image_data_source === 'product_image') {
                        $image_id = $product->get_image_id();
                        if ($image_id) {
                            $image_url = wp_get_attachment_image_url($image_id, 'full');
                        }
                    }

                    if (!empty($image_url)) {
                        $image_path = $this->url_to_path($image_url);
                        if ($image_path && file_exists($image_path)) {
                            $pdf->Image($image_path, $x, $y, $w, $h, '', '', '', false, 300, '', false, false, 0);
                        }
                    }
                    break;
            }
        }
    }

    /**
     * Strip HTML tags and decode entities
     */
    private function strip_html(string $text): string
    {
        // Remove HTML tags
        $text = wp_strip_all_tags($text);
        // Decode HTML entities
        $text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        // Remove extra whitespace
        $text = preg_replace('/\s+/', ' ', $text);
        return trim($text);
    }

    /**
     * Get text value based on element data source
     */
    private function get_text_value(WC_Product $product, array $el, string $site_url, string $host): string
    {
        $data_source = $el['dataSource'] ?? 'product_name';
        $custom_text = $el['customText'] ?? '';

        switch ($data_source) {
            // Product Data
            case 'product_id':
                return (string) $product->get_id();

            case 'product_name':
                return $this->strip_html($product->get_name());

            case 'product_sku':
                return 'SKU: ' . $product->get_sku();

            case 'product_price':
                // Get raw price number and format it without HTML
                $price = (float) $product->get_price();
                return number_format($price, 2, '.', '');

            case 'product_link':
                // Force frontend permalink, not admin URL
                return get_permalink($product->get_id());

            case 'product_image':
                $image_id = $product->get_image_id();
                if ($image_id) {
                    $image_url = wp_get_attachment_image_url($image_id, 'full');
                    if ($image_url) {
                        return $image_url;
                    }
                }
                // Fallback to placeholder if no image
                return ''; // empty will use fallback in image element

            case 'product_stock':
                $stock_qty = $product->get_stock_quantity();
                return $stock_qty !== null ? (string) $stock_qty : '';

            case 'product_weight':
                $weight = $product->get_weight();
                if ($weight) {
                    $weight_unit = get_option('woocommerce_weight_unit', 'kg');
                    return $weight . ' ' . $weight_unit;
                }
                return '';

            case 'product_length':
                $length = $product->get_length();
                if ($length) {
                    $dimension_unit = get_option('woocommerce_dimension_unit', 'cm');
                    return $length . ' ' . $dimension_unit;
                }
                return '';

            case 'product_width':
                $width = $product->get_width();
                if ($width) {
                    $dimension_unit = get_option('woocommerce_dimension_unit', 'cm');
                    return $width . ' ' . $dimension_unit;
                }
                return '';

            case 'product_height':
                $height = $product->get_height();
                if ($height) {
                    $dimension_unit = get_option('woocommerce_dimension_unit', 'cm');
                    return $height . ' ' . $dimension_unit;
                }
                return '';

            case 'product_dimensions':
                $length = $product->get_length();
                $width = $product->get_width();
                $height = $product->get_height();
                if ($length && $width && $height) {
                    $dimension_unit = get_option('woocommerce_dimension_unit', 'cm');
                    return $length . ' x ' . $width . ' x ' . $height . ' ' . $dimension_unit;
                }
                return '';

            case 'product_categories':
                $categories = get_the_terms($product->get_id(), 'product_cat');
                if ($categories && !is_wp_error($categories)) {
                    $category_names = wp_list_pluck($categories, 'name');
                    return $this->strip_html(implode(', ', $category_names));
                }
                return '';

            case 'product_tags':
                $tags = get_the_terms($product->get_id(), 'product_tag');
                if ($tags && !is_wp_error($tags)) {
                    $tag_names = wp_list_pluck($tags, 'name');
                    return $this->strip_html(implode(', ', $tag_names));
                }
                return '';

            case 'product_short_description':
                return $this->strip_html($product->get_short_description() ?: '');

            case 'product_total_sales':
                return (string) $product->get_total_sales();

            // Order Data
            case 'order_number':
                if ($this->order) {
                    return '#' . $this->order->get_order_number();
                }
                return '#' . $product->get_id();

            case 'order_date':
                if ($this->order) {
                    return $this->order->get_date_created()->date_i18n(get_option('date_format', 'Y-m-d'));
                }
                return current_time(get_option('date_format', 'Y-m-d'));

            case 'order_total':
                if ($this->order) {
                    return $this->format_price($this->order->get_total(), $this->order->get_currency());
                }
                return $this->format_price((float) $product->get_price());

            case 'order_subtotal':
                if ($this->order) {
                    return $this->format_price($this->order->get_subtotal(), $this->order->get_currency());
                }
                return $this->format_price((float) $product->get_price());

            case 'order_tax':
                if ($this->order) {
                    return $this->format_price($this->order->get_total_tax(), $this->order->get_currency());
                }
                return $this->format_price(0);

            case 'order_shipping_cost':
                if ($this->order) {
                    return $this->format_price($this->order->get_shipping_total(), $this->order->get_currency());
                }
                return $this->format_price(0);

            case 'order_discount':
                if ($this->order) {
                    return $this->format_price($this->order->get_discount_total(), $this->order->get_currency());
                }
                return $this->format_price(0);

            case 'order_status':
                if ($this->order) {
                    return wc_get_order_status_name($this->order->get_status());
                }
                return 'Processing';

            case 'order_item_count':
                if ($this->order) {
                    return (string) $this->order->get_item_count();
                }
                return '1';

            case 'order_items_list':
                if ($this->order) {
                    $names = [];
                    foreach ($this->order->get_items() as $item) {
                        $names[] = $item->get_name() . ' ×' . $item->get_quantity();
                    }
                    return implode(', ', $names);
                }
                return $this->strip_html($product->get_name());

            case 'customer_first_name':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_first_name() ?: $this->order->get_shipping_first_name());
                }
                return 'John';

            case 'customer_last_name':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_last_name() ?: $this->order->get_shipping_last_name());
                }
                return 'Doe';

            case 'customer_full_name':
                if ($this->order) {
                    $first = $this->order->get_billing_first_name() ?: $this->order->get_shipping_first_name();
                    $last = $this->order->get_billing_last_name() ?: $this->order->get_shipping_last_name();
                    return $this->strip_html(trim($first . ' ' . $last));
                }
                return 'John Doe';

            case 'customer_email':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_email());
                }
                return 'customer@example.com';

            case 'customer_phone':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_phone());
                }
                return $custom_text ?: '+212 6XX XXX XXX';

            case 'customer_company':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_company());
                }
                return '';

            case 'billing_address':
                if ($this->order) {
                    $address = $this->strip_html($this->order->get_billing_address_1());
                    if ($this->order->get_billing_address_2()) {
                        $address .= "\n" . $this->strip_html($this->order->get_billing_address_2());
                    }
                    $city = $this->strip_html($this->order->get_billing_city());
                    $state = $this->strip_html($this->order->get_billing_state());
                    $postcode = $this->strip_html($this->order->get_billing_postcode());
                    $country = $this->order->get_billing_country();
                    if ($city || $state || $postcode) {
                        $address .= "\n" . trim($city . ' ' . $state . ' ' . $postcode);
                    }
                    if ($country) {
                        $address .= "\n" . WC()->countries->countries[$country] ?? $country;
                    }
                    return $address;
                }
                return 'Billing Address';

            case 'billing_address_1':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_address_1());
                }
                return '';

            case 'billing_address_2':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_address_2());
                }
                return '';

            case 'billing_city':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_city());
                }
                return '';

            case 'billing_state':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_state());
                }
                return '';

            case 'billing_postcode':
                if ($this->order) {
                    return $this->strip_html($this->order->get_billing_postcode());
                }
                return '';

            case 'billing_country':
                if ($this->order) {
                    $country_code = $this->order->get_billing_country();
                    return $this->strip_html(WC()->countries->countries[$country_code] ?? $country_code);
                }
                return '';

            case 'shipping_address':
                if ($this->order) {
                    $address = $this->strip_html($this->order->get_shipping_address_1());
                    if ($this->order->get_shipping_address_2()) {
                        $address .= "\n" . $this->strip_html($this->order->get_shipping_address_2());
                    }
                    $city = $this->strip_html($this->order->get_shipping_city());
                    $state = $this->strip_html($this->order->get_shipping_state());
                    $postcode = $this->strip_html($this->order->get_shipping_postcode());
                    $country = $this->order->get_shipping_country();
                    if ($city || $state || $postcode) {
                        $address .= "\n" . trim($city . ' ' . $state . ' ' . $postcode);
                    }
                    if ($country) {
                        $address .= "\n" . WC()->countries->countries[$country] ?? $country;
                    }
                    return $address;
                }
                return 'Shipping Address';

            case 'shipping_address_1':
                if ($this->order) {
                    return $this->strip_html($this->order->get_shipping_address_1());
                }
                return '';

            case 'shipping_address_2':
                if ($this->order) {
                    return $this->strip_html($this->order->get_shipping_address_2());
                }
                return '';

            case 'shipping_city':
                if ($this->order) {
                    return $this->strip_html($this->order->get_shipping_city());
                }
                return '';

            case 'shipping_state':
                if ($this->order) {
                    return $this->strip_html($this->order->get_shipping_state());
                }
                return '';

            case 'shipping_postcode':
                if ($this->order) {
                    return $this->strip_html($this->order->get_shipping_postcode());
                }
                return '';

            case 'shipping_country':
                if ($this->order) {
                    $country_code = $this->order->get_shipping_country();
                    return $this->strip_html(WC()->countries->countries[$country_code] ?? $country_code);
                }
                return '';

            case 'payment_method':
                if ($this->order) {
                    return $this->order->get_payment_method_title();
                }
                return 'Payment Method';

            case 'shipping_method':
                if ($this->order) {
                    $methods = $this->order->get_shipping_methods();
                    $method_names = [];
                    foreach ($methods as $method) {
                        $method_names[] = $method->get_method_title() ?: $method->get_name();
                    }
                    return implode(', ', $method_names);
                }
                return 'Shipping Method';

            // Order items formatted as a multi-line text block for invoices
            case 'order_items_table':
                $show_qty = !isset($el['showItemQty']) || !empty($el['showItemQty']);
                $show_price = !isset($el['showItemPrice']) || !empty($el['showItemPrice']);
                $show_image = !empty($el['showItemImage']);

                if ($this->order) {
                    $currency = $this->order->get_currency();

                    // Build dynamic header based on visible columns
                    $header_parts = [];
                    if ($show_image)
                        $header_parts[] = sprintf('%5s', '[IMG]');
                    $header_parts[] = sprintf('%-28s', 'Product');
                    if ($show_qty)
                        $header_parts[] = sprintf('%5s', 'Qty');
                    if ($show_price)
                        $header_parts[] = sprintf('%12s', 'Price');
                    $header = implode(' ', $header_parts);
                    $sep = str_repeat('-', strlen($header));
                    $lines = [$header, $sep];

                    foreach ($this->order->get_items() as $item) {
                        $row_parts = [];
                        if ($show_image) {
                            // Check if product has an image
                            $product_for_image = $item->get_product();
                            if ($product_for_image && $product_for_image->get_image_id()) {
                                $row_parts[] = sprintf('%5s', '[IMG]');
                            } else {
                                $row_parts[] = sprintf('%5s', '');
                            }
                        }
                        $row_parts[] = sprintf('%-28s', mb_strimwidth($item->get_name(), 0, 26, '..'));
                        if ($show_qty)
                            $row_parts[] = sprintf('%5s', 'x' . $item->get_quantity());
                        if ($show_price)
                            $row_parts[] = sprintf('%12s', $this->format_price($item->get_total(), $currency));
                        $lines[] = implode(' ', $row_parts);
                    }

                    $lines[] = $sep;
                    if ($show_price) {
                        $total_parts = [];
                        if ($show_image)
                            $total_parts[] = sprintf('%5s', '');
                        $total_parts[] = sprintf('%-28s', 'Total');
                        if ($show_qty)
                            $total_parts[] = sprintf('%5s', '');
                        $total_parts[] = sprintf('%12s', $this->format_price($this->order->get_total(), $currency));
                        $lines[] = implode(' ', $total_parts);
                    }

                    return implode("\n", $lines);
                }

                // Preview fallback (no order context)
                $name = mb_strimwidth($this->strip_html($product->get_name()), 0, 26, '..');
                $price = $this->format_price((float) $product->get_price());
                $header_parts = [];
                if ($show_image)
                    $header_parts[] = sprintf('%5s', '[IMG]');
                $header_parts[] = sprintf('%-28s', 'Product');
                if ($show_qty)
                    $header_parts[] = sprintf('%5s', 'Qty');
                if ($show_price)
                    $header_parts[] = sprintf('%12s', 'Price');
                $header = implode(' ', $header_parts);
                $sep = str_repeat('-', strlen($header));
                $row_parts = [];
                if ($show_image)
                    $row_parts[] = sprintf('%5s', '[IMG]');
                $row_parts[] = sprintf('%-28s', $name);
                if ($show_qty)
                    $row_parts[] = sprintf('%5s', 'x1');
                if ($show_price)
                    $row_parts[] = sprintf('%12s', $price);
                $row = implode(' ', $row_parts);
                $total_line = '';
                if ($show_price) {
                    $t = [];
                    if ($show_image)
                        $t[] = sprintf('%5s', '');
                    $t[] = sprintf('%-28s', 'Total');
                    if ($show_qty)
                        $t[] = sprintf('%5s', '');
                    $t[] = sprintf('%12s', $price);
                    $total_line = "\n" . $sep . "\n" . implode(' ', $t);
                }
                return $header . "\n" . $sep . "\n" . $row . $total_line;

            // Invoice table with order totals and payment info
            case 'order_invoice_table':
                $show_subtotal = !isset($el['showSubtotal']) || !empty($el['showSubtotal']);
                $show_tax = !isset($el['showTax']) || !empty($el['showTax']);
                $show_shipping = !isset($el['showShipping']) || !empty($el['showShipping']);
                $show_total = !isset($el['showTotal']) || !empty($el['showTotal']);
                $show_payment = !empty($el['showPayment']);
                $show_shipping_via = !empty($el['showShippingVia']);

                if ($this->order) {
                    $currency = $this->order->get_currency();
                    $lines = [];

                    // Calculate maximum label width for alignment
                    $labels = [];
                    if ($show_subtotal)
                        $labels[] = 'Subtotal:';
                    if ($show_tax)
                        $labels[] = 'Tax:';
                    if ($show_shipping)
                        $labels[] = 'Shipping:';
                    if ($show_total)
                        $labels[] = 'TOTAL:';
                    if ($show_payment)
                        $labels[] = 'Payment:';
                    if ($show_shipping_via)
                        $labels[] = 'Shipping via:';

                    $max_label_len = max(array_map('strlen', $labels));

                    // Build each row with proper alignment
                    if ($show_subtotal) {
                        $subtotal = $this->format_price($this->order->get_subtotal(), $currency);
                        $lines[] = sprintf('%-' . $max_label_len . 's %s', 'Subtotal:', $subtotal);
                    }

                    if ($show_tax) {
                        $tax_total = $this->order->get_total_tax();
                        $lines[] = sprintf('%-' . $max_label_len . 's %s', 'Tax:', $this->format_price($tax_total, $currency));
                    }

                    if ($show_shipping) {
                        $shipping_total = $this->order->get_shipping_total();
                        $lines[] = sprintf('%-' . $max_label_len . 's %s', 'Shipping:', $this->format_price($shipping_total, $currency));
                    }

                    // Separator line
                    if ($show_total) {
                        $sep_len = $max_label_len + 15;
                        $lines[] = str_repeat('-', $sep_len);
                    }

                    if ($show_total) {
                        $total = $this->order->get_total();
                        $lines[] = sprintf('%-' . $max_label_len . 's %s', 'TOTAL:', $this->format_price($total, $currency));
                    }

                    // Add payment and shipping method on separate lines
                    if ($show_payment) {
                        $payment_method = $this->order->get_payment_method_title();
                        if ($payment_method) {
                            $lines[] = '';
                            $lines[] = sprintf('%-' . $max_label_len . 's %s', 'Payment:', $payment_method);
                        }
                    }

                    if ($show_shipping_via) {
                        $shipping_method = $this->order->get_shipping_method();
                        if ($shipping_method) {
                            $lines[] = sprintf('%-' . $max_label_len . 's %s', 'Shipping via:', $shipping_method);
                        }
                    }

                    return implode("\n", $lines);
                }

                // Preview fallback (no order context)
                $preview_lines = [
                    'Subtotal:      $100.00',
                    'Tax:            $20.00',
                    'Shipping:       $15.00',
                    '---------------------',
                    'TOTAL:         $135.00',
                ];
                return implode("\n", $preview_lines);

            // Full invoice with items table and totals - returns JSON for structured rendering
            case 'order_invoice_full':
                $show_image = !empty($el['showItemImage']);
                $show_qty = !isset($el['showItemQty']) || !empty($el['showItemQty']);
                $show_price = !isset($el['showItemPrice']) || !empty($el['showItemPrice']);
                $show_subtotal = !isset($el['showSubtotal']) || !empty($el['showSubtotal']);
                $show_tax = !isset($el['showTax']) || !empty($el['showTax']);
                $show_shipping = !isset($el['showShipping']) || !empty($el['showShipping']);
                $show_discount = !empty($el['showDiscount']);
                $show_total = !isset($el['showTotal']) || !empty($el['showTotal']);
                $show_payment = !empty($el['showPayment']);
                $show_shipping_via = !empty($el['showShippingVia']);

                if ($this->order) {
                    $currency = $this->order->get_currency();
                    $data = [
                        'type' => 'invoice_full',
                        'show_image' => $show_image,
                        'show_qty' => $show_qty,
                        'show_price' => $show_price,
                        'show_subtotal' => $show_subtotal,
                        'show_tax' => $show_tax,
                        'show_shipping' => $show_shipping,
                        'show_discount' => $show_discount,
                        'show_total' => $show_total,
                        'show_payment' => $show_payment,
                        'show_shipping_via' => $show_shipping_via,
                        'items' => [],
                        'totals' => [],
                    ];

                    // Build items data
                    foreach ($this->order->get_items() as $item) {
                        $item_data = [
                            'name' => mb_strimwidth($item->get_name(), 0, 40, '..'),
                            'qty' => $item->get_quantity(),
                        ];
                        if ($show_price) {
                            $unit_price = $item->get_subtotal() / max(1, $item->get_quantity());
                            $item_data['price'] = $this->format_price($unit_price, $currency);
                            $item_data['total'] = $this->format_price($item->get_total(), $currency);
                            $item_data['unit_price'] = $item_data['price'];
                        }
                        if ($show_image) {
                            $product_for_image = $item->get_product();
                            if ($product_for_image && $product_for_image->get_image_id()) {
                                $image_id = $product_for_image->get_image_id();
                                $item_data['image_id'] = $image_id;
                                $item_data['image_url'] = wp_get_attachment_image_url($image_id, 'thumbnail');
                            }
                        }
                        $data['items'][] = $item_data;
                    }

                    // Build totals data
                    if ($show_subtotal) {
                        $data['totals'][] = ['label' => 'Subtotal', 'value' => $this->format_price($this->order->get_subtotal(), $currency)];
                    }
                    if ($show_tax) {
                        $data['totals'][] = ['label' => 'Tax', 'value' => $this->format_price($this->order->get_total_tax(), $currency)];
                    }
                    if ($show_shipping) {
                        $data['totals'][] = ['label' => 'Shipping', 'value' => $this->format_price($this->order->get_shipping_total(), $currency)];
                    }
                    if ($show_discount && $this->order->get_total_discount() > 0) {
                        $data['totals'][] = ['label' => '', 'value' => '-' . $this->format_price($this->order->get_total_discount(), $currency), 'is_discount' => true];
                    }
                    if ($show_total) {
                        $data['totals'][] = ['label' => 'TOTAL', 'value' => $this->format_price($this->order->get_total(), $currency), 'is_total' => true];
                    }
                    if ($show_payment) {
                        $data['payment_method'] = $this->order->get_payment_method_title();
                    }
                    if ($show_shipping_via) {
                        $data['shipping_method'] = $this->order->get_shipping_method();
                    }

                    // Return JSON string for structured rendering
                    return wp_json_encode($data);
                }

                // Preview fallback
                return 'INVOICE - Items + Totals Table';

            // Shop / Store Info
            case 'shop_name':
                return $this->strip_html(get_bloginfo('name'));

            case 'shop_email':
                return get_option('woocommerce_email_from_address', get_option('admin_email', ''));

            case 'shop_phone':
                return get_option('woocommerce_store_phone', '');

            case 'shop_address':
                $addr_parts = array_filter([
                    get_option('woocommerce_store_address', ''),
                    get_option('woocommerce_store_address_2', ''),
                ]);
                return implode(', ', $addr_parts);

            case 'shop_city':
                return get_option('woocommerce_store_city', '');

            case 'shop_postcode':
                return get_option('woocommerce_store_postcode', '');

            case 'shop_country':
                $country_state = get_option('woocommerce_default_country', '');
                $parts = explode(':', $country_state);
                $code = $parts[0] ?? $country_state;
                return WC()->countries->countries[$code] ?? $code;

            // Other
            case 'site_url':
                return $host;

            case 'phone':
                return $custom_text ?: '+212 6XX XXX XXX';

            case 'current_date':
                return current_time('Y-m-d');

            case 'custom':
                return $custom_text ?: 'Custom Text';

            default:
                return $product->get_name();
        }
    }

    /**
     * Returns true if the layout contains any element that displays order-level
     * data (invoice, items table, billing/shipping address). These labels should
     * render as a single page for the whole order rather than one page per product.
     */
    public function is_order_level_layout(): bool
    {
        $order_sources = [
            'order_items_table',
            'order_invoice_table',
            'order_invoice_full',
            'billing_address',
            'shipping_address',
            'order_total',
            'order_subtotal',
            'order_tax',
            'order_shipping_cost',
            'order_discount',
            'order_item_count',
            'order_items_list',
        ];
        foreach ($this->layout as $el) {
            if (in_array($el['dataSource'] ?? '', $order_sources, true)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Format a price amount as plain text (no HTML entities).
     * Outputs: "170.00 MAD" style, with the currency symbol from WooCommerce.
     */
    private function format_price(float $amount, string $currency = ''): string
    {
        if (empty($currency)) {
            $currency = get_woocommerce_currency();
        }
        $symbol = html_entity_decode(get_woocommerce_currency_symbol($currency), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $decimals = (int) get_option('woocommerce_price_num_decimals', 2);
        $decimal_sep = get_option('woocommerce_price_decimal_sep', '.');
        $thousand_sep = get_option('woocommerce_price_thousand_sep', ',');
        $formatted = number_format($amount, $decimals, $decimal_sep, $thousand_sep);

        $position = get_option('woocommerce_currency_pos', 'left');
        switch ($position) {
            case 'left':
                return $symbol . $formatted;
            case 'right':
                return $formatted . $symbol;
            case 'left_space':
                return $symbol . ' ' . $formatted;
            case 'right_space':
                return $formatted . ' ' . $symbol;
            default:
                return $formatted . ' ' . $symbol;
        }
    }

    /**
     * Render a full invoice table with items and totals using TCPDF table methods.
     */
    private function render_invoice_table($pdf, $data, $x, $y, $w, $h, $fs, $align, $text_direction = 'ltr', $fit_width = false)
    {
        $is_rtl = $text_direction === 'rtl';
        // Apply RTL mode for the invoice block only
        if ($is_rtl) {
            $pdf->setRTL(true);
        }
        $show_image = $data['show_image'] ?? false;
        $show_qty = $data['show_qty'] ?? true;
        $show_price = $data['show_price'] ?? true;
        $show_subtotal = $data['show_subtotal'] ?? true;
        $show_tax = $data['show_tax'] ?? true;
        $show_shipping = $data['show_shipping'] ?? true;
        $show_discount = $data['show_discount'] ?? false;
        $show_total = $data['show_total'] ?? true;
        $show_payment = $data['show_payment'] ?? false;
        $show_shipping_via = $data['show_shipping_via'] ?? false;

        $items = $data['items'] ?? [];
        $totals = $data['totals'] ?? [];
        $payment_method = $data['payment_method'] ?? '';
        $shipping_method = $data['shipping_method'] ?? '';

        // Auto-scale font size so the table fits within the element height
        if ($fit_width && $h > 0) {
            $n_item_rows = count($items);
            $n_total_rows = count($totals);
            $n_extra = ($show_payment && !empty($payment_method) ? 1 : 0)
                + ($show_shipping_via && !empty($shipping_method) ? 1 : 0);
            // 1 header row + items + 2mm gap (≈ 1 pseudo-row) + totals + extra
            $n_rows = 1 + $n_item_rows + 1 + $n_total_rows + $n_extra;
            $estimated_height = $n_rows * max(5, $fs * 0.5);
            if ($estimated_height > $h) {
                $scale = $h / $estimated_height;
                $fs = max(4, (int) floor($fs * $scale));
            }
        }

        // Table settings
        $border_width = 0.3;
        $header_bg = [240, 240, 240];
        $text_color = [0, 0, 0];
        $border_color = [150, 150, 150];

        $pdf->SetTextColorArray($text_color);
        $pdf->SetDrawColorArray($border_color);
        $pdf->SetLineWidth($border_width);

        // Check if any item has discount to show discount column
        $has_item_discount = false;
        if ($show_discount) {
            foreach ($items as $item) {
                if (!empty($item['discount'])) {
                    $has_item_discount = true;
                    break;
                }
            }
        }

        // Calculate column widths - always have Image, Product, Qty, Price, Total
        $col_widths = [];
        $available_width = $w;

        if ($show_image) {
            $col_widths['image'] = min(10, $available_width * 0.12);
            $available_width -= $col_widths['image'];
        }

        $col_widths['name'] = $available_width * 0.40;
        $available_width -= $col_widths['name'];

        if ($show_qty) {
            $col_widths['qty'] = $available_width * 0.12;
            $available_width -= $col_widths['qty'];
        }

        $col_widths['price'] = $available_width * 0.24;
        $available_width -= $col_widths['price'];

        $col_widths['total'] = $available_width;

        // Scale font size down so cell text fits within each column width.
        // Use TCPDF's GetStringWidth() for accurate measurement (handles Unicode/Arabic).
        $pdf->SetFont(svld_get_label_font(), '', $fs);
        $scale_factors = [];

        // Check name column: header + all item names
        if (!empty($col_widths['name'])) {
            $max_w = $pdf->GetStringWidth('Product');
            foreach ($items as $item) {
                $sw = $pdf->GetStringWidth($item['name']);
                if ($sw > $max_w)
                    $max_w = $sw;
            }
            if ($max_w > $col_widths['name']) {
                $scale_factors[] = $col_widths['name'] / $max_w;
            }
        }

        // Check price and total columns: header + item prices + totals values
        foreach (['price' => 'Price', 'total' => 'Total'] as $price_col => $header_label) {
            if (!empty($col_widths[$price_col])) {
                $max_w = $pdf->GetStringWidth($header_label);
                foreach ($items as $item) {
                    if (!empty($item[$price_col])) {
                        $sw = $pdf->GetStringWidth($item[$price_col]);
                        if ($sw > $max_w)
                            $max_w = $sw;
                    }
                }
                foreach ($totals as $total_row) {
                    $sw = $pdf->GetStringWidth($total_row['value']);
                    if ($sw > $max_w)
                        $max_w = $sw;
                }
                if ($max_w > $col_widths[$price_col]) {
                    $scale_factors[] = $col_widths[$price_col] / $max_w;
                }
            }
        }

        // Apply the most restrictive scale (only ever scales down, never up)
        if (!empty($scale_factors)) {
            $min_scale = min($scale_factors);
            if ($min_scale < 1.0) {
                $fs = max(4, (int) floor($fs * $min_scale));
            }
        }

        // Row height based on (possibly scaled) font size
        $row_height = max(3, $fs * 0.5);
        $current_y = $y;

        // Draw header row
        $pdf->SetFillColorArray($header_bg);
        $col_x = $x;

        if ($show_image) {
            $pdf->Rect($col_x, $current_y, $col_widths['image'], $row_height, 'FD');
            $pdf->SetXY($col_x, $current_y);
            $pdf->Cell($col_widths['image'], $row_height, '', 'TB', 0, 'C', true);
            $col_x += $col_widths['image'];
        }

        $pdf->Rect($col_x, $current_y, $col_widths['name'], $row_height, 'FD');
        $pdf->SetXY($col_x, $current_y);
        $pdf->SetFont(svld_get_label_font(), 'B', $fs);
        $pdf->Cell($col_widths['name'], $row_height, 'Product', 'TB', 0, 'L', true);
        $col_x += $col_widths['name'];

        if ($show_qty) {
            $pdf->Rect($col_x, $current_y, $col_widths['qty'], $row_height, 'FD');
            $pdf->SetXY($col_x, $current_y);
            $pdf->Cell($col_widths['qty'], $row_height, 'Qty', 'TB', 0, 'C', true);
            $col_x += $col_widths['qty'];
        }

        $pdf->Rect($col_x, $current_y, $col_widths['price'], $row_height, 'FD');
        $pdf->SetXY($col_x, $current_y);
        $pdf->Cell($col_widths['price'], $row_height, 'Price', 'TB', 0, 'R', true);
        $col_x += $col_widths['price'];

        $pdf->Rect($col_x, $current_y, $col_widths['total'], $row_height, 'FD');
        $pdf->SetXY($col_x, $current_y);
        $pdf->Cell($col_widths['total'], $row_height, 'Total', 'TB', 0, 'R', true);

        $current_y += $row_height;
        $pdf->SetFont(svld_get_label_font(), '', $fs * 0.9);

        // Draw items rows
        foreach ($items as $item) {
            $col_x = $x;

            if ($show_image && !empty($item['image_url'])) {
                $pdf->Rect($col_x, $current_y, $col_widths['image'], $row_height, 'D');
                $image_path = $this->url_to_path($item['image_url']);
                if ($image_path && file_exists($image_path)) {
                    $img_size = min($col_widths['image'] - 1, $row_height - 1);
                    $img_offset_x = $col_x + ($col_widths['image'] - $img_size) / 2;
                    $img_offset_y = $current_y + ($row_height - $img_size) / 2;
                    $pdf->Image($image_path, $img_offset_x, $img_offset_y, $img_size, $img_size, '', '', '', false, 150, '', false, false, 0, 'T');
                }
                $col_x += $col_widths['image'];
            } elseif ($show_image) {
                $pdf->Rect($col_x, $current_y, $col_widths['image'], $row_height, 'D');
                $col_x += $col_widths['image'];
            }

            $pdf->Rect($col_x, $current_y, $col_widths['name'], $row_height, 'D');
            $pdf->SetXY($col_x, $current_y);
            $pdf->Cell($col_widths['name'], $row_height, $item['name'], 'TB', 0, 'L');
            $col_x += $col_widths['name'];

            if ($show_qty) {
                $pdf->Rect($col_x, $current_y, $col_widths['qty'], $row_height, 'D');
                $pdf->SetXY($col_x, $current_y);
                $pdf->Cell($col_widths['qty'], $row_height, 'x' . $item['qty'], 'TB', 0, 'C');
                $col_x += $col_widths['qty'];
            }

            if ($show_price) {
                $pdf->Rect($col_x, $current_y, $col_widths['price'], $row_height, 'D');
                $pdf->SetXY($col_x, $current_y);
                $pdf->Cell($col_widths['price'], $row_height, $item['price'], 'TB', 0, 'R');
                $col_x += $col_widths['price'];
            }

            $pdf->Rect($col_x, $current_y, $col_widths['total'], $row_height, 'D');
            $pdf->SetXY($col_x, $current_y);
            $pdf->Cell($col_widths['total'], $row_height, $item['total'], 'TB', 0, 'R');
            $current_y += $row_height;

            // Add discount row below item if it has a discount
            if ($has_item_discount && !empty($item['discount'])) {
                $pdf->SetXY($x + ($show_image ? $col_widths['image'] : 0) + $col_widths['name'] + ($show_qty ? $col_widths['qty'] : 0), $current_y);
                $pdf->SetFont(svld_get_label_font(), '', $fs * 0.8);
                $pdf->Cell($col_widths['price'] + $col_widths['total'], $row_height * 0.7, '-' . $item['discount'], 0, 0, 'R');
                $current_y += $row_height * 0.7;
                $pdf->SetFont(svld_get_label_font(), '', $fs * 0.9);
            }
        }

        // Draw totals section
        if (!empty($totals)) {
            $current_y += 2; // Spacing before totals

            foreach ($totals as $total_row) {
                $is_total_line = $total_row['is_total'] ?? false;
                $is_discount = $total_row['is_discount'] ?? false;
                $label = $total_row['label'];
                $value = $total_row['value'];

                if ($is_total_line) {
                    $pdf->SetFont(svld_get_label_font(), 'B', $fs);
                }

                $pdf->SetXY($x, $current_y);
                if ($is_discount) {
                    // Discount: no label — value only, right-aligned, in full row width
                    $pdf->Cell($w, $row_height * 0.8, $value, 0, 0, 'R');
                } else {
                    $pdf->Cell($w - 20, $row_height * 0.8, $label, 0, 0, 'L');
                    $pdf->Cell(20, $row_height * 0.8, $value, 0, 0, 'R');
                }
                $current_y += $row_height * 0.8;

                if ($is_total_line) {
                    $pdf->SetFont(svld_get_label_font(), '', $fs * 0.9);
                }
            }
        }

        // Draw payment and shipping method
        if ($show_payment && !empty($payment_method)) {
            $current_y += 2;
            $pdf->SetXY($x, $current_y);
            $pdf->Cell($w, $row_height * 0.7, 'Payment: ' . $payment_method, 0, 0, 'L');
            $current_y += $row_height * 0.7;
        }

        if ($show_shipping_via && !empty($shipping_method)) {
            $current_y += 1;
            $pdf->SetXY($x, $current_y);
            $pdf->Cell($w, $row_height * 0.7, 'Shipping via: ' . $shipping_method, 0, 0, 'L');
        }

        // Restore LTR after RTL invoice block so other elements are unaffected
        if ($is_rtl) {
            $pdf->setRTL(false);
        }
    }

    /**
     * Get the last error message
     */
    public function get_last_error(): string
    {
        return $this->last_error;
    }

    /**
     * Set an error message
     */
    private function set_error(string $message): void
    {
        $this->last_error = $message;
        if (defined('WP_DEBUG') && WP_DEBUG) {
            error_log('[SVLD] ' . $message);
        }
    }

    /**
     * Convert a local WordPress upload URL to an absolute file path.
     */
    private function url_to_path(string $url): string
    {
        $upload_dir = wp_upload_dir();
        if (strpos($url, $upload_dir['baseurl']) !== false) {
            return str_replace($upload_dir['baseurl'], $upload_dir['basedir'], $url);
        }
        // Fallback for other URLs within the site
        $site_url = home_url();
        $doc_root = rtrim( sanitize_text_field( wp_unslash( $_SERVER['DOCUMENT_ROOT'] ?? '' ) ), '/' );
        return str_replace($site_url, $doc_root, $url);
    }

    /**
     * Load TCPDF from bundled copy.
     */
    private function load_tcpdf(): bool
    {
        if (class_exists('TCPDF')) {
            return true;
        }

        // Load bundled TCPDF
        $local_tcpdf = SVLD_PLUGIN_DIR . 'includes/vendor/tcpdf/tcpdf.php';
        if (file_exists($local_tcpdf)) {
            require_once $local_tcpdf;
            if (class_exists('TCPDF')) {
                return true;
            }
            $this->set_error('TCPDF class not found after loading from: ' . $local_tcpdf);
            return false;
        }

        $this->set_error('TCPDF library not found. Looked in: ' . $main_tcpdf . ' and ' . $local_tcpdf);
        return false;
    }

    /**
     * Get the label font based on settings or auto-detect locale
     */
    private function get_font(): string
    {
        $saved_font = get_option('svld_label_font', '');
        if (!empty($saved_font)) {
            return $saved_font;
        }

        // Auto-detect based on locale
        $locale = get_locale();
        $locale_lang = substr($locale, 0, 2);

        // CJK locales
        $cjk_fonts = [
            'zh' => 'cid0cs',    // Chinese (Simplified)
            'ja' => 'cid0jp',    // Japanese
            'ko' => 'cid0kr',    // Korean
            'zh-tw' => 'cid0ct', // Chinese (Traditional)
            'zh-hk' => 'cid0ct', // Chinese (Hong Kong)
        ];

        // Russian/Cyrillic locales
        $cyrillic_locales = ['ru', 'uk', 'bg', 'mk', 'sr', 'be'];

        // Arabic locales
        $arabic_locales = ['ar', 'fa', 'ur', 'he', 'yi'];

        // Indian locales
        $indian_locales = ['hi', 'bn', 'ta', 'te', 'ml', 'mr', 'gu'];

        if (isset($cjk_fonts[$locale_lang])) {
            return $cjk_fonts[$locale_lang];
        }

        if (in_array($locale_lang, $cyrillic_locales, true)) {
            return 'dejavusans';
        }

        if (in_array($locale_lang, $arabic_locales, true)) {
            return 'dejavusans';
        }

        if (in_array($locale_lang, $indian_locales, true)) {
            return 'freesans';
        }

        // Default to helvetica for Western locales
        return 'helvetica';
    }

    /**
     * Get the conversion factor from stored unit to mm
     */
    private function get_unit_to_mm_factor(): float
    {
        $unit = get_option('svld_measurement_unit', 'mm');

        switch ($unit) {
            case 'cm':
                return 10.0; // 1 cm = 10 mm
            case 'in':
                return 25.4; // 1 inch = 25.4 mm
            case 'pt':
                return 0.352778; // 1 point = 0.352778 mm
            case 'mm':
            default:
                return 1.0; // 1 mm = 1 mm
        }
    }

    /**
     * Get the unit label for display
     */
    private function get_unit_label(): string
    {
        $unit = get_option('svld_measurement_unit', 'mm');

        switch ($unit) {
            case 'cm':
                return 'cm';
            case 'in':
                return 'in';
            case 'pt':
                return 'pt';
            case 'mm':
            default:
                return 'mm';
        }
    }
}
