<?php

namespace Daniv04\HaciendaPackage\Services\XmlGenerator;

use DOMDocument;
use DOMElement;
use Exception;


class XmlGeneratorService
{
    private DOMDocument $dom;
    private DOMElement $invoice;


    /**
     * Generate the XML for the electronic invoice based on the provided data.
     *
     * @param array $data Invoice data
     * @return string XML as string
     */
    public function generateXml(array $data): string
    {
        $this->dom = new DOMDocument('1.0', 'UTF-8');
        $this->dom->formatOutput = false;

        try {
            $this->initRootElement($data['TipoComprobante'] ?? '01');
            $this->addBasicData($data);
            $this->addEmisorData($data['Emisor']);
            if (!empty($data['Receptor'])) {
                $this->addReceptorData($data['Receptor']);
            }
            $this->addCondicionVentaData($data);

            if (isset($data['DetalleServicio']) && !empty($data['DetalleServicio'])) {
                $this->addDetalleServicio($data['DetalleServicio']);
            }

            if (isset($data['OtrosCargos']) && !empty($data['OtrosCargos'])) {
                $this->addOtrosCargos($data['OtrosCargos']);
            }

            $this->addResumenFactura($data['ResumenFactura']);

            if (isset($data['InformacionReferencia']) && !empty($data['InformacionReferencia'])) {
                $this->addInformacionReferencia($data['InformacionReferencia']);
            }

            if (isset($data['Otros']) && !empty($data['Otros'])) {
                $this->addOtros($data['Otros']);
            }

            return $this->dom->saveXML();
        } catch (Exception $e) {
            throw new Exception("Error generating XML: " . $e->getMessage());
        }
    }

    /**
     * Add a single value element to the XML document under the specified parent.
     *
     *
     * @param DOMElement $parent Parent element to which the new element will be appended
     * @param string $name Name of the new element
     * @param mixed $value Value of the new element
     * @return DOMElement
     */
    /**
     * Maps a Hacienda TipoComprobante code to [rootElementName, xmlns].
     * Initialises $this->invoice and appends it to the DOM.
     */
    private function initRootElement(string $tipoComprobante): void
    {
        $map = [
            '01' => ['FacturaElectronica',           'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/facturaElectronica'],
            '02' => ['NotaDebitoElectronica',         'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/notaDebitoElectronica'],
            '03' => ['NotaCreditoElectronica',        'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/notaCreditoElectronica'],
            '04' => ['TiqueteElectronico',            'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/tiqueteElectronico'],
            '08' => ['FacturaElectronicaCompras',     'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/facturaElectronicaCompras'],
            '09' => ['FacturaElectronicaExportacion', 'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/facturaElectronicaExportacion'],
        ];

        [$rootName, $namespace] = $map[$tipoComprobante]
            ?? ['FacturaElectronica', 'https://cdn.comprobanteselectronicos.go.cr/xml-schemas/v4.4/facturaElectronica'];

        $this->invoice = $this->dom->createElementNS($namespace, $rootName);
        $this->dom->appendChild($this->invoice);
    }

    private function addElement(DOMElement $parent, string $name, string $value): DOMElement
    {
        $element = $this->dom->createElement($name);
        $element->nodeValue = (string) $value;
        $parent->appendChild($element);

        return $element;
    }

    private function addBasicData(array $data): void
    {
        $this->addElement($this->invoice, 'Clave', $data['Clave']);

        $this->addElement($this->invoice, 'ProveedorSistemas', $data['ProveedorSistema']);

        // Required for FE, FEE, TE — nullable in base — prohibited in REP
        if (isset($data['CodigoActividadEmisor']) && $data['CodigoActividadEmisor'] !== null && $data['CodigoActividadEmisor'] !== '') {
            $this->addElement($this->invoice, 'CodigoActividadEmisor', $data['CodigoActividadEmisor']);
        }

        // Required for FEC — nullable for FE, NC, ND — prohibited for FEE, TE, REP
        if (isset($data['CodigoActividadReceptor']) && $data['CodigoActividadReceptor'] !== null && $data['CodigoActividadReceptor'] !== '') {
            $this->addElement($this->invoice, 'CodigoActividadReceptor', $data['CodigoActividadReceptor']);
        }

        $this->addElement($this->invoice, 'NumeroConsecutivo', $data['NumeroConsecutivo']);

        $this->addElement($this->invoice, 'FechaEmision', $data['FechaEmision']);
    }

    private function addCondicionVentaData(array $data): void
    {
        $this->addElement($this->invoice, 'CondicionVenta', $data['CondicionVenta']);

        if (isset($data['CondicionVentaOtros'])) {
            $this->addElement($this->invoice, 'CondicionVentaOtros', $data['CondicionVentaOtros']);
        }
        if (isset($data['PlazoCredito'])) {
            $this->addElement($this->invoice, 'PlazoCredito', $data['PlazoCredito']);
        }
    }

    private function addEmisorData(array $data): void
    {
        $emisor = $this->dom->createElement('Emisor');
        $this->invoice->appendChild($emisor);

        $this->addElement($emisor, 'Nombre', $data['Nombre']);
        $this->addIdentificacion($emisor, $data['Identificacion']);
        if (isset($data['Registrofiscal8707']) && !empty($data['Registrofiscal8707'])) {
            $this->addElement($emisor, 'Registrofiscal8707', $data['Registrofiscal8707']);
        }
        if (isset($data['NombreComercial']) && !empty($data['NombreComercial'])) {
            $this->addElement($emisor, 'NombreComercial', $data['NombreComercial']);
        }
        // Required for FE, FEE, TE — nullable for FEC, NC, ND — prohibited for REP
        if (isset($data['Ubicacion']) && !empty($data['Ubicacion'])) {
            $this->addUbicacion($emisor, $data['Ubicacion']);
        }
        // Only for FEC when Emisor.Identificacion.Tipo = 05 (foreign issuer)
        if (isset($data['OtrasSenasExtranjero']) && !empty($data['OtrasSenasExtranjero'])) {
            $this->addElement($emisor, 'OtrasSenasExtranjero', $data['OtrasSenasExtranjero']);
        }
        if (isset($data['Telefono']) && !empty($data['Telefono'])) {
            $this->addTelefono($emisor, $data['Telefono']);
        }
        // Required for most types — nullable for FEC
        if (isset($data['CorreoElectronico']) && !empty($data['CorreoElectronico'])) {
            foreach ($data['CorreoElectronico'] as $email) {
                $this->addElement($emisor, 'CorreoElectronico', $email);
            }
        }
    }

    private function addReceptorData(array $data): void
    {
        $receptor = $this->dom->createElement('Receptor');
        $this->invoice->appendChild($receptor);

        $this->addElement($receptor, 'Nombre', $data['Nombre']);
        // Required for FE, FEE, FEC — nullable for TE, NC, ND — tipo prohibited for REP
        if (isset($data['Identificacion']) && !empty($data['Identificacion'])) {
            $this->addIdentificacion($receptor, $data['Identificacion']);
        }
        if (isset($data['Registrofiscal8707']) && !empty($data['Registrofiscal8707'])) {
            $this->addElement($receptor, 'Registrofiscal8707', $data['Registrofiscal8707']);
        }
        // Prohibited for REP
        if (isset($data['NombreComercial']) && !empty($data['NombreComercial'])) {
            $this->addElement($receptor, 'NombreComercial', $data['NombreComercial']);
        }
        // Prohibited for FEE, REP
        if (isset($data['Ubicacion']) && !empty($data['Ubicacion'])) {
            $this->addUbicacion($receptor, $data['Ubicacion']);
        }
        // Required_if Tipo=05 — prohibited for FEC, REP
        if (isset($data['OtrasSenasExtranjero']) && !empty($data['OtrasSenasExtranjero'])) {
            $this->addElement($receptor, 'OtrasSenasExtranjero', $data['OtrasSenasExtranjero']);
        }
        // Prohibited for REP
        if (isset($data['Telefono']) && !empty($data['Telefono'])) {
            $this->addTelefono($receptor, $data['Telefono']);
        }
        if (isset($data['CorreoElectronico']) && !empty($data['CorreoElectronico'])) {
            $this->addElement($receptor, 'CorreoElectronico', $data['CorreoElectronico']);
        }
    }

    private function addIdentificacion(DOMElement $parent, array $data): void
    {
        $identificacion = $this->dom->createElement('Identificacion');
        $parent->appendChild($identificacion);

        $this->addElement($identificacion, 'Tipo', $data['Tipo']);
        $this->addElement($identificacion, 'Numero', $data['Numero']);
    }

    private function addUbicacion(DOMElement $parent, array $ubicacion): void
    {
        $ubi = $this->dom->createElement('Ubicacion');
        $parent->appendChild($ubi);

        $this->addElement($ubi, 'Provincia', $ubicacion['Provincia']);

        $this->addElement($ubi, 'Canton', $ubicacion['Canton']);

        $this->addElement($ubi, 'Distrito', $ubicacion['Distrito']);

        if (isset($ubicacion['Barrio']) && !empty($ubicacion['Barrio'])) {
            $this->addElement($ubi, 'Barrio', $ubicacion['Barrio']);
        }

        $this->addElement($ubi, 'OtrasSenas', $ubicacion['OtrasSenas']);
    }

    private function addTelefono(DOMElement $parent, array $data): void
    {
        $telefono = $this->dom->createElement('Telefono');
        $parent->appendChild($telefono);

        $this->addElement($telefono, 'CodigoPais', $data['CodigoPais']);
        $this->addElement($telefono, 'NumTelefono', $data['NumTelefono']);
    }

    private function addDetalleServicio(array $data): void
    {
        $detalleServicio = $this->dom->createElement('DetalleServicio');
        $this->invoice->appendChild($detalleServicio);

        foreach ($data['LineaDetalle'] as $line) {
            $this->addLineaDetalle($detalleServicio, $line);
        }
    }

    /**
     * Add a single line item to the invoice details section.
     * @param DOMElement $parent Parent element to which the line item will be appended
     * @param array $data Line item data
     */
    private function addLineaDetalle(DOMElement $parent, array $data): void
    {
        $lineaDetalle = $this->dom->createElement('LineaDetalle');
        $parent->appendChild($lineaDetalle);

        $this->addElement($lineaDetalle, 'NumeroLinea', $data['NumeroLinea']);
        // Prohibited for FE, FEE, FEC, TE, REP — only used in export-related contexts
        if (isset($data['PartidaArancelaria']) && !empty($data['PartidaArancelaria'])) {
            $this->addElement($lineaDetalle, 'PartidaArancelaria', $data['PartidaArancelaria']);
        }
        // Required — nullable for NC, ND — prohibited for REP
        if (isset($data['CodigoCABYS']) && !empty($data['CodigoCABYS'])) {
            $this->addElement($lineaDetalle, 'CodigoCABYS', $data['CodigoCABYS']);
        }
        // Nullable — prohibited for REP
        if (isset($data['CodigoComercial']) && !empty($data['CodigoComercial'])) {
            $this->addCodigoComercial($lineaDetalle, $data['CodigoComercial']);
        }
        // Required — prohibited for REP
        if (isset($data['Cantidad']) && $data['Cantidad'] !== null && $data['Cantidad'] !== '') {
            $this->addElement($lineaDetalle, 'Cantidad', (string) $data['Cantidad']);
        }
        // Required — prohibited for REP
        if (isset($data['UnidadMedida']) && !empty($data['UnidadMedida'])) {
            $this->addElement($lineaDetalle, 'UnidadMedida', $data['UnidadMedida']);
        }
        // Nullable — prohibited for TE, REP
        if (isset($data['TipoTransaccion']) && !empty($data['TipoTransaccion'])) {
            $this->addElement($lineaDetalle, 'TipoTransaccion', $data['TipoTransaccion']);
        }
        // Nullable — prohibited for REP
        if (isset($data['UnidadMedidaComercial']) && !empty($data['UnidadMedidaComercial'])) {
            $this->addElement($lineaDetalle, 'UnidadMedidaComercial', $data['UnidadMedidaComercial']);
        }
        $this->addElement($lineaDetalle, 'Detalle', $data['Detalle']);
        // Nullable — prohibited for REP
        if (isset($data['NumeroVINoSerie']) && !empty($data['NumeroVINoSerie'])) {
            foreach ($data['NumeroVINoSerie'] as $vin) {
                $this->addElement($lineaDetalle, 'NumeroVINoSerie', $vin);
            }
        }
        // Nullable — prohibited for REP
        if (isset($data['RegistroMedicamento']) && !empty($data['RegistroMedicamento'])) {
            $this->addElement($lineaDetalle, 'RegistroMedicamento', $data['RegistroMedicamento']);
        }
        // Nullable — prohibited for REP
        if (isset($data['FormaFarmaceutica']) && !empty($data['FormaFarmaceutica'])) {
            $this->addElement($lineaDetalle, 'FormaFarmaceutica', $data['FormaFarmaceutica']);
        }
        // Sub-assortment lines (nested inside each LineaDetalle)
        if (isset($data['DetalleSurtido']) && !empty($data['DetalleSurtido'])) {
            $this->addDetalleSurtido($lineaDetalle, $data['DetalleSurtido']);
        }
        // Required — prohibited for REP
        if (isset($data['PrecioUnitario']) && $data['PrecioUnitario'] !== null && $data['PrecioUnitario'] !== '') {
            $this->addElement($lineaDetalle, 'PrecioUnitario', (string) $data['PrecioUnitario']);
        }
        if (isset($data['MontoTotal']) && $data['MontoTotal'] !== null && $data['MontoTotal'] !== '') {
            $this->addElement($lineaDetalle, 'MontoTotal', (string) $data['MontoTotal']);
        }
        // Nullable — prohibited for REP — must come BEFORE SubTotal per XSD
        if (isset($data['Descuento']) && !empty($data['Descuento'])) {
            $this->addDescuentos($lineaDetalle, $data['Descuento']);
        }
        if (isset($data['SubTotal']) && $data['SubTotal'] !== null && $data['SubTotal'] !== '') {
            $this->addElement($lineaDetalle, 'SubTotal', (string) $data['SubTotal']);
        }
        // Nullable — prohibited for FEE, FEC
        if (isset($data['IVACobradoFabrica']) && !empty($data['IVACobradoFabrica'])) {
            $this->addElement($lineaDetalle, 'IVACobradoFabrica', $data['IVACobradoFabrica']);
        }
        // Required — prohibited for FEE(09) and REP(07)
        if (isset($data['BaseImponible']) && $data['BaseImponible'] !== null && $data['BaseImponible'] !== '') {
            $this->addElement($lineaDetalle, 'BaseImponible', (string) $data['BaseImponible']);
        }
        // Prohibited in base — nullable for FEE, NC, ND
        if (isset($data['MontoExportacion']) && $data['MontoExportacion'] !== null && $data['MontoExportacion'] !== '') {
            $this->addElement($lineaDetalle, 'MontoExportacion', (string) $data['MontoExportacion']);
        }
        // Required — nullable for REP
        if (isset($data['Impuesto']) && !empty($data['Impuesto'])) {
            $this->addImpuestos($lineaDetalle, $data['Impuesto']);
        }
        // Nullable — prohibited for FEE, FEC, REP
        if (isset($data['ImpuestoAsumidoEmisorFabrica']) && $data['ImpuestoAsumidoEmisorFabrica'] !== null && $data['ImpuestoAsumidoEmisorFabrica'] !== '') {
            $this->addElement($lineaDetalle, 'ImpuestoAsumidoEmisorFabrica', (string) $data['ImpuestoAsumidoEmisorFabrica']);
        }
        // Required — prohibited for FEE(09)
        if (isset($data['ImpuestoNeto']) && $data['ImpuestoNeto'] !== null && $data['ImpuestoNeto'] !== '') {
            $this->addElement($lineaDetalle, 'ImpuestoNeto', (string) $data['ImpuestoNeto']);
        }
        $this->addElement($lineaDetalle, 'MontoTotalLinea', (string) $data['MontoTotalLinea']);
    }

    private function addCodigoComercial(DOMElement $parent, array $data): void
    {
        if (empty($data) || !is_array($data)) {
            return;
        }

        foreach ($data as $codigoComercialData) {
            $codigoComercial = $this->dom->createElement('CodigoComercial');
            $parent->appendChild($codigoComercial);

            $this->addElement($codigoComercial, 'Tipo', $codigoComercialData['Tipo']);
            $this->addElement($codigoComercial, 'Codigo', $codigoComercialData['Codigo']);
        }
    }

    private function addDescuentos(DOMElement $parent, array $data): void
    {
        foreach ($data as $descuentoData) {
            $descuento = $this->dom->createElement('Descuento');
            $parent->appendChild($descuento);

            $this->addElement($descuento, 'MontoDescuento', $descuentoData['MontoDescuento']);
            $this->addElement($descuento, 'CodigoDescuento', $descuentoData['CodigoDescuento']);
            if (isset($descuentoData['CodigoDescuentoOtro']) && !empty($descuentoData['CodigoDescuentoOtro'])) {
                $this->addElement($descuento, 'CodigoDescuentoOTRO', $descuentoData['CodigoDescuentoOtro']);
            }
            if (isset($descuentoData['NaturalezaDescuento']) && !empty($descuentoData['NaturalezaDescuento'])) {
                $this->addElement($descuento, 'NaturalezaDescuento', $descuentoData['NaturalezaDescuento']);
            }
        }
    }

    private function addImpuestos(DOMElement $parent, array $data): void
    {
        foreach ($data as $impuestoData) {
            $impuesto = $this->dom->createElement('Impuesto');
            $parent->appendChild($impuesto);

            $this->addElement($impuesto, 'Codigo', $impuestoData['Codigo']);

            // CodigoImpuestoOTRO must come before CodigoTarifaIVA per XSD ImpuestoType sequence
            if (isset($impuestoData['CodigoImpuestoOtro']) && !empty($impuestoData['CodigoImpuestoOtro'])) {
                $this->addElement($impuesto, 'CodigoImpuestoOTRO', $impuestoData['CodigoImpuestoOtro']);
            }

            if (isset($impuestoData['CodigoTarifaIVA']) && !empty($impuestoData['CodigoTarifaIVA'])) {
                $this->addElement($impuesto, 'CodigoTarifaIVA', $impuestoData['CodigoTarifaIVA']);
            }

            if (isset($impuestoData['Tarifa']) && $impuestoData['Tarifa'] !== null && $impuestoData['Tarifa'] !== '') {
                $this->addElement($impuesto, 'Tarifa', (string) $impuestoData['Tarifa']);
            }

            if (isset($impuestoData['FactorCalculoIVA']) && $impuestoData['FactorCalculoIVA'] !== null && $impuestoData['FactorCalculoIVA'] !== '') {
                $this->addElement($impuesto, 'FactorCalculoIVA', (string) $impuestoData['FactorCalculoIVA']);
            }

            if (isset($impuestoData['DatosImpuestoEspecifico']) && !empty($impuestoData['DatosImpuestoEspecifico'])) {
                $this->addImpuestoDatosImpuestoEspecifico($impuesto, $impuestoData['DatosImpuestoEspecifico']);
            }

            // Monto must come before Exoneracion per XSD ImpuestoType sequence
            $this->addElement($impuesto, 'Monto', $impuestoData['Monto']);

            if (isset($impuestoData['Exoneracion']) && !empty($impuestoData['Exoneracion'])) {
                $this->addImpuestoExoneracion($impuesto, $impuestoData['Exoneracion']);
            }
        }
    }

    private function addImpuestoDatosImpuestoEspecifico(DOMElement $parent, array $data): void
    {
        $datosImpuestoEspecifico = $this->dom->createElement('DatosImpuestoEspecifico');
        $parent->appendChild($datosImpuestoEspecifico);

        $this->addElement($datosImpuestoEspecifico, 'CantidadUnidadMedida', $data['CantidadUnidadMedida']);

        if (isset($data['Porcentaje']) && $data['Porcentaje'] !== null && $data['Porcentaje'] !== '') {
            $this->addElement($datosImpuestoEspecifico, 'Porcentaje', (string) $data['Porcentaje']);
        }

        if (isset($data['Proporcion']) && $data['Proporcion'] !== null && $data['Proporcion'] !== '') {
            $this->addElement($datosImpuestoEspecifico, 'Proporcion', (string) $data['Proporcion']);
        }

        if (isset($data['VolumenUnidadConsumo']) && $data['VolumenUnidadConsumo'] !== null && $data['VolumenUnidadConsumo'] !== '') {
            $this->addElement($datosImpuestoEspecifico, 'VolumenUnidadConsumo', (string) $data['VolumenUnidadConsumo']);
        }

        $this->addElement($datosImpuestoEspecifico, 'ImpuestoUnidad', $data['ImpuestoUnidad']);
    }

    private function addImpuestoExoneracion(DOMElement $parent, array $data): void
    {
        $exoneracion = $this->dom->createElement('Exoneracion');
        $parent->appendChild($exoneracion);

        $this->addElement($exoneracion, 'TipoDocumentoEX1', $data['TipoDocumentoEX1']);

        if (isset($data['TipoDocumentoOTRO']) && !empty($data['TipoDocumentoOTRO'])) {
            $this->addElement($exoneracion, 'TipoDocumentoOTRO', $data['TipoDocumentoOTRO']);
        }

        $this->addElement($exoneracion, 'NumeroDocumento', $data['NumeroDocumento']);

        if (isset($data['Articulo']) && $data['Articulo'] !== null && $data['Articulo'] !== '') {
            $this->addElement($exoneracion, 'Articulo', (string) $data['Articulo']);
        }

        if (isset($data['Inciso']) && $data['Inciso'] !== null && $data['Inciso'] !== '') {
            $this->addElement($exoneracion, 'Inciso', (string) $data['Inciso']);
        }

        $this->addElement($exoneracion, 'NombreInstitucion', $data['NombreInstitucion']);

        if (isset($data['NombreInstitucionOTRO']) && !empty($data['NombreInstitucionOTRO'])) {
            $this->addElement($exoneracion, 'NombreInstitucionOtros', $data['NombreInstitucionOTRO']);
        }

        $this->addElement($exoneracion, 'FechaEmisionEX', $data['FechaEmisionEX']);
        $this->addElement($exoneracion, 'TarifaExonerada', (string) $data['TarifaExonerada']);
        $this->addElement($exoneracion, 'MontoExoneracion', (string) $data['MontoExoneracion']);
    }

    private function addOtrosCargos(array $data): void
    {
        foreach ($data as $cargoData) {
            $otrosCargos = $this->dom->createElement('OtrosCargos');
            $this->invoice->appendChild($otrosCargos);

            $this->addElement($otrosCargos, 'TipoDocumentoOC', $cargoData['TipoDocumentoOC']);

            if (isset($cargoData['TipoDocumentoOTROS']) && !empty($cargoData['TipoDocumentoOTROS'])) {
                $this->addElement($otrosCargos, 'TipoDocumentoOTROS', $cargoData['TipoDocumentoOTROS']);
            }

            if (isset($cargoData['IdentificacionTercero']) && !empty($cargoData['IdentificacionTercero'])) {
                $this->addIdentificacionTercero($otrosCargos, $cargoData['IdentificacionTercero']);
            }

            if (isset($cargoData['NombreTercero']) && !empty($cargoData['NombreTercero'])) {
                $this->addElement($otrosCargos, 'NombreTercero', $cargoData['NombreTercero']);
            }

            $this->addElement($otrosCargos, 'Detalle', $cargoData['Detalle']);

            if (isset($cargoData['PorcentajeOC']) && $cargoData['PorcentajeOC'] !== null && $cargoData['PorcentajeOC'] !== '') {
                $this->addElement($otrosCargos, 'PorcentajeOC', (string) $cargoData['PorcentajeOC']);
            }

            $this->addElement($otrosCargos, 'MontoCargo', (string) $cargoData['MontoCargo']);
        }
    }

    private function addIdentificacionTercero(DOMElement $parent, array $data): void
    {
        $identificacionTercero = $this->dom->createElement('IdentificacionTercero');
        $parent->appendChild($identificacionTercero);

        $this->addElement($identificacionTercero, 'Tipo', $data['Tipo']);
        $this->addElement($identificacionTercero, 'Numero', $data['Numero']);
    }

    private function addResumenFactura(array $data): void
    {
        $resumenFactura = $this->dom->createElement('ResumenFactura');
        $this->invoice->appendChild($resumenFactura);

        $this->addCodigoTipoMoneda($resumenFactura, $data['CodigoTipoMoneda']);

        // Prohibited for REP
        if (isset($data['TotalServGravados']) && $data['TotalServGravados'] !== null) {
            $this->addElement($resumenFactura, 'TotalServGravados', (string) $data['TotalServGravados']);
        }
        if (isset($data['TotalServExentos']) && $data['TotalServExentos'] !== null) {
            $this->addElement($resumenFactura, 'TotalServExentos', (string) $data['TotalServExentos']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalServExonerado']) && $data['TotalServExonerado'] !== null) {
            $this->addElement($resumenFactura, 'TotalServExonerado', (string) $data['TotalServExonerado']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalServNoSujeto']) && $data['TotalServNoSujeto'] !== null) {
            $this->addElement($resumenFactura, 'TotalServNoSujeto', (string) $data['TotalServNoSujeto']);
        }
        // Prohibited for REP
        if (isset($data['TotalMercanciasGravadas']) && $data['TotalMercanciasGravadas'] !== null) {
            $this->addElement($resumenFactura, 'TotalMercanciasGravadas', (string) $data['TotalMercanciasGravadas']);
        }
        if (isset($data['TotalMercanciasExentas']) && $data['TotalMercanciasExentas'] !== null) {
            $this->addElement($resumenFactura, 'TotalMercanciasExentas', (string) $data['TotalMercanciasExentas']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalMercExonerada']) && $data['TotalMercExonerada'] !== null) {
            $this->addElement($resumenFactura, 'TotalMercExonerada', (string) $data['TotalMercExonerada']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalMercNoSujeta']) && $data['TotalMercNoSujeta'] !== null) {
            $this->addElement($resumenFactura, 'TotalMercNoSujeta', (string) $data['TotalMercNoSujeta']);
        }
        // Prohibited for REP
        if (isset($data['TotalGravado']) && $data['TotalGravado'] !== null) {
            $this->addElement($resumenFactura, 'TotalGravado', (string) $data['TotalGravado']);
        }
        if (isset($data['TotalExento']) && $data['TotalExento'] !== null) {
            $this->addElement($resumenFactura, 'TotalExento', (string) $data['TotalExento']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalExonerado']) && $data['TotalExonerado'] !== null) {
            $this->addElement($resumenFactura, 'TotalExonerado', (string) $data['TotalExonerado']);
        }
        // Prohibited for FEE, REP
        if (isset($data['TotalNoSujeto']) && $data['TotalNoSujeto'] !== null) {
            $this->addElement($resumenFactura, 'TotalNoSujeto', (string) $data['TotalNoSujeto']);
        }
        $this->addElement($resumenFactura, 'TotalVenta', (string) $data['TotalVenta']);
        // Prohibited for REP
        if (isset($data['TotalDescuentos']) && $data['TotalDescuentos'] !== null) {
            $this->addElement($resumenFactura, 'TotalDescuentos', (string) $data['TotalDescuentos']);
        }
        // Prohibited for REP
        if (isset($data['TotalVentaNeta']) && $data['TotalVentaNeta'] !== null) {
            $this->addElement($resumenFactura, 'TotalVentaNeta', (string) $data['TotalVentaNeta']);
        }
        // Prohibited for REP
        if (isset($data['TotalDesgloseImpuesto']) && !empty($data['TotalDesgloseImpuesto'])) {
            $this->addTotalDesgloseImpuesto($resumenFactura, $data['TotalDesgloseImpuesto']);
        }
        if (isset($data['TotalImpuesto']) && $data['TotalImpuesto'] !== null) {
            $this->addElement($resumenFactura, 'TotalImpuesto', (string) $data['TotalImpuesto']);
        }
        // Prohibited for REP
        if (isset($data['TotalImpAsumEmisorFabrica']) && $data['TotalImpAsumEmisorFabrica'] !== null) {
            $this->addElement($resumenFactura, 'TotalImpAsumEmisorFabrica', (string) $data['TotalImpAsumEmisorFabrica']);
        }
        // Prohibited for FEE, FEC, REP
        if (isset($data['TotalIVADevuelto']) && $data['TotalIVADevuelto'] !== null) {
            $this->addElement($resumenFactura, 'TotalIVADevuelto', (string) $data['TotalIVADevuelto']);
        }
        // Prohibited for REP
        if (isset($data['TotalOtrosCargos']) && $data['TotalOtrosCargos'] !== null) {
            $this->addElement($resumenFactura, 'TotalOtrosCargos', (string) $data['TotalOtrosCargos']);
        }
        // Required for REP — required_unless CondicionVenta=02,08,10 for others
        if (isset($data['MedioPago']) && !empty($data['MedioPago'])) {
            $this->addMedioPago($resumenFactura, $data['MedioPago']);
        }
        $this->addElement($resumenFactura, 'TotalComprobante', (string) $data['TotalComprobante']);
    }

    private function addCodigoTipoMoneda(DOMElement $parent, array $data): void
    {
        $codigoTipoMoneda = $this->dom->createElement('CodigoTipoMoneda');
        $parent->appendChild($codigoTipoMoneda);

        $this->addElement($codigoTipoMoneda, 'CodigoMoneda', $data['CodigoMoneda']);
        $this->addElement($codigoTipoMoneda, 'TipoCambio', (string) $data['TipoCambio']);
    }

    private function addTotalDesgloseImpuesto(DOMElement $parent, array $data): void
    {
        foreach ($data as $desgloseData) {
            $totalDesgloseImpuesto = $this->dom->createElement('TotalDesgloseImpuesto');
            $parent->appendChild($totalDesgloseImpuesto);

            $this->addElement($totalDesgloseImpuesto, 'Codigo', $desgloseData['Codigo']);

            if (isset($desgloseData['CodigoTarifaIVA']) && !empty($desgloseData['CodigoTarifaIVA'])) {
                $this->addElement($totalDesgloseImpuesto, 'CodigoTarifaIVA', $desgloseData['CodigoTarifaIVA']);
            }

            $this->addElement($totalDesgloseImpuesto, 'TotalMontoImpuesto', (string) $desgloseData['TotalMontoImpuesto']);
        }
    }

    private function addMedioPago(DOMElement $parent, array $data): void
    {
        foreach ($data as $medioPagoData) {
            $medioPago = $this->dom->createElement('MedioPago');
            $parent->appendChild($medioPago);

            $this->addElement($medioPago, 'TipoMedioPago', $medioPagoData['TipoMedioPago']);

            if (isset($medioPagoData['MedioPagoOtros']) && !empty($medioPagoData['MedioPagoOtros'])) {
                $this->addElement($medioPago, 'MedioPagoOtros', $medioPagoData['MedioPagoOtros']);
            }

            $this->addElement($medioPago, 'TotalMedioPago', (string) $medioPagoData['TotalMedioPago']);
        }
    }

    private function addInformacionReferencia(array $data): void
    {

        foreach ($data as $refData) {
            $informacionReferencia = $this->dom->createElement('InformacionReferencia');
            $this->invoice->appendChild($informacionReferencia);

            $this->addElement($informacionReferencia, 'TipoDocIR', $refData['TipoDocIR']);

            if (isset($refData['TipoDocRefOTRO']) && !empty($refData['TipoDocRefOTRO'])) {
                $this->addElement($informacionReferencia, 'TipoDocRefOTRO', $refData['TipoDocRefOTRO']);
            }

            if (isset($refData['Numero']) && !empty($refData['Numero'])) {
                $this->addElement($informacionReferencia, 'Numero', $refData['Numero']);
            }

            $this->addElement($informacionReferencia, 'FechaEmisionIR', $refData['FechaEmisionIR']);

            if (isset($refData['Codigo']) && !empty($refData['Codigo'])) {
                $this->addElement($informacionReferencia, 'Codigo', $refData['Codigo']);
            }

            if (isset($refData['CodigoReferenciaOTRO']) && !empty($refData['CodigoReferenciaOTRO'])) {
                $this->addElement($informacionReferencia, 'CodigoReferenciaOTRO', $refData['CodigoReferenciaOTRO']);
            }

            if (isset($refData['Razon']) && !empty($refData['Razon'])) {
                $this->addElement($informacionReferencia, 'Razon', $refData['Razon']);
            }
        }
    }

    private function addOtros(array $data): void
    {
        $otros = $this->dom->createElement('Otros');
        $this->invoice->appendChild($otros);

        if (isset($data['OtroTexto']) && !empty($data['OtroTexto'])) {
            $this->addOtroTexto($otros, $data['OtroTexto']);
        }

        if (isset($data['OtroContenido']) && !empty($data['OtroContenido'])) {
            $this->addOtroContenido($otros, $data['OtroContenido']);
        }
    }

    private function addOtroTexto(DOMElement $parent, array $data): void
    {
        // OtroTexto is simpleContent: text value with optional 'codigo' XML attribute (maxOccurs="unbounded")
        foreach ($data as $textoData) {
            $otroTexto = $this->dom->createElement('OtroTexto');
            $otroTexto->nodeValue = $textoData['valor'];
            if (isset($textoData['codigo']) && !empty($textoData['codigo'])) {
                $otroTexto->setAttribute('codigo', $textoData['codigo']);
            }
            $parent->appendChild($otroTexto);
        }
    }

    private function addOtroContenido(DOMElement $parent, array $data): void
    {
        // OtroContenido is simpleContent: text value with optional 'codigo' XML attribute (maxOccurs="unbounded")
        foreach ($data as $contenidoData) {
            $otroContenido = $this->dom->createElement('OtroContenido');
            $otroContenido->nodeValue = $contenidoData['valor'];
            if (isset($contenidoData['codigo']) && !empty($contenidoData['codigo'])) {
                $otroContenido->setAttribute('codigo', $contenidoData['codigo']);
            }
            $parent->appendChild($otroContenido);
        }
    }

    private function addDetalleSurtido(DOMElement $parent, array $data): void
    {
        $detalleSurtido = $this->dom->createElement('DetalleSurtido');
        $parent->appendChild($detalleSurtido);

        foreach ($data['LineaDetalleSurtido'] as $line) {
            $this->addLineaDetalleSurtido($detalleSurtido, $line);
        }
    }

    private function addLineaDetalleSurtido(DOMElement $parent, array $data): void
    {
        $linea = $this->dom->createElement('LineaDetalleSurtido');
        $parent->appendChild($linea);

        $this->addElement($linea, 'CodigoCABYSSurtido', $data['CodigoCABYSSurtido']);

        if (isset($data['CodigoComercialSurtido']) && !empty($data['CodigoComercialSurtido'])) {
            $this->addCodigoComercialSurtido($linea, $data['CodigoComercialSurtido']);
        }

        $this->addElement($linea, 'CantidadSurtido', (string) $data['CantidadSurtido']);
        $this->addElement($linea, 'UnidadMedidaSurtido', $data['UnidadMedidaSurtido']);

        if (isset($data['UnidadMedidaComercialSurtido']) && !empty($data['UnidadMedidaComercialSurtido'])) {
            $this->addElement($linea, 'UnidadMedidaComercialSurtido', $data['UnidadMedidaComercialSurtido']);
        }

        $this->addElement($linea, 'DetalleSurtido', $data['DetalleSurtido']);
        $this->addElement($linea, 'PrecioUnitarioSurtido', (string) $data['PrecioUnitarioSurtido']);
        $this->addElement($linea, 'MontoTotalSurtido', (string) $data['MontoTotalSurtido']);

        if (isset($data['DescuentoSurtido']) && !empty($data['DescuentoSurtido'])) {
            $this->addDescuentoSurtido($linea, $data['DescuentoSurtido']);
        }

        $this->addElement($linea, 'SubTotalSurtido', (string) $data['SubTotalSurtido']);

        if (isset($data['IVACobradoFabricaSurtido']) && !empty($data['IVACobradoFabricaSurtido'])) {
            $this->addElement($linea, 'IVACobradoFabricaSurtido', $data['IVACobradoFabricaSurtido']);
        }

        $this->addElement($linea, 'BaseImponibleSurtido', (string) $data['BaseImponibleSurtido']);
        $this->addImpuestoSurtido($linea, $data['ImpuestoSurtido']);
    }

    private function addCodigoComercialSurtido(DOMElement $parent, array $data): void
    {
        foreach ($data as $item) {
            $codigoComercial = $this->dom->createElement('CodigoComercialSurtido');
            $parent->appendChild($codigoComercial);

            $this->addElement($codigoComercial, 'TipoSurtido', $item['TipoSurtido']);
            $this->addElement($codigoComercial, 'CodigoSurtido', $item['CodigoSurtido']);
        }
    }

    private function addDescuentoSurtido(DOMElement $parent, array $data): void
    {
        foreach ($data as $item) {
            $descuento = $this->dom->createElement('DescuentoSurtido');
            $parent->appendChild($descuento);

            $this->addElement($descuento, 'MontoDescuentoSurtido', (string) $item['MontoDescuentoSurtido']);
            $this->addElement($descuento, 'CodigoDescuentoSurtido', $item['CodigoDescuentoSurtido']);

            if (isset($item['CodigoDescuentoOtroSurtido']) && !empty($item['CodigoDescuentoOtroSurtido'])) {
                $this->addElement($descuento, 'DescuentoSurtidoOtros', $item['CodigoDescuentoOtroSurtido']);
            }
        }
    }

    private function addImpuestoSurtido(DOMElement $parent, array $data): void
    {
        foreach ($data as $item) {
            $impuesto = $this->dom->createElement('ImpuestoSurtido');
            $parent->appendChild($impuesto);

            $this->addElement($impuesto, 'CodigoImpuestoSurtido', $item['CodigoImpuestoSurtido']);

            if (isset($item['CodigoImpuestoOTROSurtido']) && !empty($item['CodigoImpuestoOTROSurtido'])) {
                $this->addElement($impuesto, 'CodigoImpuestoOTROSurtido', $item['CodigoImpuestoOTROSurtido']);
            }

            if (isset($item['CodigoTarifaIVASurtido']) && !empty($item['CodigoTarifaIVASurtido'])) {
                $this->addElement($impuesto, 'CodigoTarifaIVASurtido', $item['CodigoTarifaIVASurtido']);
            }

            if (isset($item['TarifaSurtido']) && $item['TarifaSurtido'] !== null && $item['TarifaSurtido'] !== '') {
                $this->addElement($impuesto, 'TarifaSurtido', (string) $item['TarifaSurtido']);
            }

            if (isset($item['DatosImpuestoEspecificoSurtido']) && !empty($item['DatosImpuestoEspecificoSurtido'])) {
                $this->addDatosImpuestoEspecificoSurtido($impuesto, $item['DatosImpuestoEspecificoSurtido']);
            }

            $this->addElement($impuesto, 'MontoImpuestoSurtido', (string) $item['MontoImpuestoSurtido']);
        }
    }

    private function addDatosImpuestoEspecificoSurtido(DOMElement $parent, array $data): void
    {
        $datos = $this->dom->createElement('DatosImpuestoEspecificoSurtido');
        $parent->appendChild($datos);

        $this->addElement($datos, 'CantidadUnidadMedidaSurtido', (string) $data['CantidadUnidadMedidaSurtido']);

        if (isset($data['PorcentajeSurtido']) && $data['PorcentajeSurtido'] !== null && $data['PorcentajeSurtido'] !== '') {
            $this->addElement($datos, 'PorcentajeSurtido', (string) $data['PorcentajeSurtido']);
        }

        if (isset($data['ProporcionSurtido']) && $data['ProporcionSurtido'] !== null && $data['ProporcionSurtido'] !== '') {
            $this->addElement($datos, 'ProporcionSurtido', (string) $data['ProporcionSurtido']);
        }

        if (isset($data['VolumenUnidadConsumoSurtido']) && $data['VolumenUnidadConsumoSurtido'] !== null && $data['VolumenUnidadConsumoSurtido'] !== '') {
            $this->addElement($datos, 'VolumenUnidadConsumoSurtido', (string) $data['VolumenUnidadConsumoSurtido']);
        }

        $this->addElement($datos, 'ImpuestoUnidadSurtido', (string) $data['ImpuestoUnidadSurtido']);
    }
}
