<?php

namespace Storeplugin\PDFInvoice\App\Integrations;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\App\Settings\SettingsSchema;
use Storeplugin\PDFInvoice\App\TemplateLoader;
use Storeplugin\PDFInvoice\App\TempDir;
use Storeplugin\PDFInvoice\App\Utils;
use Mpdf\Config\ConfigVariables;
use Mpdf\Config\FontVariables;
use Mpdf\HTMLParserMode;
use Mpdf\Mpdf;

class PdfDocumentGenerator {
    private $mpdf;
    private array $previewOverrides = [];
    private array $settings = [];
    private string $editorOptionGroup = Utils::GROUP_INVOICE_EDITOR;
    private string $templateScope = TemplateLoader::SCOPE_INVOICE;

    public function __construct($previewOverrides = [], string $editorOptionGroup = Utils::GROUP_INVOICE_EDITOR) {
        $this->previewOverrides = is_array($previewOverrides) ? $previewOverrides : [];
        $this->editorOptionGroup = $this->normalize_editor_option_group($editorOptionGroup);
        $this->templateScope = $this->resolve_template_scope($this->editorOptionGroup);
        $this->settings = $this->resolve_settings();
    }

    /**
     * Normalize editor option group to a supported group key.
     */
    private function normalize_editor_option_group(string $group): string
    {
        $group = sanitize_key($group);
        if ($group === Utils::GROUP_PACKING_SLIP_EDITOR) {
            return Utils::GROUP_PACKING_SLIP_EDITOR;
        }

        return Utils::GROUP_INVOICE_EDITOR;
    }

    /**
     * Resolve template scope from editor option group.
     */
    private function resolve_template_scope(string $editorOptionGroup): string
    {
        if ($editorOptionGroup === Utils::GROUP_PACKING_SLIP_EDITOR) {
            return TemplateLoader::SCOPE_PACKING_SLIPS;
        }

        return TemplateLoader::SCOPE_INVOICE;
    }

    /**
     * Resolve preview overrides and stored settings into a single payload.
     *
     * Preview overrides (used by live preview) win over persisted options.
     */
    private function resolve_settings(): array
    {
        $resolved = [];

        foreach (SettingsSchema::allKeys() as $key => $meta) {
            $hasDefault = array_key_exists('default', $meta);
            $optionGroup = (string) ($meta['group'] ?? '');
            if ($optionGroup === Utils::GROUP_INVOICE_EDITOR) {
                $optionGroup = $this->editorOptionGroup;
            }

            if (array_key_exists($key, $this->previewOverrides)) {
                $value = $this->previewOverrides[$key];
                $resolved[$key] = ($hasDefault && ($value === '' || $value === null))
                    ? $meta['default']
                    : $value;
            } elseif ($hasDefault) {
                $resolved[$key] = Utils::getOption($key, $optionGroup, $meta['default']);
            } else {
                $resolved[$key] = Utils::getOption($key, $optionGroup);
            }
        }

        foreach ($resolved as $key => $value) {
            $resolved[$key] = SettingsSchema::sanitizeValue((string) $key, $value);
        }

        return SettingsSchema::normalizeRuntimeSettings($resolved, $this->editorOptionGroup);
    }

    /**
     * Get or generate the document number for an order.
     *
     * Uses scope-specific order meta so invoice and packing slip numbering do not
     * overwrite each other when they have different editor settings.
     *
     * @param \WC_Order $order
     * @return string
     */
    public function get_invoice_number($order) {
        $mode = (string) ($this->settings['invoice_number_mode'] ?? '');

        if ($mode !== 'sequential') {
            return $this->format_invoice_number($order->get_order_number());
        }

        $stored_number_key = $this->get_document_number_meta_key();
        $stored_number_raw_key = $this->get_document_number_raw_meta_key();

        // Sequential mode - check if order already has a stored document number.
        $stored_number = $order->get_meta($stored_number_key, true);
        if ($stored_number) {
            return $stored_number;
        }

        // Generate new sequential number
        $number = $this->generate_sequential_number();
        $formatted = $this->format_invoice_number($number);

        // Store on order
        $order->update_meta_data($stored_number_key, $formatted);
        $order->update_meta_data($stored_number_raw_key, $number);
        $order->save();

        return $formatted;
    }

    /**
     * Generate PDF filename based on format setting.
     *
     * @param array $data Order data with order_number, invoice_number, etc.
     * @return string Filename with .pdf extension
     */
    public function get_pdf_filename($data = []) {
        $format = (string) ($this->settings['pdf_filename_format'] ?? '');

        $replacements = [
            '{order_number}' => $data['order_number'] ?? '',
            '{invoice_number}' => $data['invoice_number'] ?? '',
            '{date}' => wp_date('Y-m-d'),
            '{Y}' => wp_date('Y'),
            '{m}' => wp_date('m'),
            '{d}' => wp_date('d'),
        ];

        $filename = str_replace(array_keys($replacements), array_values($replacements), $format);
        $filename = sanitize_file_name($filename);

        return $filename . '.pdf';
    }

    /**
     * Generate the next sequential invoice number.
     *
     * @return int
     */
    private function generate_sequential_number() {
        $next = (int) get_option($this->get_sequence_counter_option_key(), 1);
        $reset = (string) Utils::getOption('invoice_number_reset', $this->editorOptionGroup, 'never');

        // Check if we need to reset the counter
        $reset_key = $this->get_reset_key($reset);
        $stored_reset_key = get_option($this->get_sequence_reset_option_key(), '');

        if ($reset !== 'never' && $reset_key !== $stored_reset_key) {
            // Reset counter
            $next = 1;
            update_option($this->get_sequence_reset_option_key(), $reset_key);
        }

        // Increment for next time
        update_option($this->get_sequence_counter_option_key(), $next + 1);

        return $next;
    }

    /**
     * Get the internal option key for the next sequential number.
     */
    private function get_sequence_counter_option_key(): string
    {
        return $this->editorOptionGroup . '_sequence_next';
    }

    /**
     * Get the internal option key for the last reset marker.
     */
    private function get_sequence_reset_option_key(): string
    {
        return $this->editorOptionGroup . '_sequence_reset_key';
    }

    /**
     * Get the scope-specific order meta key for the formatted document number.
     */
    private function get_document_number_meta_key(): string
    {
        if ($this->editorOptionGroup === Utils::GROUP_PACKING_SLIP_EDITOR) {
            return '_nxts_packing_slip_number';
        }

        return '_nxts_invoice_number';
    }

    /**
     * Get the scope-specific order meta key for the raw document number.
     */
    private function get_document_number_raw_meta_key(): string
    {
        if ($this->editorOptionGroup === Utils::GROUP_PACKING_SLIP_EDITOR) {
            return '_nxts_packing_slip_number_raw';
        }

        return '_nxts_invoice_number_raw';
    }

    /**
     * Get the reset key based on reset mode.
     *
     * @param string $reset
     * @return string
     */
    private function get_reset_key($reset) {
        switch ($reset) {
            case 'yearly':
                return wp_date('Y');
            case 'monthly':
                return wp_date('Y-m');
            default:
                return 'never';
        }
    }

    /**
     * Format invoice number with prefix, suffix, and padding.
     *
     * @param mixed $number
     * @return string
     */
    private function format_invoice_number($number) {
        $prefix = (string) ($this->settings['invoice_number_prefix'] ?? '');
        $suffix = (string) ($this->settings['invoice_number_suffix'] ?? '');
        $padding = (int) ($this->settings['invoice_number_padding'] ?? 0);

        // Replace placeholders in prefix/suffix
        $prefix = $this->replace_date_placeholders($prefix);
        $suffix = $this->replace_date_placeholders($suffix);

        // Pad the number if it's numeric
        if (is_numeric($number)) {
            $number = str_pad((string) $number, $padding, '0', STR_PAD_LEFT);
        }

        return $prefix . $number . $suffix;
    }

    /**
     * Replace date placeholders in a string.
     *
     * @param string $string
     * @return string
     */
    private function replace_date_placeholders($string) {
        $replacements = [
            '{Y}' => wp_date('Y'),      // Full year: 2025
            '{y}' => wp_date('y'),      // Short year: 25
            '{m}' => wp_date('m'),      // Month: 01-12
            '{d}' => wp_date('d'),      // Day: 01-31
        ];

        return str_replace(array_keys($replacements), array_values($replacements), $string);
    }

    /**
     * Initialize mPDF
     */
    private function init_mpdf() {
        $temp_dir = TempDir::path();

        // The free build is fixed to A4 portrait.
        $format = 'A4';
        $orientation = 'P';

        // Get margins
        $margin_top = (int) $this->settings['page_margin_top'];
        $margin_right = (int) $this->settings['page_margin_right'];
        $margin_bottom = (int) $this->settings['page_margin_bottom'];
        $margin_left = (int) $this->settings['page_margin_left'];

        $config = [
            'mode' => 'utf-8',
            'format' => $format,
            'orientation' => $orientation,
            'tempDir' => $temp_dir,
            'default_font_size' => 10,
            'default_font' => 'dejavusans',
            'margin_left' => $margin_left,
            'margin_right' => $margin_right,
            'margin_top' => $margin_top,
            'margin_bottom' => $margin_bottom,
            'margin_header' => 9,
            'margin_footer' => 9,
        ];

        // Add custom font directory if exists
        $custom_font_dir = NXTPDF_DIR . '/fonts/';
        if (file_exists($custom_font_dir)) {
            $config['fontDir'] = array_merge(
                (new ConfigVariables())->getDefaults()['fontDir'],
                [$custom_font_dir]
            );

            // Define custom fonts with readable style keys, then map to mPDF keys.
            $font_style_aliases = [
                'regular' => 'R',
                'bold' => 'B',
                'italic' => 'I',
                'bold_italic' => 'BI',
            ];

            $custom_font_definitions = [
                'notosans' => [
                    'regular' => 'NotoSans-Regular.ttf',
                    'bold' => 'NotoSans-Bold.ttf',
                    'italic' => 'NotoSans-Italic.ttf',
                    'bold_italic' => 'NotoSans-BoldItalic.ttf',
                    'useOTL' => 0xFF,
                    'useKashida' => 75,
                ],
                'notosanscjk' => [
                    'regular' => 'NotoSansCJK-Regular.ttc',
                    'bold' => 'NotoSansCJK-Bold.ttc',
                ],
                'notosansarabic' => [
                    'regular' => 'NotoSansArabic-Regular.ttf',
                    'bold' => 'NotoSansArabic-Bold.ttf',
                    'useOTL' => 0xFF,
                    'useKashida' => 75,
                ],
            ];

            $mapped_font_definitions = [];
            foreach ($custom_font_definitions as $font_key => $font_definition) {
                $mapped_font_definitions[$font_key] = [];

                foreach ($font_definition as $style_name => $font_value) {
                    if (isset($font_style_aliases[$style_name])) {
                        $mapped_font_definitions[$font_key][$font_style_aliases[$style_name]] = $font_value;
                        continue;
                    }

                    $mapped_font_definitions[$font_key][$style_name] = $font_value;
                }
            }

            $default_font_data = (new FontVariables())->getDefaults()['fontdata'];
            $config['fontdata'] = $default_font_data + $mapped_font_definitions;
        }

        // Create mPDF object
        $this->mpdf = new Mpdf($config);

        // Enable auto language/font detection for better CJK/RTL support
        $this->mpdf->autoScriptToLang = true;
        $this->mpdf->autoLangToFont = true;

        // Set metadata
        $documentLabel = $this->templateScope === TemplateLoader::SCOPE_PACKING_SLIPS ? 'Packing Slip' : 'Invoice';
        $companyName = (string) ($this->settings['company_name'] ?? '');
        $this->mpdf->SetTitle($documentLabel . ' - ' . $companyName);
        $this->mpdf->SetAuthor($companyName);
        $this->mpdf->SetCreator('PDF Invoice Editor for WordPress');
    }

    /**
     * Load PDF stylesheets for the selected template and shared helpers.
     */
    private function load_document_styles(): void
    {
        foreach ($this->get_document_stylesheet_files() as $cssFile) {
            $this->load_stylesheet_file($cssFile);
        }

        $dynamicCss = $this->get_dynamic_stylesheet();
        if ($dynamicCss !== '') {
            $this->mpdf->WriteHTML($dynamicCss, HTMLParserMode::HEADER_CSS);
        }
    }

    /**
     * Load a stylesheet file into mPDF.
     */
    private function load_stylesheet_file(string $cssFile): void
    {
        if (!file_exists($cssFile)) {
            return;
        }

        $css = file_get_contents($cssFile);
        if ($css === false) {
            return;
        }

        $this->mpdf->WriteHTML($css, HTMLParserMode::HEADER_CSS);
    }

    /**
     * Generate PDF for WooCommerce order
     */
    public function generate_order_pdf($order_id) {
        if (!class_exists('WooCommerce')) {
            return new \WP_Error('woocommerce_missing', 'WooCommerce is not active');
        }

        $order = wc_get_order($order_id);
        if (!$order) {
            return new \WP_Error('order_not_found', 'Order not found');
        }

        $this->init_mpdf();
        $this->load_document_styles();

        $html = $this->build_invoice_html($order);
        $this->mpdf->WriteHTML($html);

        return $this->mpdf;
    }

    /**
     * Build preview markup with sample data.
     */
    public function get_preview_markup($sample_data = null): string
    {
        if (null === $sample_data) {
            $sample_data = $this->get_sample_order_data();
        }

        return $this->build_invoice_html_from_data($sample_data);
    }

    /**
     * Build preview page CSS variables for the iframe preview screen.
     */
    public function get_preview_page_stylesheet(): string
    {
        [$pageWidth, $pageHeight] = $this->get_page_dimensions_mm();

        $marginTop = max(0, (int) ($this->settings['page_margin_top'] ?? 10));
        $marginRight = max(0, (int) ($this->settings['page_margin_right'] ?? 10));
        $marginBottom = max(0, (int) ($this->settings['page_margin_bottom'] ?? 10));
        $marginLeft = max(0, (int) ($this->settings['page_margin_left'] ?? 10));

        return sprintf(
            'body.nxtpdf-preview-admin{--nxtpdf-preview-page-width:%1$smm;--nxtpdf-preview-page-height:%2$smm;--nxtpdf-preview-padding-top:%3$dmm;--nxtpdf-preview-padding-right:%4$dmm;--nxtpdf-preview-padding-bottom:%5$dmm;--nxtpdf-preview-padding-left:%6$dmm;}',
            sprintf('%.2F', $pageWidth),
            sprintf('%.2F', $pageHeight),
            $marginTop,
            $marginRight,
            $marginBottom,
            $marginLeft
        );
    }

    /**
     * Generate preview PDF with sample data
     */
    public function generate_preview_pdf($sample_data = null) {
        $this->init_mpdf();
        $this->load_document_styles();

        if (null === $sample_data) {
            $sample_data = $this->get_sample_order_data();
        }

        $html = $this->build_invoice_html_from_data($sample_data);
        $this->mpdf->WriteHTML($html);

        return $this->mpdf;
    }

    /**
     * Get sample order data for preview
     */
    private function get_sample_order_data() {
        // Try to get real order data if WooCommerce is active
        if (class_exists('WooCommerce')) {
            $orders = wc_get_orders(['limit' => 1, 'orderby' => 'date', 'order' => 'DESC']);
            if (!empty($orders)) {
                $order = $orders[0];
                $date_format = (string) $this->settings['invoice_date_format'];
                $order_date_raw = $order->get_date_created();
                return [
                    'invoice_number' => $this->format_invoice_number($order->get_order_number()),
                    'order_number' => $order->get_order_number(),
                    'order_date' => $order_date_raw ? $order_date_raw->format($date_format) : '',
                    'invoice_date' => $this->settings['invoice_date_source'] === 'invoice' ? wp_date($date_format) : ($order_date_raw ? $order_date_raw->format($date_format) : wp_date($date_format)),
                    'order_date_label' => (string) $this->settings['order_date_label'],
                    'invoice_date_label' => (string) $this->settings['invoice_date_label'],
                    'billing' => [
                        'first_name' => $order->get_billing_first_name(),
                        'last_name' => $order->get_billing_last_name(),
                        'company' => $order->get_billing_company(),
                        'address_1' => $order->get_billing_address_1(),
                        'address_2' => $order->get_billing_address_2(),
                        'city' => $order->get_billing_city(),
                        'state' => $order->get_billing_state(),
                        'postcode' => $order->get_billing_postcode(),
                        'country' => $order->get_billing_country(),
                        'email' => $order->get_billing_email(),
                        'phone' => $order->get_billing_phone(),
                    ],
                    'shipping' => [
                        'first_name' => $order->get_shipping_first_name(),
                        'last_name' => $order->get_shipping_last_name(),
                        'company' => $order->get_shipping_company(),
                        'address_1' => $order->get_shipping_address_1(),
                        'address_2' => $order->get_shipping_address_2(),
                        'city' => $order->get_shipping_city(),
                        'state' => $order->get_shipping_state(),
                        'postcode' => $order->get_shipping_postcode(),
                        'country' => $order->get_shipping_country(),
                    ],
                    'items' => array_map(function($item) {
                        $product = $item->get_product();
                        return [
                            'name' => $item->get_name(),
                            'quantity' => $item->get_quantity(),
                            'subtotal' => $item->get_subtotal(),
                            'total' => $item->get_total(),
                            'tax' => $item->get_total_tax(),
                            'discount' => $item->get_subtotal() - $item->get_total(),
                            'sku' => $product ? $product->get_sku() : '',
                            'image' => $product ? wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') : '',
                            'variations' => [],
                            'meta' => [],
                        ];
                    }, $order->get_items()),
                    'subtotal' => $order->get_subtotal(),
                    'shipping_total' => $order->get_shipping_total(),
                    'shipping_tax' => $order->get_shipping_tax(),
                    'tax_total' => $order->get_total_tax(),
                    'discount_total' => $order->get_discount_total(),
                    'total' => $order->get_total(),
                    'currency' => $order->get_currency(),
                    'payment_method' => $order->get_payment_method_title(),
                    'notes' => $order->get_customer_note(),
                    'coupons' => $this->get_order_coupons($order),
                    'fees' => $this->get_order_fees($order),
                    'tax_rates' => $this->get_order_tax_rates($order),
                ];
            }
        }

        // Return sample data
        $date_format = (string) $this->settings['invoice_date_format'];
        return [
            'invoice_number' => $this->format_invoice_number('1001'),
            'order_number' => '1001',
            'order_date' => wp_date($date_format),
            'invoice_date' => wp_date($date_format),
            'order_date_label' => (string) $this->settings['order_date_label'],
            'invoice_date_label' => (string) $this->settings['invoice_date_label'],
            'billing' => [
                'first_name' => 'John',
                'last_name' => 'Doe',
                'company' => 'Sample Company Ltd.',
                'address_1' => '123 Main Street',
                'address_2' => 'Suite 100',
                'city' => 'New York',
                'state' => 'NY',
                'postcode' => '10001',
                'country' => 'US',
                'email' => 'john.doe@example.com',
                'phone' => '+1 (555) 123-4567',
            ],
            'shipping' => [
                'first_name' => 'John',
                'last_name' => 'Doe',
                'company' => '',
                'address_1' => '123 Main Street',
                'address_2' => 'Suite 100',
                'city' => 'New York',
                'state' => 'NY',
                'postcode' => '10001',
                'country' => 'US',
            ],
            'items' => [
                [
                    'name' => 'Premium Widget',
                    'quantity' => 2,
                    'subtotal' => 99.98,
                    'total' => 89.98,
                    'tax' => 7.20,
                    'discount' => 10.00,
                    'sku' => 'PRW-001',
                    'image' => '',
                    'variations' => [['key' => 'Color', 'value' => 'Blue']],
                    'meta' => [],
                ],
                [
                    'name' => 'Standard Gadget',
                    'quantity' => 1,
                    'subtotal' => 29.99,
                    'total' => 29.99,
                    'tax' => 2.40,
                    'discount' => 0,
                    'sku' => 'STG-002',
                    'image' => '',
                    'variations' => [],
                    'meta' => [['key' => 'Gift Wrap', 'value' => 'Yes']],
                ],
                [
                    'name' => 'Deluxe Package',
                    'quantity' => 3,
                    'subtotal' => 59.97,
                    'total' => 59.97,
                    'tax' => 4.80,
                    'discount' => 0,
                    'sku' => 'DLP-003',
                    'image' => '',
                    'variations' => [['key' => 'Size', 'value' => 'Large']],
                    'meta' => [],
                ],
            ],
            'subtotal' => 189.94,
            'shipping_total' => 10.00,
            'shipping_tax' => 0.80,
            'tax_total' => 15.99,
            'discount_total' => 10.00,
            'total' => 215.93,
            'currency' => 'USD',
            'payment_method' => 'Credit Card',
            'notes' => 'Please deliver between 9 AM and 5 PM.',
            'coupons' => [
                ['code' => 'SAVE10', 'discount' => 10.00],
            ],
            'fees' => [
                ['name' => 'Handling Fee', 'total' => 5.00, 'tax' => 0.40],
            ],
            'tax_rates' => [
                ['label' => 'VAT (20%)', 'amount' => 15.99, 'is_compound' => false],
            ],
        ];
    }

    /**
     * Build invoice HTML from WooCommerce order
     */
    private function build_invoice_html($order) {
        $date_format = (string) $this->settings['invoice_date_format'];
        $order_date_raw = $order->get_date_created();
        $invoice_date_source = (string) ($this->settings['invoice_date_source'] ?? '');

        // Determine invoice date based on source setting
        if ($invoice_date_source === 'invoice') {
            $invoice_date = wp_date($date_format);
        } else {
            $invoice_date = $order_date_raw ? $order_date_raw->format($date_format) : wp_date($date_format);
        }

        $data = [
            'invoice_number' => $this->get_invoice_number($order),
            'order_number' => $order->get_order_number(),
            'order_date' => $order_date_raw ? $order_date_raw->format($date_format) : '',
            'invoice_date' => $invoice_date,
            'order_date_label' => (string) $this->settings['order_date_label'],
            'invoice_date_label' => (string) $this->settings['invoice_date_label'],
            'billing' => [
                'first_name' => $order->get_billing_first_name(),
                'last_name' => $order->get_billing_last_name(),
                'company' => $order->get_billing_company(),
                'address_1' => $order->get_billing_address_1(),
                'address_2' => $order->get_billing_address_2(),
                'city' => $order->get_billing_city(),
                'state' => $order->get_billing_state(),
                'postcode' => $order->get_billing_postcode(),
                'country' => $order->get_billing_country(),
                'email' => $order->get_billing_email(),
                'phone' => $order->get_billing_phone(),
            ],
            'shipping' => [
                'first_name' => $order->get_shipping_first_name(),
                'last_name' => $order->get_shipping_last_name(),
                'company' => $order->get_shipping_company(),
                'address_1' => $order->get_shipping_address_1(),
                'address_2' => $order->get_shipping_address_2(),
                'city' => $order->get_shipping_city(),
                'state' => $order->get_shipping_state(),
                'postcode' => $order->get_shipping_postcode(),
                'country' => $order->get_shipping_country(),
            ],
            'items' => array_map(function($item) {
                $product = $item->get_product();
                $item_data = [
                    'name' => $item->get_name(),
                    'quantity' => $item->get_quantity(),
                    'subtotal' => $item->get_subtotal(),
                    'total' => $item->get_total(),
                    'tax' => $item->get_total_tax(),
                    'discount' => $item->get_subtotal() - $item->get_total(),
                    'sku' => $product ? $product->get_sku() : '',
                    'image' => $product ? wp_get_attachment_image_url($product->get_image_id(), 'thumbnail') : '',
                    'variations' => [],
                    'meta' => [],
                ];

                // Get variation attributes
                if ($item->get_variation_id() && $product) {
                    $item_data['variations'] = $item->get_meta_data() ? array_filter(
                        array_map(function($meta) {
                            $key = $meta->key;
                            if (strpos($key, 'pa_') === 0 || strpos($key, '_') !== 0) {
                                return ['key' => wc_attribute_label($key), 'value' => $meta->value];
                            }
                            return null;
                        }, $item->get_meta_data()),
                        function($v) { return $v !== null; }
                    ) : [];
                }

                // Get item meta (non-variation)
                foreach ($item->get_meta_data() as $meta) {
                    if (strpos($meta->key, '_') !== 0 && strpos($meta->key, 'pa_') !== 0) {
                        $item_data['meta'][] = ['key' => $meta->key, 'value' => $meta->value];
                    }
                }

                return $item_data;
            }, $order->get_items()),
            'subtotal' => $order->get_subtotal(),
            'shipping_total' => $order->get_shipping_total(),
            'shipping_tax' => $order->get_shipping_tax(),
            'tax_total' => $order->get_total_tax(),
            'discount_total' => $order->get_discount_total(),
            'total' => $order->get_total(),
            'currency' => $order->get_currency(),
            'payment_method' => $order->get_payment_method_title(),
            'notes' => $order->get_customer_note(),
            'coupons' => $this->get_order_coupons($order),
            'fees' => $this->get_order_fees($order),
            'tax_rates' => $this->get_order_tax_rates($order),
        ];

        return $this->build_invoice_html_from_data($data);
    }

    /**
     * Build invoice HTML from data array
     *
     * @param array $data Order data
     * @return string     Rendered HTML
     */
    private function build_invoice_html_from_data($data) {
        $currency = $data['currency'] ?? 'USD';
        $currency_symbol = function_exists('get_woocommerce_currency_symbol')
            ? get_woocommerce_currency_symbol($currency)
            : '$';

        // Create a currency formatter closure for templates
        $format_price = function($amount) use ($currency) {
            return $this->format_price($amount, $currency);
        };

        $template = (string) ($this->settings['template_selector'] ?? '');
        $resolvedTemplate = TemplateLoader::resolveTemplateSlug($template, $this->templateScope);
        $templateHtml = TemplateLoader::view($template, [
            'settings'        => $this->settings,
            'data'            => $data,
            'currency_symbol' => $currency_symbol,
            'format_price'    => $format_price,
        ], true, $this->templateScope);

        return $this->wrap_template_document($templateHtml, $resolvedTemplate);
    }

    /**
     * Wrap the rendered template in a stable root element for scoped CSS hooks.
     */
    private function wrap_template_document(string $html, string $templateSlug): string
    {
        return sprintf(
            '<div class="%1$s">%2$s</div>',
            esc_attr(implode(' ', $this->get_template_document_classes($templateSlug))),
            $html
        );
    }

    /**
     * Build template wrapper classes for static and dynamic PDF styling hooks.
     *
     * @return array<int, string>
     */
    private function get_template_document_classes(string $templateSlug): array
    {
        $styleState = $this->get_dynamic_style_state();

        return [
            'nxtpdf-document',
            'nxtpdf-template-scope-' . sanitize_html_class($this->templateScope),
            'nxtpdf-template-' . sanitize_html_class($templateSlug),
            'nxtpdf-template-' . sanitize_html_class($this->templateScope . '-' . $templateSlug),
            'nxtpdf-header-align-' . sanitize_html_class($styleState['header_align']),
            'nxtpdf-footer-align-' . sanitize_html_class($styleState['footer_align']),
            'nxtpdf-header-size-' . (int) $styleState['header_font_size'],
            'nxtpdf-footer-size-' . (int) $styleState['footer_font_size'],
            'nxtpdf-logo-height-' . (int) $styleState['logo_height'],
            'nxtpdf-header-color-' . sanitize_html_class($styleState['header_color_token']),
            'nxtpdf-footer-color-' . sanitize_html_class($styleState['footer_color_token']),
        ];
    }

    /**
     * Resolve the current exact-value styling state.
     *
     * @return array<string, int|string>
     */
    private function get_dynamic_style_state(): array
    {
        $isPackingSlip = $this->templateScope === TemplateLoader::SCOPE_PACKING_SLIPS;
        $headerColor = $this->normalize_color_value(
            (string) ($this->settings['header_color'] ?? ''),
            $isPackingSlip ? '#1f2937' : '#333333'
        );
        $footerColor = $this->normalize_color_value(
            (string) ($this->settings['footer_color'] ?? ''),
            $isPackingSlip ? '#6b7280' : '#666666'
        );

        return [
            'header_align' => $this->normalize_alignment_value(
                (string) ($this->settings['header_align'] ?? ''),
                $isPackingSlip ? 'left' : 'center'
            ),
            'footer_align' => $this->normalize_alignment_value(
                (string) ($this->settings['footer_align'] ?? ''),
                'center'
            ),
            'header_font_size' => $this->normalize_range_value($this->settings['header_font_size'] ?? null, 10, 1, 72),
            'footer_font_size' => $this->normalize_range_value($this->settings['footer_font_size'] ?? null, 10, 1, 72),
            'logo_height' => $this->normalize_range_value($this->settings['logo_height'] ?? null, 150, 10, 300),
            'header_color' => $headerColor,
            'footer_color' => $footerColor,
            'header_color_token' => ltrim($headerColor, '#'),
            'footer_color_token' => ltrim($footerColor, '#'),
        ];
    }

    /**
     * Render the exact-value dynamic stylesheet for the current settings.
     */
    public function get_dynamic_stylesheet(): string
    {
        $styleState = $this->get_dynamic_style_state();

        return implode("\n", [
            sprintf('.nxtpdf-header-size-%1$d .nxtpdf-header-zone{font-size:%1$dpt;}', (int) $styleState['header_font_size']),
            sprintf('.nxtpdf-footer-size-%1$d .nxtpdf-footer-zone{font-size:%1$dpt;}', (int) $styleState['footer_font_size']),
            sprintf('.nxtpdf-logo-height-%1$d .nxtpdf-logo-image{height:%1$dpx;max-height:%1$dpx;}', (int) $styleState['logo_height']),
            sprintf('.nxtpdf-header-color-%1$s .nxtpdf-header-zone{color:#%1$s;}', $styleState['header_color_token']),
            sprintf('.nxtpdf-footer-color-%1$s .nxtpdf-footer-zone{color:#%1$s;}', $styleState['footer_color_token']),
        ]);
    }

    /**
     * Resolve the selected template and shared stylesheet files for PDF rendering.
     *
     * @return array<int, string>
     */
    public function get_document_stylesheet_files(): array
    {
        $template = (string) ($this->settings['template_selector'] ?? '');
        $stylesheets = [];
        $templateStylesheet = TemplateLoader::resolveTemplateStylesheet($template, $this->templateScope);

        if ($templateStylesheet !== '') {
            $stylesheets[] = $templateStylesheet;
        }

        $sharedStylesheet = trailingslashit(NXTPDF_DIR) . 'assets/css/pdf-shared.css';
        if (file_exists($sharedStylesheet)) {
            $stylesheets[] = $sharedStylesheet;
        }

        return $stylesheets;
    }

    /**
     * Normalize an alignment value to a supported token.
     */
    private function normalize_alignment_value(string $value, string $default): string
    {
        return in_array($value, ['left', 'center', 'right'], true) ? $value : $default;
    }

    /**
     * Normalize a numeric value within an allowed range.
     */
    private function normalize_range_value($value, int $default, int $min, int $max): int
    {
        $value = (int) $value;
        if ($value < $min || $value > $max) {
            return $default;
        }

        return $value;
    }

    /**
     * Normalize a color value to a safe hex token.
     */
    private function normalize_color_value(string $value, string $default): string
    {
        $value = sanitize_hex_color($value);
        if (!$value) {
            return strtolower($default);
        }

        return strtolower($value);
    }

    /**
     * Resolve the current paper size in millimeters for browser preview.
     *
     * @return array{0: float, 1: float}
     */
    private function get_page_dimensions_mm(): array
    {
        return [210.0, 297.0];
    }

    /**
     * Get order coupons with details.
     *
     * @param \WC_Order $order
     * @return array
     */
    private function get_order_coupons($order) {
        $coupons = [];
        foreach ($order->get_items('coupon') as $item) {
            $coupons[] = [
                'code' => $item->get_code(),
                'discount' => $item->get_discount(),
            ];
        }
        return $coupons;
    }

    /**
     * Get order fees.
     *
     * @param \WC_Order $order
     * @return array
     */
    private function get_order_fees($order) {
        $fees = [];
        foreach ($order->get_fees() as $fee) {
            $fees[] = [
                'name' => $fee->get_name(),
                'total' => $fee->get_total(),
                'tax' => $fee->get_total_tax(),
            ];
        }
        return $fees;
    }

    /**
     * Get order tax rates breakdown.
     *
     * @param \WC_Order $order
     * @return array
     */
    private function get_order_tax_rates($order) {
        $tax_rates = [];
        foreach ($order->get_tax_totals() as $tax) {
            $tax_rates[] = [
                'label' => $tax->label,
                'amount' => $tax->amount,
                'is_compound' => $tax->is_compound,
            ];
        }
        return $tax_rates;
    }

    /**
     * Format a price using the currency settings.
     *
     * @param float  $amount   The amount to format.
     * @param string $currency The currency code (e.g., 'USD').
     * @return string Formatted price string.
     */
    public function format_price($amount, $currency = 'USD') {
        $decimal_places = (int) $this->settings['decimal_places'];
        $decimal_sep = (string) $this->settings['decimal_separator'];
        $thousand_sep = (string) $this->settings['thousand_separator'];
        $currency_display = function_exists('get_woocommerce_currency_symbol')
            ? get_woocommerce_currency_symbol($currency)
            : '$';

        // Format the number
        $formatted_number = number_format((float) $amount, $decimal_places, $decimal_sep, $thousand_sep);

        return $currency_display . $formatted_number;
    }

    /**
     * Output PDF to browser
     */
    public function output($filename = 'invoice.pdf', $dest = 'I') {
        if ($this->mpdf) {
            $this->mpdf->Output($filename, $dest);
        }
    }

    /**
     * Get PDF as string
     */
    public function get_pdf_string() {
        if ($this->mpdf) {
            return $this->mpdf->Output('', 'S');
        }
        return '';
    }
}
