<?php
/**
 * BuildXml.php
 *
 * @since      1.0.0
 * @package    WcElectronInvoice\Xml
 * @author     alfiopiccione <alfio.piccione@gmail.com>
 * @license    http://opensource.org/licenses/gpl-2.0.php GPL v2
 *
 * Copyright (C) 2025 POP
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

namespace WcElectronInvoice\Xml;

if (! defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

use WcElectronInvoice\Admin\Settings\OptionPage;
use WcElectronInvoice\Cache\CacheTransient;
use WcElectronInvoice\Plugin;
use WcElectronInvoice\Providers\OrderAbstract;
use WcElectronInvoice\Providers\OrderQuery;
use WcElectronInvoice\Providers\ProductsQuery;
use WcElectronInvoice\Utils\TimeZone;

/**
 * Class BuildXml
 *
 * @since  1.0.0
 * @author alfiopiccione <alfio.piccione@gmail.com>
 */
class BuildXml extends BuildQuery
{
    /**
     * Xml Data.
     *
     * @since 1.0.0
     *
     * @var array
     */
    public $xmlData = array();

    /**
     * Send Xml
     *
     * @return bool
     * @since 1.0.0
     *
     */
    public function send()
    {
        // Initialized var for check xml type: loop xml, single xml or bulk actions save
        $format   = \WcElectronInvoice\Functions\filterInput($_GET, 'format', FILTER_UNSAFE_RAW);
        $saveBulk = \WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
        // Used for maker invoices csv
        $saveBulkType = \WcElectronInvoice\Functions\filterInput($_GET, 'type', FILTER_UNSAFE_RAW);

        // If stored data use this. Store filtered order in table list
        $storedTempData = get_option('temp_order_filtered');
        if (! empty($storedTempData) && 'xml' === $format && 'true' === $saveBulk) {
            /** This part is to handle bulk downloads **/
            $xmlData = $storedTempData;
        } else {
            /** This part is to handle single xml requests **/

            // Get query
            $query = $this->xmlQuery();

            // Return if instance of the query don't in condition.
            if (! $this->typeXmlCondition($query)) {
                return false;
            }

            switch ($query) {
                // Single order
                case is_subclass_of($query, '\WcElectronInvoice\Providers\Order'):
                    $this->singleOrder($query, '\WcElectronInvoice\Providers\Order');
                    break;

                // Single order refund
                case is_subclass_of($query, '\WcElectronInvoice\Providers\OrderRefund'):
                    $this->singleOrderRefund($query, '\WcElectronInvoice\Providers\OrderRefund');
                    break;
            }

            // Get json data.
            $xmlData = $this->getXmlData();
        }

        /**
         * Filter xml data
         *
         * @since 1.0.0
         */
        $xmlData = apply_filters('wc_el_inv-xml_data_filter', $xmlData);

        // Initialized
        $elements = is_array($xmlData) ? count($xmlData) : 0;

        // Zip invoices xml archive
        if (! empty($xmlData) && 1 <= $elements && 'true' === $saveBulk) {

            // Maker invoices csv
            if ('csv' === $saveBulkType) {
                $this->invoiceCsvMaker($xmlData);
                die();
            }

            // Maker invoice xml archive
            $this->invoiceZipArchive($xmlData);
            // Single invoice xml
        } elseif (! empty($xmlData)) {
            $data = new CreateXml(new \SimpleXMLElement(
                    '<?xml version="1.0" encoding="UTF-8"?><xmlns:p:FatturaElettronica/>',
                    LIBXML_NOERROR,
                    false,
                    'p',
                    true)
            );
            $data->create($xmlData);
            die();
        } else {
            wp_safe_redirect(add_query_arg('found_order', 'no'));
            exit;
        }
    }

    /**
     * invoiceCsvMaker
     * Maker invoices CSV
     *
     * @param      $xmlData
     * @param bool $download
     *
     * @return string|void
     * @since 2.0.0
     */
    public function invoiceCsvMaker($xmlData, bool $download = true)
    {
        if ($download) {
            // @codingStandardsIgnoreLine
            $saveBulk     = \WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
            $saveBulkType = \WcElectronInvoice\Functions\filterInput($_GET, 'type', FILTER_UNSAFE_RAW);

            if (! isset($saveBulk) && 'true' !== $saveBulk && ! isset($saveBulkType) && 'csv' !== $saveBulkType) {
                return;
            }
        }

        if (! is_user_logged_in() &&
            // min role shop_manager
            ! current_user_can(OptionPage::init()->getPluginCapability())
        ) {
            return;
        }

        /**
         * Begin Maker
         */
        try {
            $csvLoop = array();
            // Csv header
            $csvHeader = array(
                esc_html__('Order', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Type', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Tax code', WC_EL_INV_TEXTDOMAIN),
                esc_html__('VAT number', WC_EL_INV_TEXTDOMAIN),
                esc_html__('E-mail (PEC) or the unique code', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Customer', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Customer type', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Company', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Address', WC_EL_INV_TEXTDOMAIN),
                esc_html__('City', WC_EL_INV_TEXTDOMAIN),
                esc_html__('State', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Country', WC_EL_INV_TEXTDOMAIN),
                esc_html__('PostCode', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Email', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Phone', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Invoice Number', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Invoice Date', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Total Taxable', WC_EL_INV_TEXTDOMAIN),
                esc_html__('Total', WC_EL_INV_TEXTDOMAIN),
            );

            /**
             * Filter csv header
             */
            $csvHeader = apply_filters('wc_el_inv-header_invoice_csv', $csvHeader);

            $i = 1;
            /**
             * Set tax rate lists
             */
            $taxLists = array();
            foreach ($xmlData as $xml) {

                $order = OrderQuery::instance()->getProviderOrder($xml->id, $xml->provider);

                \WcElectronInvoice\Functions\setCustomerLocation($order->get_user_id());

                $provider   = ! empty($xml->provider) ? $xml->provider : 'woocommerce';
                $noWCRefund = (! empty($xml->items) &&
                               empty($xml->current_refund_items) &&
                               'shop_order_refund' === $xml->order_type &&
                               apply_filters('wc_el_inv-provider_order_refund_use_order_items',
                                   'woocommerce') !== $provider
                );

                if (! empty($xml->items) && 'shop_order' === $xml->order_type ||
                    // (*) PMPro use order items for refund
                    $noWCRefund
                ) {
                    foreach ($xml->items as $item) {
                        $itemTaxes = $order->get_tax_provider()->get_rates($item['tax_class']);
                        if (! empty($itemTaxes)) {
                            $taxRate                    = reset($itemTaxes);
                            $taxLists[$taxRate['rate']] = $taxRate['rate'];
                        }
                    }
                }
                if (! empty($xml->refunded) && 'shop_order_refund' === $xml->order_type) {
                    if (property_exists($xml, 'current_refund_items') && ! empty($xml->current_refund_items)) {
                        foreach ($xml->current_refund_items as $refundItem) {
                            $productID = isset($refundItem['product_id']) ? $refundItem['product_id'] : null;
                            $id        = isset($refundItem['variation_id']) && '0' !== $refundItem['variation_id'] ? $refundItem['variation_id'] : $productID;
                            $product   = ProductsQuery::instance()->getProduct($id, $xml->provider);
                            // Set product summary
                            if (isset($refundItem['product_id'])) {
                                $itemTaxes = $order->get_tax_provider()->get_rates($product->get_tax_class());
                                if (! empty($itemTaxes)) {
                                    $taxRate                    = reset($itemTaxes);
                                    $taxLists[$taxRate['rate']] = $taxRate['rate'];
                                }
                            }
                        }
                    }
                }
            }

            /**
             * Add csv line data
             */
            foreach ($xmlData as $xml) {
                $order = OrderQuery::instance()->getProviderOrder($xml->id, $xml->provider);
                \WcElectronInvoice\Functions\setCustomerLocation($order->get_user_id());

                /**
                 * Hydrate fields that lightweight list summaries (temp_order_filtered) do not carry.
                 * Full XML data objects already have these fields; the guards make this safe for both sources.
                 */
                if (! property_exists($xml, 'billing') || ! is_array($xml->billing)) {
                    $orderData = $order->get_data();
                    $billing   = $orderData['billing'] ?? array();
                    // Refund objects carry no billing; mirror the singleOrderRefund() behaviour and read from the parent.
                    if (empty(array_filter((array)$billing)) && is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
                        $parentId    = $order->get_parent_id();
                        $parentOrder = $parentId ? OrderQuery::instance()->getProviderOrder($parentId, $xml->provider) : null;
                        if ($parentOrder) {
                            $parentData = $parentOrder->get_data();
                            $billing    = $parentData['billing'] ?? $billing;
                        }
                    }
                    $xml->billing = $billing;
                }
                if (! property_exists($xml, 'sdi_type')) {
                    $xml->sdi_type = $order->get_sdi_type();
                }
                if (! property_exists($xml, 'tax_code')) {
                    $xml->tax_code = $order->get_billing_tax_code();
                }
                if (! property_exists($xml, 'vat_number')) {
                    $xml->vat_number = $order->get_vat_number();
                }

                /**
                 * Set total taxable
                 */
                $totalTaxable = abs($xml->total - $xml->total_tax);
                $totalTaxable = number_format($totalTaxable, 2, '.', '');
                $total        = number_format(abs($xml->total), 2, '.', '');

                /**
                 * Set invoice date
                 */
                $date     = isset($xml->date_created) && '' !== $xml->date_created ? $xml->date_created : $xml->date_completed;
                $timeZone = new TimeZone();
                $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
                $date     = is_numeric($date) ? new \DateTime('@' . (int)$date) : new \DateTime($date);
                $date->setTimezone($timeZone);
                $dateTime = $date->format('Y-m-d H:i');

                /**
                 * Set Invoice number
                 */
                $options = OptionPage::init();
                $number  = $order->get_order_number_invoice();
                $reverseCharge = $order->get_nature_rc();

                // Numeration type
                $numerationType = $order->get_billing_choice_type() ?: 'invoice';
                if (is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
                    $number = $order->get_meta("refund_number_invoice-{$order->get_id()}");
                    if ('receipt' === $numerationType) {
                        $numerationType = 'credit_note_receipt';
                    } else {
                        $numerationType = 'credit_note';
                    }
                }

                $invNumber = $order->get_meta("{$order->get_type()}_formatted_number_{$order->get_id()}") ?? null;
                $invNumber = apply_filters('wc_el_inv-normalize_formatted_number', $invNumber, $order);
                if (! $invNumber) {
                    $resolvedNumber = \WcElectronInvoice\Functions\getStoredInvoiceNumberValue($order);
                    if ($resolvedNumber < 1) {
                        $resolvedNumber = \WcElectronInvoice\Functions\recoverOrderInvoiceNumberFromFormatted($order,
                            $numerationType);
                    }

                    $invNumber = \WcElectronInvoice\Functions\buildFormattedInvoiceNumberValue($order, $numerationType,
                        $resolvedNumber);
                }

                /**
                 * Set invoice summary
                 */
                $summaryRate = array();
                // Order
                if (! empty($xml->items) && 'shop_order' === $xml->order_type) {
                    foreach ($xml->items as $item) {
                        /**
                         * Add item summary date
                         */
                        $itemTaxes = $order->get_tax_provider()->get_rates($item['tax_class']);
                        if (! empty($itemTaxes)) {
                            $taxRate = reset($itemTaxes);

                            // Zero rate if total tax is zero
                            if ($reverseCharge && 0.00 === floatval($item['total_tax'])) {
                                $rate = 0;
                            } else {
                                $rate = $taxRate['rate'];
                            }

                            $summaryRate[$rate][]       = array(
                                'total'     => number_format(abs($item['total']), 2, '.', ''),
                                'total_tax' => number_format(abs($item['total_tax']), 2, '.', ''),
                            );
                            $taxLists[$taxRate['rate']] = $taxRate['rate'];
                        }
                    }
                }

                /**
                 * Shipping
                 */
                if (! empty($xml->items_shipping) && 'shop_order' === $xml->order_type) {

                    $orderForShipping = OrderQuery::instance()->getProviderOrder($xml->id, $xml->provider);
                    $totalShipping    = floatval($orderForShipping->get_shipping_total());
                    $totalShippingTax = floatval($orderForShipping->get_shipping_tax());
                    $taxRate          = null;
                    foreach ($orderForShipping->get_items('shipping') as $itemID => $item) {
                        $taxRates = $orderForShipping->get_tax_provider()->get_rates($item->get_tax_class());
                        if (empty($taxRates)) {
                            $taxRates = $orderForShipping->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $taxRate                    = reset($taxRates);
                            $taxLists[$taxRate['rate']] = $taxRate['rate'];
                        }
                    }

                    if (isset($xml->items_shipping[1]) && is_array($xml->items_shipping[1])) {
                        $dataShippingPartialRefund = $xml->items_shipping[1]['refund_shipping'];
                        $totalShipping             = number_format($totalShipping - $dataShippingPartialRefund['total'],
                            2, '.', '');
                        $totalShippingTax          = number_format($totalShippingTax - $dataShippingPartialRefund['total_tax'],
                            2, '.', '');
                    }

                    // Zero rate if total tax is zero
                    if ($reverseCharge && 0.00 === floatval($totalShippingTax)) {
                        $rate = 0;
                    } else {
                        $rate = $taxRate['rate'];
                    }

                    $summaryRate[$rate][] = array(
                        'total'     => number_format(abs($totalShipping), 2, ',', ''),
                        'total_tax' => number_format($totalShippingTax, 2, '.', ''),
                    );
                }

                /**
                 * Fee
                 */
                if (! empty($xml->items_fee) && 'shop_order' === $xml->order_type) {
                    $itemFeeTotal = $itemFeeTotalTax = $refundFee = $rate = null;
                    $itemsFees    = $xml->items_fee;
                    foreach ($itemsFees as $key => $itemFees) {
                        if (! isset($itemFees['refund_fee'])) {
                            $taxRates = $order->get_tax_provider()->get_rates($itemFees['tax_class']);
                            if (empty($taxRates)) {
                                $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                            }
                            if (empty($taxRates)) {
                                $taxRates = array(0);
                            }

                            if (! empty($taxRates)) {
                                $taxRate         = reset($taxRates);
                                $rate            = $taxRate['rate'];
                                $taxLists[$rate] = $rate;
                            }

                            $itemFeeTotal    = isset($itemsFees[$key]['total']) ? $itemsFees[$key]['total'] : null;
                            $itemFeeTotalTax = isset($itemsFees[$key]['total_tax']) ? $itemsFees[$key]['total_tax'] : null;
                        }
                        $refundFee = isset($itemFees['refund_fee']) ? $itemFees['refund_fee'] : null;
                    }

                    // Refund fee - recalculate fee total and total tax
                    if (! empty($refundFee)) {
                        // Sanitize empty value in array
                        array_walk($refundFee, function (&$v) {
                            $v = ($v === '' || $v === null) ? 0 : (float)$v;
                        });
                        $itemFeeTotal    = abs($itemFeeTotal) - abs($refundFee['total']);
                        $itemFeeTotalTax = abs($itemFeeTotalTax) - abs($refundFee['total_tax']);
                        $total           = number_format(abs($total) - abs(($refundFee['total'] + abs($refundFee['total_tax']))),
                            2, '.', '');
                        $totalTaxable    = number_format(abs($totalTaxable) - abs($refundFee['total']), 2, '.', '');
                    }

                    // Zero rate if total tax is zero
                    if ($reverseCharge && 0.00 === floatval($itemFeeTotalTax)) {
                        $rate = 0;
                    }

                    $summaryRate[$rate][] = array(
                        'total'     => number_format(abs($itemFeeTotal), 2, '.', ''),
                        'total_tax' => number_format(abs($itemFeeTotalTax), 2, '.', ''),
                    );
                }

                /**
                 * Refund
                 */
                if (! empty($xml->refunded) && 'shop_order_refund' === $xml->order_type) {
                    if (property_exists($xml, 'current_refund_items') && ! empty($xml->current_refund_items)) {
                        foreach ($xml->current_refund_items as $refundItem) {
                            $productID = isset($refundItem['product_id']) ? $refundItem['product_id'] : null;
                            $id        = isset($refundItem['variation_id']) && '0' !== $refundItem['variation_id'] ? $refundItem['variation_id'] : $productID;
                            $product   = wc_get_product($id);

                            $refundItemTotal    = isset($refundItem['total']) ? $refundItem['total'] : 0;
                            $refundItemTotalTax = isset($refundItem['total_tax']) ? $refundItem['total_tax'] : 0;

                            // Set product summary
                            if (isset($refundItem['product_id'])) {
                                $itemTaxes = $order->get_tax_provider()->get_rates($product->get_tax_class());
                                if (! empty($itemTaxes)) {
                                    $taxRate = reset($itemTaxes);

                                    // Zero rate if total tax is zero
                                    if ($reverseCharge && 0.00 === floatval($refundItemTotalTax)) {
                                        $rate = 0;
                                    } else {
                                        $rate = $taxRate['rate'];
                                    }

                                    $summaryRate[$rate][]       = array(
                                        'total'     => number_format(abs($refundItem['total']), 2, '.', ''),
                                        'total_tax' => number_format(abs($refundItem['total_tax']), 2, '.', ''),
                                    );
                                    $taxLists[$taxRate['rate']] = $taxRate['rate'];
                                }
                            }

                            // Shipping
                            // Recalculate totals
                            if (isset($xml->items_shipping[1]) && is_array($xml->items_shipping[1])) {
                                $refundShipping     = $xml->items_shipping[1];
                                $refundItemTotal    = $refundShipping['refund_shipping']['total'];
                                $refundItemTotalTax = $refundShipping['refund_shipping']['total_tax'];
                                if (0 === abs($refundItemTotalTax)) {
                                    $refundItemTotalTax = $refundItem['total_tax'] - $refundShipping['refund_shipping']['total_tax'];
                                }
                            }

                            // Shipping
                            if (isset($refundItem['method_id']) && '' !== $refundItem['method_id']) {
                                $taxRate  = array();
                                $taxRates = $order->get_tax_provider()->get_shipping_tax_rates();
                                if (empty($taxRates)) {
                                    $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                                }
                                if (! empty($taxRates)) {
                                    $taxRate                    = reset($taxRates);
                                    $taxLists[$taxRate['rate']] = $taxRate['rate'];
                                }

                                // Zero rate if total tax is zero
                                if ($reverseCharge && 0.00 === floatval($refundItemTotalTax)) {
                                    $rate = 0;
                                } else {
                                    $rate = $taxRate['rate'];
                                }

                                $summaryRate[$rate][] = array(
                                    'total'     => abs($refundItemTotal),
                                    'total_tax' => abs($refundItemTotalTax),
                                );
                            }

                            // Fee
                            if (isset($refundItem['refund_type']) && 'fee' === $refundItem['refund_type']) {
                                $taxLists[$refundItem['tax_rate']] = $refundItem['tax_rate'];

                                // Zero rate if total tax is zero
                                if ($reverseCharge && 0.00 === floatval($refundItem['total_tax'])) {
                                    $rate = 0;
                                } else {
                                    $rate = $refundItem['tax_rate'];
                                }

                                $summaryRate[$rate][] = array(
                                    'total'     => abs($refundItem['total']),
                                    'total_tax' => abs($refundItem['total_tax']),
                                );
                            }
                        }
                    }
                }

                /**
                 * Set order type value
                 */
                switch ($xml->order_type) {
                    case 'shop_order':
                        if ('receipt' === $xml->choice_type) {
                            $orderType = esc_html__('Receipt', WC_EL_INV_TEXTDOMAIN);
                        } else {
                            $orderType = esc_html__('Invoice', WC_EL_INV_TEXTDOMAIN);
                        }
                        break;
                    case 'shop_order_refund':
                        $orderType = esc_html__('Credit note', WC_EL_INV_TEXTDOMAIN);
                        break;
                    default:
                        $orderType = '';
                        break;
                }

                /**
                 * Set csv line data
                 */
                $fieldsInvoiceTypeDefault = array(
                    'private'   => esc_html__('Private', WC_EL_INV_TEXTDOMAIN),
                    'company'   => esc_html__('Company', WC_EL_INV_TEXTDOMAIN),
                    'freelance' => esc_html__('Freelance', WC_EL_INV_TEXTDOMAIN),
                );
                // Set customer type
                $customerType = $fieldsInvoiceTypeDefault[$xml->invoice_type] ?? '';
                // Set vat number
                $vat_number = 'company' === $xml->invoice_type || 'freelance' === $xml->invoice_type ? $xml->vat_number : '';
                // Set SDI value
                $sdi = "" !== $xml->sdi_type ? $xml->sdi_type : '0000000';
                // Set float number
                $totalTaxable = floatval($totalTaxable);
                $total        = floatval($total);

                $dataCsv = array(
                    "{$xml->id}",
                    "{$orderType}",
                    "{$xml->tax_code}",
                    "{$vat_number}",
                    "{$sdi}",
                    "{$xml->billing['first_name']} {$xml->billing['last_name']}",
                    "{$customerType}",
                    "{$xml->billing['company']}",
                    "{$xml->billing['address_1']}",
                    "{$xml->billing['city']}",
                    "{$xml->billing['state']}",
                    "{$xml->billing['country']}",
                    "{$xml->billing['postcode']}",
                    "{$xml->billing['email']}",
                    "{$xml->billing['phone']}",
                    "{$invNumber}",
                    "{$dateTime}",
                    "{$totalTaxable}",
                    "{$total}",
                );

                /**
                 * Filter csv data
                 *
                 * @param     $dataCsv array The dataCsv
                 * @param int $order   ->get_id() The order id
                 */
                $dataCsv = apply_filters('wc_el_inv-data_line_invoice_csv', $dataCsv, $order->get_id());

                /**
                 * Extend CSV data
                 * Add summary data by tax rate
                 */
                if (! empty($taxLists)) {
                    ksort($taxLists);
                    $taxLists = array_reverse($taxLists, true);
                    foreach ($taxLists as $taxRate) {
                        if (isset($summaryRate[$taxRate])) {
                            $tax = $total = 0;
                            foreach ($summaryRate[$taxRate] as $item) {
                                $tax   += abs((float)$item['total_tax']);
                                $total += abs((float)$item['total']);
                            }
                        } else {
                            $tax   = abs(0);
                            $total = abs(0);
                        }

                        // Set float number
                        $total = floatval($total);
                        $tax   = floatval($tax);

                        $dataCsv[] = "{$total}";
                        $dataCsv[] = "{$tax}";
                    }
                }

                /**
                 * Set line CSV
                 */
                $csvLoop[$i] = implode('|', $dataCsv);
                $i++;
            }

            /**
             * Extend CSV Header
             * Add summary header by tax rate
             */
            ksort($taxLists);
            $taxLists = array_reverse($taxLists, true);
            if (! empty($taxLists)) {
                foreach ($taxLists as $taxRate) {
                    $csvHeader[] = sprintf('%1$s %2$s%3$s',
                        esc_html__('Total', WC_EL_INV_TEXTDOMAIN),
                        $taxRate,
                        "%"
                    );
                    $csvHeader[] = sprintf('%1$s %2$s%3$s',
                        esc_html__('Tax', WC_EL_INV_TEXTDOMAIN),
                        $taxRate,
                        "%"
                    );
                }
            }

            $csvLoop[0] = implode('|', $csvHeader);
            ksort($csvLoop);

            /**
             * Maker csv
             */
            $csvFileName      = apply_filters('wc_el_inv-orders_csv_filename', "orders");
            $uploadDir        = wp_upload_dir();
            $uploadsCsvPopDir = apply_filters('wc_el_inv-uploads_csv_directory',
                trailingslashit($uploadDir['basedir']) . 'pop-csv', $uploadDir, $download);
            if (! current_user_can(apply_filters('wc_el_inv-options-capability', Plugin::$capability))) {
                if ($download) {
                    wp_die(esc_html__('You do not have permission to perform this action.', WC_EL_INV_TEXTDOMAIN),
                        'error');
                } else {
                    error_log(esc_html__('You do not have permission to perform this action.', WC_EL_INV_TEXTDOMAIN));
                }
            }
            if (! file_exists($uploadsCsvPopDir)) {
                if (! wp_mkdir_p($uploadsCsvPopDir)) {
                    if ($download) {
                        wp_die(esc_html__('Unable to create the directory to save the CSV file.', WC_EL_INV_TEXTDOMAIN),
                            'error');
                    } else {
                        error_log(esc_html__('Unable to create the directory to save the CSV file.',
                            WC_EL_INV_TEXTDOMAIN));
                    }
                }
            }
            if (! is_writable($uploadsCsvPopDir)) {
                if (! @chmod($uploadsCsvPopDir, 0755)) {
                    if ($download) {
                        wp_die(esc_html__('The directory is not writable, and permissions could not be modified. Please check manually.',
                            WC_EL_INV_TEXTDOMAIN), 'error');
                    } else {
                        error_log(esc_html__('The directory is not writable, and permissions could not be modified. Please check manually.',
                            WC_EL_INV_TEXTDOMAIN));
                    }
                }
                if (! is_writable($uploadsCsvPopDir)) {
                    if ($download) {
                        wp_die(esc_html__('The directory is still not writable after attempting to change permissions.',
                            WC_EL_INV_TEXTDOMAIN), 'error');
                    } else {
                        error_log(esc_html__('The directory is still not writable after attempting to change permissions.',
                            WC_EL_INV_TEXTDOMAIN));
                    }
                }
            }

            $todayDate = date('Ymd');
            $filePath  = apply_filters('wc_el_inv-uploads_csv_file_path',
                trailingslashit($uploadsCsvPopDir) . $csvFileName . '_' . $todayDate . '.csv',
                $uploadsCsvPopDir, $csvFileName
            );
            $fileMode  = file_exists($filePath) ? 'a' : 'w';
            $fileMode  = apply_filters('wc_el_inv-uploads_csv_file_mode', $fileMode, $filePath);

            if ($fileMode === 'a') {
                function normalizeCsvString($data)
                {
                    $normalized = preg_replace('/\r\n|\r/', "\n", trim($data));
                    $normalized = preg_replace('/"([^"]+)"/', '$1', $normalized);

                    return preg_replace('/\s+/', ' ', $normalized);
                }

                // Calculate hash of the new data
                $newDataString = '';
                foreach ($csvLoop as $line) {
                    $newDataString .= implode(',', explode('|', $line)) . "\n";
                }
                $normalizedNewData = normalizeCsvString($newDataString);
                $newDataHash       = md5($normalizedNewData);

                // Calculate hash of the existing data
                $existingHash = '';
                if (file_exists($filePath)) {
                    $existingContent           = file_get_contents($filePath);
                    $normalizedExistingContent = normalizeCsvString($existingContent);
                    $existingHash              = md5($normalizedExistingContent);
                }

                // Skip writing if data is identical
                if ($newDataHash === $existingHash) {
                    $fileUrl = str_replace($uploadDir['basedir'], $uploadDir['baseurl'], $filePath);
                    $message = sprintf('%s <a href="%s" target="_blank">%s</a>',
                        esc_html__('No new data to append. File is up-to-date. You can download the existing file',
                            WC_EL_INV_TEXTDOMAIN),
                        esc_url($fileUrl),
                        esc_html__('here', WC_EL_INV_TEXTDOMAIN),
                    );
                    if ($download) {
                        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
                        wp_die($message, 'error');
                    } else {
                        $message = sprintf(
                            esc_html__('No new data to append. File is up-to-date. You can download the existing file %s.',
                                WC_EL_INV_TEXTDOMAIN),
                            esc_url($fileUrl)
                        );
                        error_log($message);
                    }
                }
            }

            $fp = fopen($filePath, $fileMode);
            if (! $fp) {
                if ($download) {
                    wp_die(esc_html__('Error creating the CSV file.', WC_EL_INV_TEXTDOMAIN), 'error');
                } else {
                    error_log(esc_html__('Error creating the CSV file.', WC_EL_INV_TEXTDOMAIN));
                }
            }

            foreach ($csvLoop as $line) {
                $val = explode('|', $line);
                fputcsv($fp, $val);
            }
            fclose($fp);

            if (! file_exists($filePath)) {
                if ($download) {
                    wp_die(esc_html__('Error: The CSV file was not saved.', WC_EL_INV_TEXTDOMAIN), 'error');
                } else {
                    error_log(esc_html__('Error: The CSV file was not saved.', WC_EL_INV_TEXTDOMAIN));
                }
            }

            if ($download) {
                header('Content-Type: application/excel');
                header('Content-Disposition: attachment; filename="' . basename($filePath) . '"');
                header('Pragma: no-cache');
                header('Expires: 0');
                readfile($filePath);
            }

            return $filePath;
        } catch (\Exception $e) {
            print_r(
                esc_html__('Error while creating CSV: ', WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage())
            );
            die();
        }
    }

    /**
     * Create invoice ZIP
     *
     * @param $xmlData
     *
     * @since 1.1.0
     *
     */
    private function invoiceZipArchive($xmlData)
    {
        // @codingStandardsIgnoreLine
        $saveBulk       = \WcElectronInvoice\Functions\filterInput($_GET, 'all', FILTER_UNSAFE_RAW);
        $storeForLogged = \WcElectronInvoice\Functions\filterInput($_GET, 'noLogged', FILTER_VALIDATE_BOOLEAN);
        if (! isset($saveBulk) && 'true' !== $saveBulk) {
            return;
        }

        if (empty($storeForLogged)) {
            if (! is_user_logged_in() &&
                ! current_user_can(apply_filters('wc_el_inv-options-capability', Plugin::$capability)) &&
                ! current_user_can('manage_network')
            ) {
                return;
            }
        }

        try {
            if (is_array($xmlData) && ! empty($xmlData)) {
                // Initialized
                $xmlLoop  = array();
                $elements = count($xmlData);
                $files    = array();

                foreach ($xmlData as $xml) {
                    // Build full order data; temp_order_filtered stores lightweight summaries only.
                    $xmlProvider     = isset($xml->provider) ? $xml->provider : (isset($xml->order_provider) ? $xml->order_provider : 'woocommerce');
                    $xmlDocType      = isset($xml->document_type) ? $xml->document_type : (isset($xml->order_type) ? $xml->order_type : 'shop_order');
                    $xmlParentId     = isset($xml->parent_id) ? (int)$xml->parent_id : 0;
                    $xmlOrder        = \WcElectronInvoice\Functions\resolveProviderDocument((int)$xml->id, $xmlProvider, $xmlDocType, $xmlParentId);

                    if (! $xmlOrder) {
                        $elements--;
                        continue;
                    }

                    $xmlProviderIface = 'shop_order_refund' === $xmlDocType
                        ? '\WcElectronInvoice\Providers\OrderRefund'
                        : '\WcElectronInvoice\Providers\Order';

                    $xmlBuilder = new self();
                    $xmlBuilder->singleOrderXmlData($xmlOrder, $xmlProviderIface);
                    $xmlLoop    = $xmlBuilder->getXmlData();

                    if (empty($xmlLoop)) {
                        $elements--;
                        continue;
                    }

                    $data    = new CreateXml(new \SimpleXMLElement(
                            '<?xml version="1.0" encoding="UTF-8"?><xmlns:p:FatturaElettronica/>',
                            LIBXML_NOERROR,
                            false,
                            'p',
                            true)
                    );
                    $file    = $data->create($xmlLoop);
                    $files[] = $file;

                    // reset xml index
                    $xmlLoop = array();
                    $elements--;

                    if (0 === $elements) {
                        $pathInfo    = pathinfo(reset($files));
                        $zipFileName = 'invoice_zip';
                        if (! empty($pathInfo)) {
                            $zipFileName = $pathInfo['filename'];
                        }

                        $tempZipDir = Plugin::getPluginDirPath('/') . "/tempZip/";
                        if (! is_dir($tempZipDir)) {
                            wp_mkdir_p(trailingslashit($tempZipDir));
                            if (! is_writable($tempZipDir)) {
                                @chmod($tempZipDir, 0755);
                                if (! is_writable($tempZipDir)) {
                                    wp_die(esc_html__('The directory is not writable even after attempting to change permissions. Please check server settings.',
                                            WC_EL_INV_TEXTDOMAIN) . ' DIR: ' . $tempZipDir);
                                }
                            }
                        }

                        $zipCreated = CreateXml::createZip($files,
                            Plugin::getPluginDirPath('/') . "/tempZip/{$zipFileName}.zip");
                        if ($zipCreated && ($storeForLogged || strpos(wp_get_referer(),
                                    'admin.php?page=wc_el_inv-options-page'))) {
                            wp_redirect(Plugin::getPluginDirUrl('/') . "/tempZip/{$zipFileName}.zip");
                        } else {
                            wp_safe_redirect(wp_get_referer() . "&created_zip=error");
                        }

                        return;
                    }
                }
            }
        } catch (\Exception $e) {
            print_r(
                esc_html__(
                    'Error while creating ZipArchive: ', WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage())
            );
            die();
        }
    }

    /**
     * Get Xml Data
     *
     * @return array
     * @since 1.0.0
     *
     */
    public function getXmlData()
    {
        return $this->xmlData;
    }

    /**
     * Set Xml Data
     *
     * @param \stdClass $data
     * @param string    $type
     *
     * @since 1.0.0
     *
     */
    public function setXmlData(\stdClass $data, string $type = '')
    {
        if (! $data instanceof \stdClass) {
            $this->xmlData = array();
        }

        if (! empty($data)) {
            $this->xmlData[] = $data;
        }

        if ('' !== $type) {
            $cache       = new CacheTransient();
            $currentData = $cache->get($type);
            $data        = is_array($currentData) ? array_merge($this->xmlData, $currentData) : $this->xmlData;
            $json        = maybe_serialize($data);

            $cache->set($json, $type);
        }
    }

    /**
     * Orders Loop
     *
     * @param OrderQuery $query
     *
     * @return void
     * @throws \Exception
     * @since 1.0.0
     *
     */
    private function ordersLoop(OrderQuery $query)
    {
        $orders = $query->getProvidersOrders();
        foreach ($orders as $order) {
            if (method_exists($order, 'get_refunds')) {
                $refunded = $order->get_refunds();
                if (! empty($refunded)) {
                    $orders = array_merge($orders, $refunded);
                }
            }
        }

        if (! empty($orders)) {
            foreach ($orders as $order) {
                switch ($order) {
                    // Shop Order
                    case is_subclass_of($order, '\WcElectronInvoice\Providers\Order'):
                        $this->getDataOrder($order);
                        break;
                    // Order Refunded
                    case is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund'):
                        $this->getDataRefundOrder($order);
                        break;
                    default:
                        break;
                }
            }
        }
    }

    /**
     * Data Order
     *
     * @param OrderAbstract $order
     *
     * @return array
     * @since 1.0.0
     *
     */
    public function getDataOrder($order)
    {
        // Customer ID
        $customerID = $order->get_user_id();
        \WcElectronInvoice\Functions\setCustomerLocation($customerID);

        // Initialize Orders data and type.
        $orderType   = $order->get_type();
        $orderData   = $order->get_data();
        $invoiceMeta = array(
            'vat_number'   => $order->get_vat_number(),
            'tax_code'     => $order->get_billing_tax_code(),
            'invoice_type' => $order->get_invoice_type(),
            'sdi_type'     => $order->get_sdi_type(),
            'choice_type'  => $order->get_billing_choice_type(),
        );

        $refundedData = array(
            'remaining_amount'        => $order->get_remaining_refund_amount(),
            'remaining_items'         => $order->get_remaining_refund_items(),
            'total_qty_refunded'      => abs($order->get_total_qty_refunded()),
            'total_refunded'          => $order->get_total_refunded(),
            'refunded_payment_method' => $order->get_refund_payment_method(),
        );

        // Initialize Order Items
        $orderItems      = $order->get_items();
        $orderItemsTaxes = $order->get_items('tax');
        $orderItemsShip  = $order->get_items('shipping');
        $orderItemsFee   = $order->get_items('fee');
        $itemsDataTax    = array();
        $itemsDataShip   = array();
        $itemsDataFee    = array();
        $itemsData       = array();
        $refundedItem    = array();

        if (! empty($orderItems)) {
            foreach ($orderItems as $item) {
                if (is_subclass_of($item, '\WcElectronInvoice\Providers\OrderItemAbstract')) {
                    $varID   = $item->get_variation_id();
                    $id      = isset($varID) && 0 !== $varID ? $varID : $item->get_product_id();
                    $product = $item->get_product();
                    $sku     = null;
                    if (is_object($product)) {
                        $sku = $product->get_sku();
                    }

                    $itemsData[] = array_merge(
                        $item->get_data(),
                        isset($sku) ? array('sku' => $sku) : array()
                    );

                    if (0 !== $order->get_qty_refunded_for_item($item->get_id())) {
                        $refundedItem[] = array(
                            'product_id'            => $item->get_product_id(),
                            'name'                  => $item->get_name(),
                            'total_price'           => $item->get_total(),
                            'total_tax'             => $item->get_total_tax(),
                            'qty_refunded_for_item' => abs($order->get_qty_refunded_for_item($item->get_id())),
                        );
                    }
                }
            }
        }

        // Tax
        if (! empty($orderItemsTaxes)) {
            foreach ($orderItemsTaxes as $itemID => $itemTax) {
                $itemsDataTax[] = $itemTax->get_data();
                $itemsDataTax   = array_filter($itemsDataTax);
            }
        }

        // Shipping
        if (! empty($orderItemsShip)) {
            foreach ($orderItemsShip as $itemID => $itemShip) {
                $dataShip   = $itemShip->get_data();
                $refundShip = $refundShipTax = 0;
                foreach ($dataShip as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $order->get_tax_provider()->get_rates($itemShip->get_tax_class());
                        if (empty($taxRates)) {
                            $order->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id            = array_keys($taxRates);
                            $refundShip    = $order->get_total_refunded_for_item($data, 'shipping');
                            $refundShipTax = $order->get_tax_refunded_for_item($data, $id[0], 'shipping');
                        }
                    }
                }

                $itemsDataShip[] = $dataShip;
                if (0 !== $refundShip || 0 !== $refundShipTax) {
                    $itemsDataShip[]['refund_shipping'] = array(
                        'total'     => $refundShip,
                        'total_tax' => $refundShipTax,
                    );
                }
                $itemsDataShip = array_filter($itemsDataShip);
            }
        }

        // Fee
        if (! empty($orderItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee     = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];
                $feeTaxClass = method_exists($itemFee,
                    'get_tax_class') ? $itemFee->get_tax_class() : $dataFee['tax_class'];

                $refundFee = $refundFeeTax = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $order->get_tax_provider()->get_rates($feeTaxClass);
                        if (empty($taxRates)) {
                            $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id           = array_keys($taxRates);
                            $refundFee    = $order->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $order->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }
                $itemsDataFee[] = $dataFee;
                if (0 !== $refundFee || 0 !== $refundFeeTax) {
                    $itemsDataFee[]['refund_fee'] = array(
                        'total'     => $refundFee,
                        'total_tax' => $refundFeeTax,
                    );
                }
                $itemsDataFee = array_filter($itemsDataFee);
            }
        }

        $filePath = '/inc/ordersJsonArgs.php';
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);

        $this->setXmlData($data);
    }

    /**
     * Data Refund Order
     *
     * @param OrderAbstract $order
     *
     * @return array
     * @since 1.0.0
     *
     */
    public function getDataRefundOrder($order)
    {
        if ('shop_order_refund' !== $order->get_type()) {
            return array();
        }

        $parentOrder = OrderQuery::instance()->getProviderOrder($order->get_parent_id(), $order->get_provider());
        if (! $parentOrder) {
            return array();
        }

        // Customer ID
        $customerID = $order->get_user_id();
        \WcElectronInvoice\Functions\setCustomerLocation($customerID);

        // Initialize Orders data and type.
        $orderType   = $order->get_type();
        $orderData   = $order->get_data();
        $invoiceMeta = array(
            'vat_number'   => $order->get_vat_number(),
            'tax_code'     => $order->get_billing_tax_code(),
            'invoice_type' => $order->get_invoice_type(),
            'sdi_type'     => $order->get_sdi_type(),
            'choice_type'  => $order->get_billing_choice_type(),
        );
        // Parent billing data.
        $billingParentData = array(
            'first_name' => $parentOrder->get_billing_first_name(),
            'last_name'  => $parentOrder->get_billing_last_name(),
            'company'    => $parentOrder->get_billing_company(),
            'address_1'  => $parentOrder->get_billing_address_1(),
            'address_2'  => $parentOrder->get_billing_address_2(),
            'city'       => $parentOrder->get_billing_city(),
            'state'      => $parentOrder->get_billing_state(),
            'postcode'   => $parentOrder->get_billing_postcode(),
            'country'    => $parentOrder->get_billing_country(),
            'email'      => $parentOrder->get_billing_email(),
            'phone'      => $parentOrder->get_billing_phone(),
        );

        $refundedData = array(
            'remaining_amount'        => $parentOrder->get_remaining_refund_amount(),
            'remaining_items'         => $parentOrder->get_remaining_refund_items(),
            'total_qty_refunded'      => $parentOrder->get_total_qty_refunded(),
            'total_refunded'          => $parentOrder->get_total_refunded(),
            'refunded_payment_method' => $parentOrder->get_refund_payment_method(),
        );

        // Initialize Order Items
        $orderItems                = $parentOrder->get_items();
        $orderItemsTaxes           = $parentOrder->get_items('tax');
        $orderItemsShipping        = $parentOrder->get_items('shipping');
        $orderItemsFee             = $parentOrder->get_items('fee');
        $itemsRefundedDataTax      = array();
        $itemsRefundedDataFee      = array();
        $itemsRefundedDataShipping = array();
        $itemsRefundedData         = array();
        $refundedItem              = array();

        // Current order refund item data
        // Use the refund entity already passed to this method.
        $refundOrder      = $order;
        $refundOrderItems = $refundOrder->get_items();
        // Items refunded
        $refundItemsShipping = $refundOrder->get_items('shipping');
        $refundItemsFee      = $refundOrder->get_items('fee');
        $currentRefund       = array();
        if (! empty($refundOrderItems)) {
            foreach ($refundOrderItems as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    'provider'     => $order->get_provider(),
                    'order_id'     => $order->get_parent_id(),
                    'refund_id'    => $data['order_id'],
                    'name'         => $data['name'],
                    'product_id'   => $data['product_id'],
                    'variation_id' => $data['variation_id'],
                    'quantity'     => $data['quantity'],
                    'subtotal'     => $data['subtotal'],
                    'subtotal_tax' => $data['subtotal_tax'],
                    'total'        => $data['total'],
                    'total_tax'    => $data['total_tax'],
                );
            }
        }
        // Refund Shipping
        if (! empty($refundItemsShipping) && false !== strpos($order->get_shipping_total(), '-')) {
            foreach ($orderItemsShipping as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    'provider'     => $order->get_provider(),
                    'order_id'     => $order->get_parent_id(),
                    'refund_id'    => $order->get_id(),
                    'refund_type'  => 'shipping',
                    'name'         => $data['name'],
                    'method_title' => $data['method_title'],
                    'method_id'    => $data['method_id'],
                    'instance_id'  => $data['instance_id'],
                    'total'        => $data['total'],
                    'total_tax'    => $data['total_tax'],
                );
            }
        }

        // Refund Fee
        if (! empty($refundItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee   = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];
                $refundFee = $refundFeeTax = $rate = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $order->get_tax_provider()->get_rates($dataFee['tax_class']);
                        if (empty($taxRates)) {
                            $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $taxRate      = reset($taxRates);
                            $rate         = $taxRate['rate'];
                            $id           = array_keys($taxRates);
                            $refundFee    = $parentOrder->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }
                $currentRefund[] = array(
                    'provider'    => $order->get_provider(),
                    'order_id'    => $order->get_parent_id(),
                    'refund_id'   => $order->get_id(),
                    'refund_type' => 'fee',
                    'name'        => trim($dataFee['name']),
                    'tax_rate'    => $rate,
                    'total'       => $refundFee,
                    'total_tax'   => $refundFeeTax,
                );
            }
        }

        // Orders
        if (! empty($orderItems)) {
            foreach ($orderItems as $item) {
                if (is_subclass_of($item, 'WcElectronInvoice\Providers\OrderItemAbstract')) {
                    $varID   = $item->get_variation_id();
                    $id      = isset($varID) && 0 !== $varID ? $varID : $item->get_product_id();
                    $product = $item->get_product();
                    $sku     = null;
                    if (is_object($product)) {
                        $sku = $product->get_sku();
                    }

                    $itemsRefundedData[] = array_merge(
                        $item->get_data(),
                        isset($sku) ? array('sku' => $sku) : array()
                    );

                    if (0 !== $parentOrder->get_qty_refunded_for_item($item->get_id())) {
                        $refundedItem[] = array(
                            'product_id'            => $item->get_product_id(),
                            'name'                  => $item->get_name(),
                            'total_price'           => $item->get_total(),
                            'total_tax'             => $item->get_total_tax(),
                            'qty_refunded_for_item' => abs($parentOrder->get_qty_refunded_for_item($item->get_id())),
                        );
                    }
                }
            }
        }

        // Tax
        if (! empty($orderItemsTaxes)) {
            foreach ($orderItemsTaxes as $itemID => $itemTax) {
                $itemsRefundedDataTax[] = $itemTax->get_data();
                $itemsRefundedDataTax   = array_filter($itemsRefundedDataTax);
            }
        }

        // Shipping
        if (! empty($orderItemsShipping)) {
            foreach ($orderItemsShipping as $itemID => $itemShip) {
                $dataShip   = $itemShip->get_data();
                $refundShip = $refundShipTax = 0;
                foreach ($dataShip as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $parentOrder->get_tax_provider()->get_rates($itemShip->get_tax_class());
                        if (empty($taxRates)) {
                            $taxRates = $parentOrder->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id            = array_keys($taxRates);
                            $refundShip    = $parentOrder->get_total_refunded_for_item($data, 'shipping');
                            $refundShipTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'shipping');
                        }
                    }
                }
                $itemsRefundedDataShipping[] = $dataShip;
                if (0 !== $refundShip || 0 !== $refundShipTax) {
                    $itemsRefundedDataShipping[]['refund_shipping'] = array(
                        'total'     => $refundShip,
                        'total_tax' => $refundShipTax,
                    );
                }
                $itemsRefundedDataShipping = array_filter($itemsRefundedDataShipping);
            }
        }

        // Fee
        if (! empty($orderItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee   = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];
                $refundFee = $refundFeeTax = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $parentOrder->get_tax_provider()->get_rates($dataFee['tax_class']);
                        if (empty($taxRates)) {
                            $taxRates = $parentOrder->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id           = array_keys($taxRates);
                            $refundFee    = $parentOrder->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }
                $itemsRefundedDataFee[] = $dataFee;
                if (0 !== $refundFee || 0 !== $refundFeeTax) {
                    $itemsRefundedDataFee[]['refund_fee'] = array(
                        'name'      => $dataFee['name'],
                        'total'     => $refundFee,
                        'total_tax' => $refundFeeTax,
                    );
                }
                $itemsRefundedDataFee = array_filter($itemsRefundedDataFee);
            }
        }

        $filePath = '/inc/ordersRefundedJsonArgs.php';
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);

        $this->setXmlData($data);
    }

    /**
     * Single Order
     *
     * @param OrderAbstract $query
     * @param               $orderProvider
     *
     * @return array
     * @since 1.0.0
     *
     */
    private function singleOrder($query, $orderProvider)
    {
        if (! is_subclass_of($query, $orderProvider)) {
            return array();
        }

        // Set order var
        $order = $query;

        // Customer ID
        $customerID = $query->get_user_id();
        \WcElectronInvoice\Functions\setCustomerLocation($customerID);

        // Initialize Orders data and type.
        $orderType   = $query->get_type();
        $orderData   = $query->get_data();
        $invoiceMeta = array(
            'vat_number'   => $query->get_vat_number(),
            'tax_code'     => $query->get_billing_tax_code(),
            'invoice_type' => $query->get_invoice_type(),
            'sdi_type'     => $query->get_sdi_type(),
            'choice_type'  => $query->get_billing_choice_type(),
        );

        $refundedData = array(
            'remaining_amount'        => $query->get_remaining_refund_amount(),
            'remaining_items'         => $query->get_remaining_refund_items(),
            'total_qty_refunded'      => $query->get_total_qty_refunded(),
            'total_refunded'          => $query->get_total_refunded(),
            'refunded_payment_method' => $query->get_refund_payment_method(),
        );

        // Initialize Order Items
        $orderItems         = $query->get_items();
        $orderItemsTaxes    = $query->get_items('tax');
        $orderItemsShipping = $query->get_items('shipping');
        $orderItemsFee      = $query->get_items('fee');
        $itemsDataTax       = array();
        $itemsDataShip      = array();
        $itemsDataFee       = array();
        $itemsData          = array();
        $refundedItem       = array();

        if (! empty($orderItems)) {
            foreach ($orderItems as $item) {
                if (is_subclass_of($item, '\WcElectronInvoice\Providers\OrderItemAbstract')) {

                    $varID   = $item->get_variation_id();
                    $id      = isset($varID) && 0 !== $varID ? $varID : $item->get_product_id();
                    $product = $item->get_product();
                    $sku     = null;
                    if (is_object($product)) {
                        $sku = $product->get_sku();
                    }

                    $itemsData[] = array_merge(
                        $item->get_data(),
                        isset($sku) ? array('sku' => $sku) : array()
                    );

                    if (0 !== $query->get_qty_refunded_for_item($item->get_id())) {
                        $refundedItem[] = array(
                            'product_id'            => $item->get_product_id(),
                            'name'                  => $item->get_name(),
                            'total_price'           => $item->get_total(),
                            'total_tax'             => $item->get_total_tax(),
                            'qty_refunded_for_item' => $query->get_qty_refunded_for_item($item->get_id()),
                        );
                    }
                }
            }
        }

        // Tax
        if (! empty($orderItemsTaxes)) {
            foreach ($orderItemsTaxes as $itemID => $itemTax) {
                $itemsDataTax[] = $itemTax->get_data();
                $itemsDataTax   = array_filter($itemsDataTax);
            }
        }

        // Shipping
        if (! empty($orderItemsShipping)) {
            foreach ($orderItemsShipping as $itemID => $itemShip) {
                $dataShip   = $itemShip->get_data();
                $refundShip = $refundShipTax = 0;
                foreach ($dataShip as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $query->get_tax_provider()->get_rates($itemShip->get_tax_class());
                        if (empty($taxRates)) {
                            $taxRates = $query->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id            = array_keys($taxRates);
                            $refundShip    = $query->get_total_refunded_for_item($data, 'shipping');
                            $refundShipTax = $query->get_tax_refunded_for_item($data, $id[0], 'shipping');
                        }
                    }
                }

                $itemsDataShip[] = $dataShip;
                if (0 !== $refundShip || 0 !== $refundShipTax) {
                    $itemsDataShip[]['refund_shipping'] = array(
                        'total'     => $refundShip,
                        'total_tax' => $refundShipTax,
                    );
                }
                $itemsDataShip = array_filter($itemsDataShip);
            }
        }

        // Fee
        if (! empty($orderItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee   = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];
                $refundFee = $refundFeeTax = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        // support for Add Custom Fee to WooCommerce
                        // @see https://it.wordpress.org/plugins/woo-add-custom-fee/
                        $wacfTaxClass = get_option('wacf_tax_class');
                        if (! $dataFee['tax_class'] && $wacfTaxClass && '' !== $wacfTaxClass) {
                            $dataFee['tax_class'] = sanitize_title_for_query(get_option('wacf_tax_class'));
                        }
                        /**
                         * Filter fee tax class
                         */
                        $feeTaxClass = apply_filters('wc_el_inv-tax-class-order-fee', $dataFee['tax_class'], $dataFee);

                        $taxRates = $query->get_tax_provider()->get_rates($feeTaxClass);
                        if (empty($taxRates)) {
                            $taxRates = $query->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id           = array_keys($taxRates);
                            $refundFee    = $query->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $query->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }
                $itemsDataFee[] = $dataFee;
                if (0 !== $refundFee || 0 !== $refundFeeTax) {
                    $itemsDataFee[]['refund_fee'] = array(
                        'total'     => $refundFee,
                        'total_tax' => $refundFeeTax,
                    );
                }
                $itemsDataFee = array_filter($itemsDataFee);
            }
        }

        $filePath = '/inc/ordersJsonArgs.php';
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);
        // var_dump( $data ); exit;

        $this->setXmlData($data);
    }

    /**
     * singleOrderXmlData
     *
     * @param $query
     * @param $orderProvider
     *
     * @return array
     */
    public function singleOrderXmlData($query, $orderProvider)
    {
        if ('shop_order_refund' === $query->get_type()) {
            return $this->singleOrderRefund($query, $orderProvider);
        }

        return $this->singleOrder($query, $orderProvider);
    }

    /**
     * Single Order
     *
     * @param $query
     * @param $orderRefundProvider
     *
     * @return array
     * @since 1.0.0
     *
     */
    private function singleOrderRefund($query, $orderRefundProvider)
    {
        if (! is_subclass_of($query, $orderRefundProvider)) {
            return array();
        }

        $parentOrder = OrderQuery::instance()->getProviderOrder($query->get_parent_id(), $query->get_provider());
        if (! $parentOrder) {
            return array();
        }

        // Customer ID
        $customerID = $query->get_user_id();
        \WcElectronInvoice\Functions\setCustomerLocation($customerID);

        // Initialize Orders data and type.
        $orderType   = $query->get_type();
        $orderData   = $query->get_data();
        $invoiceMeta = array(
            'vat_number'   => $parentOrder->get_vat_number(),
            'tax_code'     => $parentOrder->get_billing_tax_code(),
            'invoice_type' => $parentOrder->get_invoice_type(),
            'sdi_type'     => $parentOrder->get_sdi_type(),
            'choice_type'  => $parentOrder->get_billing_choice_type(),
        );
        // Parent billing data.
        $billingParentData = array(
            'first_name' => $parentOrder->get_billing_first_name(),
            'last_name'  => $parentOrder->get_billing_last_name(),
            'company'    => $parentOrder->get_billing_company(),
            'address_1'  => $parentOrder->get_billing_address_1(),
            'address_2'  => $parentOrder->get_billing_address_2(),
            'city'       => $parentOrder->get_billing_city(),
            'state'      => $parentOrder->get_billing_state(),
            'postcode'   => $parentOrder->get_billing_postcode(),
            'country'    => $parentOrder->get_billing_country(),
            'email'      => $parentOrder->get_billing_email(),
            'phone'      => $parentOrder->get_billing_phone(),
        );

        $refundedData = array(
            'remaining_amount'        => $parentOrder->get_remaining_refund_amount(),
            'remaining_items'         => $parentOrder->get_remaining_refund_items(),
            'total_qty_refunded'      => abs($parentOrder->get_total_qty_refunded()),
            'total_refunded'          => $parentOrder->get_total_refunded(),
            'refunded_payment_method' => $parentOrder->get_refund_payment_method(),
        );

        // Initialize Order Items
        $orderItems                = $parentOrder->get_items();
        $orderItemsTaxes           = $parentOrder->get_items('tax');
        $orderItemsShipping        = $parentOrder->get_items('shipping');
        $orderItemsFee             = $parentOrder->get_items('fee');
        $itemsRefundedDataTax      = array();
        $itemsRefundedDataFee      = array();
        $itemsRefundedDataShipping = array();
        $itemsRefundedData         = array();
        $refundedItem              = array();

        // Current order refund item data
        // Use the refund entity already passed to this method.
        $refundOrder      = $query;
        $refundOrderItems = $refundOrder->get_items();
        // Items refunded
        $refundItemsShipping = $refundOrder->get_items('shipping');
        $refundItemsFee      = $refundOrder->get_items('fee');
        $currentRefund       = array();
        if (! empty($refundOrderItems)) {
            foreach ($refundOrderItems as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    'provider'     => $query->get_provider(),
                    'order_id'     => $query->get_parent_id(),
                    'refund_id'    => $data['order_id'],
                    'name'         => $data['name'],
                    'product_id'   => $data['product_id'],
                    'variation_id' => $data['variation_id'],
                    'quantity'     => $data['quantity'],
                    'subtotal'     => $data['subtotal'],
                    'subtotal_tax' => $data['subtotal_tax'],
                    'total'        => $data['total'],
                    'total_tax'    => $data['total_tax'],
                );
            }
        }
        // Shipping
        if (! empty($refundItemsShipping) && false !== strpos($query->get_shipping_total(), '-')) {
            foreach ($orderItemsShipping as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    'provider'     => $query->get_provider(),
                    'order_id'     => $query->get_parent_id(),
                    'refund_id'    => $query->get_id(),
                    'refund_type'  => 'shipping',
                    'name'         => $data['name'],
                    'method_title' => $data['method_title'],
                    'method_id'    => $data['method_id'],
                    'instance_id'  => $data['instance_id'],
                    'total'        => $data['total'],
                    'total_tax'    => $data['total_tax'],
                );
            }
        }
        // Fee
        if (! empty($refundItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];

                $refundFee = $refundFeeTax = $rate = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        // support for Add Custom Fee to WooCommerce
                        // @see https://it.wordpress.org/plugins/woo-add-custom-fee/
                        $wacfTaxClass = get_option('wacf_tax_class');
                        if (! $dataFee['tax_class'] && $wacfTaxClass && '' !== $wacfTaxClass) {
                            $dataFee['tax_class'] = sanitize_title_for_query(get_option('wacf_tax_class'));
                        }

                        /**
                         * Filter fee tax class
                         */
                        $feeTaxClass = apply_filters('wc_el_inv-tax-class-order-fee', $dataFee['tax_class'], $dataFee);

                        $taxRates = $parentOrder->get_tax_provider()->get_rates($feeTaxClass);
                        $taxRates = \WC_Tax::get_rates($feeTaxClass);
                        if (empty($taxRates)) {
                            $taxRates = $refundOrder->get_tax_provider()->get_rates_for_tax_class($feeTaxClass);
                            $taxRates = reset($taxRates);

                            if (empty($taxRates) && ! is_object($taxRates)) {
                                $taxRates = $refundOrder->get_tax_provider()->get_base_tax_rates();
                            }
                        }

                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $taxRate      = is_array($taxRates) ? reset($taxRates) : $taxRates;
                            $rate         = is_array($taxRates) ? $taxRate['rate'] : $taxRates->tax_rate;
                            $id           = is_array($taxRates) ? array_keys($taxRates) : $taxRates->tax_rate_id;
                            $refundFee    = $parentOrder->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }

                $currentRefund[] = array(
                    'provider'    => $query->get_provider(),
                    'order_id'    => $query->get_parent_id(),
                    'refund_id'   => $query->get_id(),
                    'refund_type' => 'fee',
                    'name'        => trim($dataFee['name']),
                    'tax_rate'    => $rate,
                    'total'       => $refundFee,
                    'total_tax'   => $refundFeeTax,
                );
            }
        }

        if (! empty($orderItems)) {
            foreach ($orderItems as $item) {
                if (is_subclass_of($item, '\WcElectronInvoice\Providers\OrderItemAbstract')) {
                    $varID   = $item->get_variation_id();
                    $id      = isset($varID) && 0 !== $varID ? $varID : $item->get_product_id();
                    $product = $item->get_product();
                    $sku     = null;
                    if (is_object($product)) {
                        $sku = $product->get_sku();
                    }

                    $itemsRefundedData[] = array_merge(
                        $item->get_data(),
                        isset($sku) ? array('sku' => $sku) : array()
                    );

                    if (0 !== $parentOrder->get_qty_refunded_for_item($item->get_id())) {
                        $refundedItem[] = array(
                            'product_id'            => $item->get_product_id(),
                            'name'                  => $item->get_name(),
                            'total_price'           => $item->get_total(),
                            'total_tax'             => $item->get_total_tax(),
                            'qty_refunded_for_item' => $parentOrder->get_qty_refunded_for_item($item->get_id()),
                        );
                    }
                }
            }
        }

        // Tax
        if (! empty($orderItemsTaxes)) {
            foreach ($orderItemsTaxes as $itemID => $itemTax) {
                $itemsRefundedDataTax[] = $itemTax->get_data();
                $itemsRefundedDataTax   = array_filter($itemsRefundedDataTax);
            }
        }

        // Shipping
        if (! empty($orderItemsShipping)) {
            foreach ($orderItemsShipping as $itemID => $itemShip) {
                $dataShip   = $itemShip->get_data();
                $refundShip = $refundShipTax = 0;
                foreach ($dataShip as $key => $data) {
                    if ('id' === $key) {
                        $taxRates = $parentOrder->get_tax_provider()->get_rates($itemShip->get_tax_class());
                        if (empty($taxRates)) {
                            $taxRates = $parentOrder->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id            = array_keys($taxRates);
                            $refundShip    = $parentOrder->get_total_refunded_for_item($data, 'shipping');
                            $refundShipTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'shipping');
                        }
                    }
                }
                $itemsRefundedDataShipping[] = $dataShip;
                if (0 !== $refundShip || 0 !== $refundShipTax) {
                    $itemsRefundedDataShipping[]['refund_shipping'] = array(
                        'total'     => $refundShip,
                        'total_tax' => $refundShipTax,
                    );
                }
                $itemsRefundedDataShipping = array_filter($itemsRefundedDataShipping);
            }
        }

        // Fee
        if (! empty($orderItemsFee)) {
            foreach ($orderItemsFee as $itemID => $itemFee) {
                $dataFee = method_exists($itemFee, 'get_data')
                    ? $itemFee->get_data()
                    : [
                        'id'        => $itemFee->id ?? '',
                        'name'      => $itemFee->name ?? '',
                        'total'     => $itemFee->total ?? 0,
                        'total_tax' => $itemFee->total_tax ?? 0,
                        'tax_class' => $itemFee->tax_class ?? '',
                    ];

                $refundFee = $refundFeeTax = 0;
                foreach ($dataFee as $key => $data) {
                    if ('id' === $key) {
                        // support for Add Custom Fee to WooCommerce
                        // @see https://it.wordpress.org/plugins/woo-add-custom-fee/
                        $wacfTaxClass = get_option('wacf_tax_class');
                        if (! $dataFee['tax_class'] && $wacfTaxClass && '' !== $wacfTaxClass) {
                            $dataFee['tax_class'] = sanitize_title_for_query(get_option('wacf_tax_class'));
                        }

                        /**
                         * Filter fee tax class
                         */
                        $feeTaxClass = apply_filters('wc_el_inv-tax-class-order-fee', $dataFee['tax_class'], $dataFee);

                        $taxRates = $parentOrder->get_tax_provider()->get_rates($feeTaxClass);
                        if (empty($taxRates)) {
                            $taxRates = $parentOrder->get_tax_provider()->get_base_tax_rates();
                        }
                        if (empty($taxRates)) {
                            $taxRates = array(0);
                        }

                        if (! empty($taxRates)) {
                            $id           = array_keys($taxRates);
                            $refundFee    = $parentOrder->get_total_refunded_for_item($data, 'fee');
                            $refundFeeTax = $parentOrder->get_tax_refunded_for_item($data, $id[0], 'fee');
                        }
                    }
                }
                $itemsRefundedDataFee[] = $dataFee;
                if (0 !== $refundFee || 0 !== $refundFeeTax) {
                    $itemsRefundedDataFee[]['refund_fee'] = array(
                        'name'      => $dataFee['name'],
                        'total'     => $refundFee,
                        'total_tax' => $refundFeeTax,
                    );
                }
                $itemsRefundedDataFee = array_filter($itemsRefundedDataFee);
            }
        }

        $filePath = '/inc/ordersRefundedJsonArgs.php';
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);

        $this->setXmlData($data);
    }

    /**
     * Global condition
     *
     * @param $query
     *
     * @return bool
     * @since 1.0.0
     *
     */
    private function typeXmlCondition($query)
    {
        return is_subclass_of($query, '\WcElectronInvoice\Providers\Order') ||
               is_subclass_of($query, '\WcElectronInvoice\Providers\OrderRefund');
    }
}
