<?php

declare(strict_types=1);

namespace App\Services;

use PDO;
use Throwable;

final class DocumentTemplateService
{
    /** @return array<string, mixed> */
    public static function defaults(): array
    {
        return [
            'template_code' => 'DEFAULT',
            'name' => 'Plantilla corporativa compacta',
            'page_size' => 'A4',
            'density' => 'compact',
            'font_size' => 9.0,
            'accent_color' => '#1f7a4d',
            'header_style' => 'corporate',
            'show_logo' => 1,
            'show_watermark' => 0,
            'show_internal_notice' => 1,
            'show_signature_block' => 1,
            'show_payment_footer' => 1,
            'footer_text' => 'Documento interno emitido por CRM Agro. Los comprobantes fiscales se autorizan únicamente desde el módulo ARCA cuando corresponda.',
            'legal_note' => 'Comprobante interno no válido como factura fiscal hasta su autorización electrónica correspondiente.',
            'payment_terms' => 'Importes expresados en pesos argentinos. La referencia en dólares se calcula según la cotización registrada en el documento.',
            'print_observations' => '',
            'header_title' => 'Comprobante interno',
            'header_subtitle' => '',
            'legal_footer' => 'Comprobante interno no válido como factura fiscal hasta su autorización electrónica correspondiente.',
            'bank_footer' => 'Importes expresados en pesos argentinos. La referencia en dólares se calcula según la cotización registrada en el documento.',

            // v0.13.3 - controles avanzados de formato y distribución.
            'layout_mode' => 'standard',
            'price_footer_layout' => 'notes_left_totals_right',
            'document_width' => 'auto',
            'print_margin_mm' => 8.0,
            'page_padding_px' => 0,
            'section_padding_px' => 6.0,
            'section_gap_px' => 5.0,
            'section_radius_px' => 7.0,
            'table_cell_padding_y_px' => 3.0,
            'table_cell_padding_x_px' => 4.0,
            'logo_size_px' => 34,
            'header_box_width_px' => 190,
            'totals_box_width_px' => 260,
            'meta_columns' => 3,
            'show_section_borders' => 1,

            'font_base_px' => 9.2,
            'font_title_px' => 12.0,
            'font_header_px' => 8.6,
            'font_body_px' => 8.6,
            'font_table_px' => 8.2,
            'font_totals_px' => 9.0,
            'font_footer_px' => 7.4,

            'order_header' => 10,
            'order_meta' => 20,
            'order_items' => 30,
            'order_prices' => 40,
            'order_signature' => 50,
            'order_footer' => 60,
        ];
    }

    /** Alias estático usado por helpers. @return array<string, mixed> */
    public static function get(PDO $pdo, int $companyId): array
    {
        return (new self())->settings($pdo, $companyId);
    }

    /** @return array<string, mixed> */
    public function settings(PDO $pdo, int $companyId): array
    {
        $defaults = self::defaults();
        if ($companyId <= 0) {
            return $defaults;
        }

        try {
            if (!$this->tableExists($pdo)) {
                return $defaults;
            }

            // Repara columnas faltantes de forma silenciosa. Si falla, la pantalla puede seguir usando defaults.
            $this->ensureColumns($pdo);

            $stmt = $pdo->prepare('SELECT * FROM document_template_settings WHERE company_id = ? AND template_code = ? LIMIT 1');
            $stmt->execute([$companyId, 'DEFAULT']);
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!$row) {
                return $defaults;
            }

            return $this->normalize(array_merge($defaults, $row));
        } catch (Throwable) {
            return $defaults;
        }
    }

    /** @param array<string, mixed> $input */
    public function save(PDO $pdo, int $companyId, array $input): void
    {
        if ($companyId <= 0) {
            throw new \RuntimeException('Empresa no válida para guardar plantilla.');
        }

        $this->ensureTable($pdo);
        $data = $this->normalize(array_merge(self::defaults(), $input));
        $columns = $this->columns($pdo);

        $stmt = $pdo->prepare('SELECT id FROM document_template_settings WHERE company_id = ? AND template_code = ? LIMIT 1');
        $stmt->execute([$companyId, 'DEFAULT']);
        $exists = (bool)$stmt->fetchColumn();

        $saveKeys = $this->persistableColumns();


        // Usamos placeholders posicionales para evitar HY093 por diferencias de PDO/MySQL con named parameters.
        if ($exists) {
            $assignments = [];
            $values = [];
            foreach ($saveKeys as $key) {
                if (!isset($columns[$key])) {
                    continue;
                }
                $assignments[] = '`' . $key . '` = ?';
                $values[] = $data[$key] ?? null;
            }

            if ($assignments === []) {
                throw new \RuntimeException('No hay columnas de plantilla disponibles para actualizar. Ejecutá el instalador de plantilla.');
            }

            $values[] = $companyId;
            $values[] = 'DEFAULT';
            $sql = 'UPDATE document_template_settings SET ' . implode(', ', $assignments) . ', updated_at = CURRENT_TIMESTAMP WHERE company_id = ? AND template_code = ?';
            $pdo->prepare($sql)->execute($values);
            return;
        }

        $insertColumns = ['company_id', 'template_code'];
        $values = [$companyId, 'DEFAULT'];
        foreach ($saveKeys as $key) {
            if (!isset($columns[$key])) {
                continue;
            }
            $insertColumns[] = $key;
            $values[] = $data[$key] ?? null;
        }

        $placeholders = implode(', ', array_fill(0, count($insertColumns), '?'));
        $quotedColumns = '`' . implode('`, `', $insertColumns) . '`';
        $sql = 'INSERT INTO document_template_settings (' . $quotedColumns . ') VALUES (' . $placeholders . ')';
        $pdo->prepare($sql)->execute($values);
    }

    public function reset(PDO $pdo, int $companyId): void
    {
        if ($companyId <= 0) {
            throw new \RuntimeException('Empresa no válida para restaurar plantilla.');
        }
        $this->ensureTable($pdo);
        $stmt = $pdo->prepare('DELETE FROM document_template_settings WHERE company_id = ? AND template_code = ?');
        $stmt->execute([$companyId, 'DEFAULT']);
        $this->save($pdo, $companyId, self::defaults());
    }


    /** @return list<string> */
    public function persistableColumns(): array
    {
        return [
            'name', 'page_size', 'density', 'font_size', 'accent_color', 'header_style',
            'show_logo', 'show_watermark', 'show_internal_notice', 'show_signature_block', 'show_payment_footer',
            'footer_text', 'legal_note', 'payment_terms', 'print_observations', 'header_title', 'header_subtitle',
            'legal_footer', 'bank_footer',
            'layout_mode', 'price_footer_layout', 'document_width', 'print_margin_mm', 'page_padding_px',
            'section_padding_px', 'section_gap_px', 'section_radius_px', 'table_cell_padding_y_px', 'table_cell_padding_x_px',
            'logo_size_px', 'header_box_width_px', 'totals_box_width_px', 'meta_columns', 'show_section_borders',
            'font_base_px', 'font_title_px', 'font_header_px', 'font_body_px', 'font_table_px', 'font_totals_px', 'font_footer_px',
            'order_header', 'order_meta', 'order_items', 'order_prices', 'order_signature', 'order_footer',
        ];
    }

    public function ensureTable(PDO $pdo): void
    {
        // Tabla sin FK explícita para evitar errores por motores/privilegios en VPS productivos.
        // La integridad por empresa se controla desde la aplicación.
        $pdo->exec(
            'CREATE TABLE IF NOT EXISTS document_template_settings (
                id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                company_id BIGINT UNSIGNED NOT NULL,
                template_code VARCHAR(40) NOT NULL DEFAULT "DEFAULT",
                name VARCHAR(120) NOT NULL DEFAULT "Plantilla corporativa compacta",
                page_size VARCHAR(20) NOT NULL DEFAULT "A4",
                density VARCHAR(20) NOT NULL DEFAULT "compact",
                font_size DECIMAL(5,2) NOT NULL DEFAULT 9.00,
                accent_color VARCHAR(7) NOT NULL DEFAULT "#1f7a4d",
                header_style VARCHAR(30) NOT NULL DEFAULT "corporate",
                show_logo TINYINT(1) NOT NULL DEFAULT 1,
                show_watermark TINYINT(1) NOT NULL DEFAULT 0,
                show_internal_notice TINYINT(1) NOT NULL DEFAULT 1,
                show_signature_block TINYINT(1) NOT NULL DEFAULT 1,
                show_payment_footer TINYINT(1) NOT NULL DEFAULT 1,
                footer_text TEXT NULL,
                legal_note TEXT NULL,
                payment_terms TEXT NULL,
                print_observations TEXT NULL,
                header_title VARCHAR(160) NOT NULL DEFAULT "Comprobante interno",
                header_subtitle VARCHAR(255) NULL,
                legal_footer TEXT NULL,
                bank_footer TEXT NULL,
                layout_mode VARCHAR(20) NOT NULL DEFAULT "standard",
                price_footer_layout VARCHAR(40) NOT NULL DEFAULT "notes_left_totals_right",
                document_width VARCHAR(20) NOT NULL DEFAULT "auto",
                print_margin_mm DECIMAL(5,2) NOT NULL DEFAULT 8.00,
                page_padding_px DECIMAL(6,2) NOT NULL DEFAULT 0.00,
                section_padding_px DECIMAL(6,2) NOT NULL DEFAULT 6.00,
                section_gap_px DECIMAL(6,2) NOT NULL DEFAULT 5.00,
                section_radius_px DECIMAL(6,2) NOT NULL DEFAULT 7.00,
                table_cell_padding_y_px DECIMAL(6,2) NOT NULL DEFAULT 3.00,
                table_cell_padding_x_px DECIMAL(6,2) NOT NULL DEFAULT 4.00,
                logo_size_px SMALLINT UNSIGNED NOT NULL DEFAULT 34,
                header_box_width_px SMALLINT UNSIGNED NOT NULL DEFAULT 190,
                totals_box_width_px SMALLINT UNSIGNED NOT NULL DEFAULT 260,
                meta_columns TINYINT UNSIGNED NOT NULL DEFAULT 3,
                show_section_borders TINYINT(1) NOT NULL DEFAULT 1,
                font_base_px DECIMAL(5,2) NOT NULL DEFAULT 9.20,
                font_title_px DECIMAL(5,2) NOT NULL DEFAULT 12.00,
                font_header_px DECIMAL(5,2) NOT NULL DEFAULT 8.60,
                font_body_px DECIMAL(5,2) NOT NULL DEFAULT 8.60,
                font_table_px DECIMAL(5,2) NOT NULL DEFAULT 8.20,
                font_totals_px DECIMAL(5,2) NOT NULL DEFAULT 9.00,
                font_footer_px DECIMAL(5,2) NOT NULL DEFAULT 7.40,
                order_header SMALLINT UNSIGNED NOT NULL DEFAULT 10,
                order_meta SMALLINT UNSIGNED NOT NULL DEFAULT 20,
                order_items SMALLINT UNSIGNED NOT NULL DEFAULT 30,
                order_prices SMALLINT UNSIGNED NOT NULL DEFAULT 40,
                order_signature SMALLINT UNSIGNED NOT NULL DEFAULT 50,
                order_footer SMALLINT UNSIGNED NOT NULL DEFAULT 60,
                created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
                UNIQUE KEY uq_document_template_company_code (company_id, template_code),
                KEY idx_document_template_company (company_id)
             ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci'
        );

        $this->ensureColumns($pdo);
    }

    public function ensureColumns(PDO $pdo): void
    {
        if (!$this->tableExists($pdo)) {
            return;
        }

        $columns = $this->columns($pdo);
        $definitions = [
            'header_title' => 'ALTER TABLE document_template_settings ADD COLUMN header_title VARCHAR(160) NOT NULL DEFAULT "Comprobante interno" AFTER print_observations',
            'header_subtitle' => 'ALTER TABLE document_template_settings ADD COLUMN header_subtitle VARCHAR(255) NULL AFTER header_title',
            'legal_footer' => 'ALTER TABLE document_template_settings ADD COLUMN legal_footer TEXT NULL AFTER header_subtitle',
            'bank_footer' => 'ALTER TABLE document_template_settings ADD COLUMN bank_footer TEXT NULL AFTER legal_footer',
            'show_signature_block' => 'ALTER TABLE document_template_settings ADD COLUMN show_signature_block TINYINT(1) NOT NULL DEFAULT 1 AFTER show_internal_notice',
            'show_payment_footer' => 'ALTER TABLE document_template_settings ADD COLUMN show_payment_footer TINYINT(1) NOT NULL DEFAULT 1 AFTER show_signature_block',
            'print_observations' => 'ALTER TABLE document_template_settings ADD COLUMN print_observations TEXT NULL AFTER payment_terms',

            'layout_mode' => 'ALTER TABLE document_template_settings ADD COLUMN layout_mode VARCHAR(20) NOT NULL DEFAULT "standard" AFTER bank_footer',
            'price_footer_layout' => 'ALTER TABLE document_template_settings ADD COLUMN price_footer_layout VARCHAR(40) NOT NULL DEFAULT "notes_left_totals_right" AFTER layout_mode',
            'document_width' => 'ALTER TABLE document_template_settings ADD COLUMN document_width VARCHAR(20) NOT NULL DEFAULT "auto" AFTER price_footer_layout',
            'print_margin_mm' => 'ALTER TABLE document_template_settings ADD COLUMN print_margin_mm DECIMAL(5,2) NOT NULL DEFAULT 8.00 AFTER document_width',
            'page_padding_px' => 'ALTER TABLE document_template_settings ADD COLUMN page_padding_px DECIMAL(6,2) NOT NULL DEFAULT 0.00 AFTER print_margin_mm',
            'section_padding_px' => 'ALTER TABLE document_template_settings ADD COLUMN section_padding_px DECIMAL(6,2) NOT NULL DEFAULT 6.00 AFTER page_padding_px',
            'section_gap_px' => 'ALTER TABLE document_template_settings ADD COLUMN section_gap_px DECIMAL(6,2) NOT NULL DEFAULT 5.00 AFTER section_padding_px',
            'section_radius_px' => 'ALTER TABLE document_template_settings ADD COLUMN section_radius_px DECIMAL(6,2) NOT NULL DEFAULT 7.00 AFTER section_gap_px',
            'table_cell_padding_y_px' => 'ALTER TABLE document_template_settings ADD COLUMN table_cell_padding_y_px DECIMAL(6,2) NOT NULL DEFAULT 3.00 AFTER section_radius_px',
            'table_cell_padding_x_px' => 'ALTER TABLE document_template_settings ADD COLUMN table_cell_padding_x_px DECIMAL(6,2) NOT NULL DEFAULT 4.00 AFTER table_cell_padding_y_px',
            'logo_size_px' => 'ALTER TABLE document_template_settings ADD COLUMN logo_size_px SMALLINT UNSIGNED NOT NULL DEFAULT 34 AFTER table_cell_padding_x_px',
            'header_box_width_px' => 'ALTER TABLE document_template_settings ADD COLUMN header_box_width_px SMALLINT UNSIGNED NOT NULL DEFAULT 190 AFTER logo_size_px',
            'totals_box_width_px' => 'ALTER TABLE document_template_settings ADD COLUMN totals_box_width_px SMALLINT UNSIGNED NOT NULL DEFAULT 260 AFTER header_box_width_px',
            'meta_columns' => 'ALTER TABLE document_template_settings ADD COLUMN meta_columns TINYINT UNSIGNED NOT NULL DEFAULT 3 AFTER totals_box_width_px',
            'show_section_borders' => 'ALTER TABLE document_template_settings ADD COLUMN show_section_borders TINYINT(1) NOT NULL DEFAULT 1 AFTER meta_columns',

            'font_base_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_base_px DECIMAL(5,2) NOT NULL DEFAULT 9.20 AFTER show_section_borders',
            'font_title_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_title_px DECIMAL(5,2) NOT NULL DEFAULT 12.00 AFTER font_base_px',
            'font_header_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_header_px DECIMAL(5,2) NOT NULL DEFAULT 8.60 AFTER font_title_px',
            'font_body_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_body_px DECIMAL(5,2) NOT NULL DEFAULT 8.60 AFTER font_header_px',
            'font_table_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_table_px DECIMAL(5,2) NOT NULL DEFAULT 8.20 AFTER font_body_px',
            'font_totals_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_totals_px DECIMAL(5,2) NOT NULL DEFAULT 9.00 AFTER font_table_px',
            'font_footer_px' => 'ALTER TABLE document_template_settings ADD COLUMN font_footer_px DECIMAL(5,2) NOT NULL DEFAULT 7.40 AFTER font_totals_px',

            'order_header' => 'ALTER TABLE document_template_settings ADD COLUMN order_header SMALLINT UNSIGNED NOT NULL DEFAULT 10 AFTER font_footer_px',
            'order_meta' => 'ALTER TABLE document_template_settings ADD COLUMN order_meta SMALLINT UNSIGNED NOT NULL DEFAULT 20 AFTER order_header',
            'order_items' => 'ALTER TABLE document_template_settings ADD COLUMN order_items SMALLINT UNSIGNED NOT NULL DEFAULT 30 AFTER order_meta',
            'order_prices' => 'ALTER TABLE document_template_settings ADD COLUMN order_prices SMALLINT UNSIGNED NOT NULL DEFAULT 40 AFTER order_items',
            'order_signature' => 'ALTER TABLE document_template_settings ADD COLUMN order_signature SMALLINT UNSIGNED NOT NULL DEFAULT 50 AFTER order_prices',
            'order_footer' => 'ALTER TABLE document_template_settings ADD COLUMN order_footer SMALLINT UNSIGNED NOT NULL DEFAULT 60 AFTER order_signature',
        ];

        foreach ($definitions as $column => $sql) {
            if (!isset($columns[$column])) {
                try {
                    $pdo->exec($sql);
                } catch (Throwable) {
                    // No detenemos la pantalla; settings() usará defaults si algo falta.
                }
            }
        }
    }

    /** @return array<string, bool> */
    private function columns(PDO $pdo): array
    {
        try {
            $stmt = $pdo->query('SHOW COLUMNS FROM document_template_settings');
            $result = [];
            foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                $result[(string)$row['Field']] = true;
            }
            return $result;
        } catch (Throwable) {
            return [];
        }
    }

    /** @return array<string, mixed> */
    public function companyProfile(PDO $pdo, int $companyId, ?int $branchId = null): array
    {
        try {
            $stmt = $pdo->prepare('SELECT * FROM companies WHERE id = ? LIMIT 1');
            $stmt->execute([$companyId]);
            $company = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable) {
            $company = [];
        }

        $branch = [];
        if ($branchId !== null && $branchId > 0) {
            try {
                $branchStmt = $pdo->prepare('SELECT * FROM branches WHERE company_id = ? AND id = ? LIMIT 1');
                $branchStmt->execute([$companyId, $branchId]);
                $branch = $branchStmt->fetch(PDO::FETCH_ASSOC) ?: [];
            } catch (Throwable) {
                $branch = [];
            }
        }

        // v0.66.6: el membrete legal/comercial debe salir SIEMPRE de companies.
        // Antes address/city/province/phone/email se pisaban con datos de branches cuando el
        // comprobante tenía branch_id. Eso hacía que la vista previa de plantilla mostrara
        // los datos correctos de Empresa, pero la impresión real de ventas/remitos/recibos
        // mostrara otra información guardada en sucursales.
        //
        // Dejamos los datos de sucursal en claves separadas para mostrarlos como
        // "Sucursal / Depósito" sin contaminar el membrete de la empresa emisora.
        return [
            'business_name' => (string)($company['business_name'] ?? 'Empresa'),
            'trade_name' => (string)($company['trade_name'] ?? $company['business_name'] ?? 'CRM Agro'),
            'cuit' => (string)($company['cuit'] ?? ''),
            'iva_condition' => (string)($company['iva_condition'] ?? ''),
            'address' => (string)($company['address'] ?? ''),
            'city' => (string)($company['city'] ?? ''),
            'province' => (string)($company['province'] ?? ''),
            'country' => (string)($company['country'] ?? ''),
            'phone' => (string)($company['phone'] ?? ''),
            'email' => (string)($company['email'] ?? ''),
            'logo_path' => (string)($company['logo_path'] ?? ''),

            'branch_name' => (string)($branch['name'] ?? ''),
            'branch_address' => (string)($branch['address'] ?? ''),
            'branch_city' => (string)($branch['city'] ?? ''),
            'branch_province' => (string)($branch['province'] ?? ''),
            'branch_phone' => (string)($branch['phone'] ?? ''),
            'branch_email' => (string)($branch['email'] ?? ''),
        ];
    }

    public function tableExists(PDO $pdo): bool
    {
        try {
            $stmt = $pdo->query("SHOW TABLES LIKE 'document_template_settings'");
            return (bool)$stmt->fetchColumn();
        } catch (Throwable) {
            return false;
        }
    }

    /** @param array<string, mixed> $settings */
    public static function styleVars(array $settings): string
    {
        $settings = array_merge(self::defaults(), $settings);
        $fontSize = self::clampFloat($settings['font_size'] ?? 9.0, 8.0, 12.5);
        $base = self::clampFloat($settings['font_base_px'] ?? $fontSize, 7.0, 13.0);
        $scale = number_format($fontSize / 10.0, 2, '.', '');
        $accent = self::normalizeColor((string)($settings['accent_color'] ?? '#1f7a4d'));
        $density = (string)($settings['density'] ?? 'compact');
        $gap = match ($density) {
            'comfortable' => '0.92rem',
            'normal' => '0.72rem',
            default => '0.48rem',
        };
        $rowPadding = match ($density) {
            'comfortable' => '0.58rem 0.62rem',
            'normal' => '0.46rem 0.54rem',
            default => '0.30rem 0.42rem',
        };

        $metaColumns = self::clampInt($settings['meta_columns'] ?? 3, 1, 4);
        $sectionBorder = !empty($settings['show_section_borders']) ? '#d7dde6' : 'transparent';

        return implode('; ', [
            '--doc-primary: ' . $accent,
            '--doc-accent: ' . $accent,
            '--doc-accent-soft: ' . self::softColor($accent),
            '--doc-text: #17202a',
            '--doc-muted: #667085',
            '--doc-font-scale: ' . $scale,
            '--doc-gap: ' . $gap,
            '--doc-row-padding: ' . $rowPadding,
            '--doc-print-margin: ' . self::cssMm($settings['print_margin_mm'] ?? 8.0),
            '--doc-page-padding: ' . self::cssPx($settings['page_padding_px'] ?? 0),
            '--doc-section-padding: ' . self::cssPx($settings['section_padding_px'] ?? 6.0),
            '--doc-section-gap: ' . self::cssPx($settings['section_gap_px'] ?? 5.0),
            '--doc-section-radius: ' . self::cssPx($settings['section_radius_px'] ?? 7.0),
            '--doc-section-border-color: ' . $sectionBorder,
            '--doc-table-pad-y: ' . self::cssPx($settings['table_cell_padding_y_px'] ?? 3.0),
            '--doc-table-pad-x: ' . self::cssPx($settings['table_cell_padding_x_px'] ?? 4.0),
            '--doc-logo-size: ' . self::cssPx($settings['logo_size_px'] ?? 34),
            '--doc-header-box-width: ' . self::cssPx($settings['header_box_width_px'] ?? 190),
            '--doc-totals-width: ' . self::cssPx($settings['totals_box_width_px'] ?? 260),
            '--doc-meta-columns: ' . (string)$metaColumns,
            '--doc-font-base: ' . self::cssPx($base),
            '--doc-font-title: ' . self::cssPx($settings['font_title_px'] ?? 12.0),
            '--doc-font-header: ' . self::cssPx($settings['font_header_px'] ?? 8.6),
            '--doc-font-body: ' . self::cssPx($settings['font_body_px'] ?? 8.6),
            '--doc-font-table: ' . self::cssPx($settings['font_table_px'] ?? 8.2),
            '--doc-font-totals: ' . self::cssPx($settings['font_totals_px'] ?? 9.0),
            '--doc-font-footer: ' . self::cssPx($settings['font_footer_px'] ?? 7.4),
            '--doc-order-header: ' . self::clampInt($settings['order_header'] ?? 10, 1, 99),
            '--doc-order-meta: ' . self::clampInt($settings['order_meta'] ?? 20, 1, 99),
            '--doc-order-items: ' . self::clampInt($settings['order_items'] ?? 30, 1, 99),
            '--doc-order-prices: ' . self::clampInt($settings['order_prices'] ?? 40, 1, 99),
            '--doc-order-signature: ' . self::clampInt($settings['order_signature'] ?? 50, 1, 99),
            '--doc-order-footer: ' . self::clampInt($settings['order_footer'] ?? 60, 1, 99),
        ]);
    }

    /** @param array<string, mixed> $data @return array<string, mixed> */
    public function normalize(array $data): array
    {
        $density = in_array((string)($data['density'] ?? ''), ['compact', 'normal', 'comfortable'], true) ? (string)$data['density'] : 'compact';
        $pageSize = in_array((string)($data['page_size'] ?? ''), ['A4', 'LETTER'], true) ? (string)$data['page_size'] : 'A4';
        $headerStyle = in_array((string)($data['header_style'] ?? ''), ['corporate', 'classic', 'minimal'], true) ? (string)$data['header_style'] : 'corporate';
        $layoutMode = in_array((string)($data['layout_mode'] ?? ''), ['standard', 'custom'], true) ? (string)$data['layout_mode'] : 'standard';
        $priceFooterLayout = in_array((string)($data['price_footer_layout'] ?? ''), ['notes_left_totals_right', 'totals_left_notes_right', 'stacked'], true) ? (string)$data['price_footer_layout'] : 'notes_left_totals_right';
        $documentWidth = in_array((string)($data['document_width'] ?? ''), ['auto', 'narrow', 'wide'], true) ? (string)$data['document_width'] : 'auto';

        $fontSize = self::clampFloat($data['font_size'] ?? 9.0, 8.0, 12.5);
        $accent = self::normalizeColor((string)($data['accent_color'] ?? '#1f7a4d'));
        $legal = self::limit(trim((string)($data['legal_note'] ?? $data['legal_footer'] ?? '')), 1200);
        $payment = self::limit(trim((string)($data['payment_terms'] ?? $data['bank_footer'] ?? '')), 1200);

        return [
            'template_code' => 'DEFAULT',
            'name' => self::limit(trim((string)($data['name'] ?? 'Plantilla corporativa compacta')) ?: 'Plantilla corporativa compacta', 120),
            'page_size' => $pageSize,
            'density' => $density,
            'font_size' => round($fontSize, 2),
            'accent_color' => strtolower($accent),
            'header_style' => $headerStyle,
            'show_logo' => !empty($data['show_logo']) ? 1 : 0,
            'show_watermark' => !empty($data['show_watermark']) ? 1 : 0,
            'show_internal_notice' => !empty($data['show_internal_notice']) ? 1 : 0,
            'show_signature_block' => !empty($data['show_signature_block']) ? 1 : 0,
            'show_payment_footer' => !empty($data['show_payment_footer']) ? 1 : 0,
            'footer_text' => self::limit(trim((string)($data['footer_text'] ?? '')), 1200),
            'legal_note' => $legal,
            'payment_terms' => $payment,
            'print_observations' => self::limit(trim((string)($data['print_observations'] ?? '')), 1200),
            'header_title' => self::limit(trim((string)($data['header_title'] ?? 'Comprobante interno')) ?: 'Comprobante interno', 160),
            'header_subtitle' => self::limit(trim((string)($data['header_subtitle'] ?? '')), 255),
            'legal_footer' => $legal,
            'bank_footer' => $payment,

            'layout_mode' => $layoutMode,
            'price_footer_layout' => $priceFooterLayout,
            'document_width' => $documentWidth,
            'print_margin_mm' => round(self::clampFloat($data['print_margin_mm'] ?? 8.0, 4.0, 20.0), 2),
            'page_padding_px' => round(self::clampFloat($data['page_padding_px'] ?? 0, 0, 24), 2),
            'section_padding_px' => round(self::clampFloat($data['section_padding_px'] ?? 6.0, 2.0, 24.0), 2),
            'section_gap_px' => round(self::clampFloat($data['section_gap_px'] ?? 5.0, 0.0, 24.0), 2),
            'section_radius_px' => round(self::clampFloat($data['section_radius_px'] ?? 7.0, 0.0, 24.0), 2),
            'table_cell_padding_y_px' => round(self::clampFloat($data['table_cell_padding_y_px'] ?? 3.0, 1.0, 12.0), 2),
            'table_cell_padding_x_px' => round(self::clampFloat($data['table_cell_padding_x_px'] ?? 4.0, 1.0, 16.0), 2),
            'logo_size_px' => self::clampInt($data['logo_size_px'] ?? 34, 20, 72),
            'header_box_width_px' => self::clampInt($data['header_box_width_px'] ?? 190, 120, 360),
            'totals_box_width_px' => self::clampInt($data['totals_box_width_px'] ?? 260, 160, 460),
            'meta_columns' => self::clampInt($data['meta_columns'] ?? 3, 1, 4),
            'show_section_borders' => !empty($data['show_section_borders']) ? 1 : 0,

            'font_base_px' => round(self::clampFloat($data['font_base_px'] ?? 9.2, 7.0, 13.0), 2),
            'font_title_px' => round(self::clampFloat($data['font_title_px'] ?? 12.0, 8.0, 20.0), 2),
            'font_header_px' => round(self::clampFloat($data['font_header_px'] ?? 8.6, 6.5, 14.0), 2),
            'font_body_px' => round(self::clampFloat($data['font_body_px'] ?? 8.6, 6.5, 14.0), 2),
            'font_table_px' => round(self::clampFloat($data['font_table_px'] ?? 8.2, 6.5, 14.0), 2),
            'font_totals_px' => round(self::clampFloat($data['font_totals_px'] ?? 9.0, 7.0, 18.0), 2),
            'font_footer_px' => round(self::clampFloat($data['font_footer_px'] ?? 7.4, 6.0, 12.0), 2),

            'order_header' => 10,
            'order_meta' => $this->safeSectionOrders($data)['order_meta'],
            'order_items' => $this->safeSectionOrders($data)['order_items'],
            'order_prices' => $this->safeSectionOrders($data)['order_prices'],
            'order_signature' => $this->safeSectionOrders($data)['order_signature'],
            'order_footer' => 60,
        ];
    }

    /**
     * Mantiene el comprobante legible y evita configuraciones peligrosas.
     * Cabecera siempre arriba y pie siempre abajo; el usuario puede ordenar las secciones centrales.
     *
     * @param array<string, mixed> $data
     * @return array<string, int>
     */
    private function safeSectionOrders(array $data): array
    {
        $middle = [
            'order_meta' => self::clampInt($data['order_meta'] ?? 20, 1, 99),
            'order_items' => self::clampInt($data['order_items'] ?? 30, 1, 99),
            'order_prices' => self::clampInt($data['order_prices'] ?? 40, 1, 99),
            'order_signature' => self::clampInt($data['order_signature'] ?? 50, 1, 99),
        ];

        // Orden estable: ante valores repetidos conserva el orden profesional recomendado.
        $recommendedIndex = [
            'order_meta' => 1,
            'order_items' => 2,
            'order_prices' => 3,
            'order_signature' => 4,
        ];
        uksort($middle, static function (string $a, string $b) use ($middle, $recommendedIndex): int {
            $cmp = $middle[$a] <=> $middle[$b];
            return $cmp !== 0 ? $cmp : ($recommendedIndex[$a] <=> $recommendedIndex[$b]);
        });

        $safe = ['order_header' => 10, 'order_footer' => 60];
        $slot = 20;
        foreach (array_keys($middle) as $key) {
            $safe[$key] = $slot;
            $slot += 10;
        }
        return $safe;
    }

    private static function normalizeColor(string $color): string
    {
        return preg_match('/^#[0-9a-fA-F]{6}$/', $color) === 1 ? strtolower($color) : '#1f7a4d';
    }

    private static function softColor(string $color): string
    {
        $color = ltrim(self::normalizeColor($color), '#');
        $r = hexdec(substr($color, 0, 2));
        $g = hexdec(substr($color, 2, 2));
        $b = hexdec(substr($color, 4, 2));
        $r = (int)round($r + (255 - $r) * 0.88);
        $g = (int)round($g + (255 - $g) * 0.88);
        $b = (int)round($b + (255 - $b) * 0.88);
        return sprintf('#%02x%02x%02x', $r, $g, $b);
    }

    private static function limit(string $text, int $max): string
    {
        if (function_exists('mb_substr')) {
            return mb_substr($text, 0, $max);
        }
        return substr($text, 0, $max);
    }

    private static function clampFloat(mixed $value, float $min, float $max): float
    {
        $number = (float)str_replace(',', '.', (string)$value);
        if ($number < $min) { return $min; }
        if ($number > $max) { return $max; }
        return $number;
    }

    private static function clampInt(mixed $value, int $min, int $max): int
    {
        $number = (int)round((float)str_replace(',', '.', (string)$value));
        if ($number < $min) { return $min; }
        if ($number > $max) { return $max; }
        return $number;
    }

    private static function cssPx(mixed $value): string
    {
        $number = (float)str_replace(',', '.', (string)$value);
        return rtrim(rtrim(number_format($number, 2, '.', ''), '0'), '.') . 'px';
    }

    private static function cssMm(mixed $value): string
    {
        $number = (float)str_replace(',', '.', (string)$value);
        return rtrim(rtrim(number_format($number, 2, '.', ''), '0'), '.') . 'mm';
    }
}
