<?php
/**
 * XmlOrderListTable.php
 *
 * @since      1.0.0
 * @package    WcElectronInvoice\Admin
 * @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\Admin;

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

use WcElectronInvoice\Admin\Settings\Fields\XmlOrdersList;
use WcElectronInvoice\Admin\Settings\OptionPage;
use WcElectronInvoice\Integrations;
use WcElectronInvoice\Plugin;
use WcElectronInvoice\Providers\OrderAbstract;
use WcElectronInvoice\Providers\OrderQuery;
use WcElectronInvoice\Providers\OrdersProviderAbstract;
use WcElectronInvoice\Utils\TimeZone;
use WcElectronInvoice\WooCommerce\Fields\InvoiceFields;
use WcElectronInvoice\WooCommerce\Fields\ReverseChargeSettings;
use function WcElectronInvoice\Functions\appendDocumentEndpointActions;
use function WcElectronInvoice\Functions\containsNumber;
use function WcElectronInvoice\Functions\disableInvoiceOnOrderTotalZero;
use function WcElectronInvoice\Functions\paymentMethodCode;
use function WcElectronInvoice\Functions\resolveDocumentEndpointActionVisibility;
use function WcElectronInvoice\Functions\setFormattedInvoiceNumber;
use function WcElectronInvoice\Functions\xmlNoInCache;

/**
 * Class XmlOrderListTable
 *
 * @since  1.0.0
 * @author alfiopiccione <alfio.piccione@gmail.com>
 */
class XmlOrderListTable extends \WP_List_Table
{
    /**
     * List type
     *
     * @since 1.0.0
     */
    const LIST_TYPE = 'shop_order';

    /**
     * Pagination number
     */
    const LIST_PAGINATION = 25;

    /**
     * Order Data
     *
     * @since 1.0.0
     *
     * @var array
     */
    private $data = array();

    /**
     * Full filtered document summary index used for counters and bulk flows.
     *
     * @var array<string, array<string, mixed>>
     */
    public static $filteredListIndex = array();

    /**
     * Full filtered item count before pagination.
     *
     * @var int
     */
    public static $filteredListCount = 0;

    /**
     * Total items for pagination.
     *
     * @var int
     */
    private $totalItems = 0;

    /**
     * IDS list
     *
     * @var OrderAbstract[]
     */
    public static $listIds = array();

    /**
     * XmlOrderListTable constructor.
     *
     * @param array $args
     *
     * @since 1.0.0
     *
     */
    public function __construct($args = array())
    {
        parent::__construct(array(
            'singular' => 'pop-invoice',
            'plural'   => 'pop-invoices',
            'ajax'     => false,
        ));

        self::$listIds           = array();
        self::$filteredListIndex = array();
        self::$filteredListCount = 0;

        $this->processBulkAction();

        $this->data = $this->convertObjInArray($this->getOrdersList());
    }

    /**
     * getOrdersData
     *
     * @return array
     */
    public function getOrdersData()
    {
        return $this->data;
    }

    /**
     * Convert Object in Array
     *
     * @param $dataObj
     *
     * @return array
     * @since 1.0.0
     *
     */
    private function convertObjInArray($dataObj)
    {
        $dataArray = array();

        if (! empty($dataObj)) {
            foreach ($dataObj as $data) {
                if (is_array($data)) {
                    $dataArray[] = $data;
                } elseif (is_object($data)) {
                    $dataArray[] = get_object_vars($data);
                }
            }
        }

        return $dataArray;
    }

    /**
     * Build a stable key for a normalized summary row.
     *
     * @param array<string, mixed> $summary
     *
     * @return string
     */
    private function buildSummaryListKey(array $summary)
    {
        if (isset($summary['document_key']) && '' !== (string)$summary['document_key']) {
            return (string)$summary['document_key'];
        }

        return implode(':', array(
            isset($summary['provider']) ? (string)$summary['provider'] : '',
            isset($summary['document_type']) ? (string)$summary['document_type'] : (isset($summary['order_type']) ? (string)$summary['order_type'] : 'shop_order'),
            isset($summary['id']) ? (string)$summary['id'] : '0',
            isset($summary['parent_id']) ? (string)$summary['parent_id'] : '0',
        ));
    }

    /**
     * Hydrate a normalized summary into its provider document.
     *
     * @param array<string, mixed> $summary
     *
     * @return OrderAbstract|null
     */
    private function hydrateSummaryDocument(array $summary)
    {
        return \WcElectronInvoice\Functions\resolveProviderDocument(
            isset($summary['id']) ? $summary['id'] : 0,
            isset($summary['provider']) ? $summary['provider'] : '',
            isset($summary['document_type']) ? (string)$summary['document_type'] : (isset($summary['order_type']) ? (string)$summary['order_type'] : 'shop_order'),
            isset($summary['parent_id']) ? (int)$summary['parent_id'] : 0
        );
    }

    /**
     * Build a stable list key for a document row.
     *
     * PMPro refunds share the same source order id as the parent order, so the
     * list cannot use the raw id as the unique key.
     *
     * @param OrderAbstract $order
     *
     * @return string
     */
    private function buildDocumentListKey($order)
    {
        return implode(':', array(
            (string)$order->get_provider(),
            (string)$order->get_type(),
            (string)$order->get_id(),
            method_exists($order, 'get_parent_id') ? (string)$order->get_parent_id() : '',
        ));
    }

    /**
     * Store a list row under a document-specific key.
     *
     * PMPro invoices and refunds can share the same numeric id, so the final
     * rendered list must not use the raw order id as the array key.
     *
     * @param array         $ordersList
     * @param OrderAbstract $order
     * @param mixed         $data
     *
     * @return void
     */
    private function storeDocumentListEntry(array &$ordersList, $order, $data)
    {
        $documentKey = '';
        if (is_object($data) && isset($data->document_key) && '' !== (string)$data->document_key) {
            $documentKey = (string)$data->document_key;
        }

        if ('' === $documentKey) {
            $documentKey = $this->buildDocumentListKey($order);
        }

        $ordersList[$documentKey] = $data;
    }

    /**
     * Resolve the provider document represented by a list row.
     *
     * @param array $item
     *
     * @return OrderAbstract|null
     */
    private function resolveItemDocument(array $item)
    {
        $documentType = isset($item['document_type']) ? $item['document_type'] : (isset($item['order_type']) ? $item['order_type'] : 'shop_order');
        $parentId     = isset($item['parent_id']) ? (int)$item['parent_id'] : 0;

        return \WcElectronInvoice\Functions\resolveProviderDocument(
            isset($item['id']) ? $item['id'] : 0,
            isset($item['provider']) ? $item['provider'] : '',
            (string)$documentType,
            $parentId
        );
    }

    /**
     * Build endpoint query args for a document row.
     *
     * @param int   $id
     * @param array $item
     * @param array $extra
     *
     * @return array
     */
    private function buildDocumentEndpointArgs($id, array $item, array $extra = array())
    {
        $args = array(
            self::LIST_TYPE => $id,
            'id'            => $id,
            'provider'      => $item['provider'],
            'document_type' => isset($item['document_type']) ? $item['document_type'] : (isset($item['order_type']) ? $item['order_type'] : 'shop_order'),
        );

        if (! empty($item['parent_id'])) {
            $args['parent_id'] = (int)$item['parent_id'];
        }

        return array_merge($args, $extra);
    }

    /**
     * Build current-page table rows from normalized summaries.
     *
     * @param array<int, array<string, mixed>> $summaries
     *
     * @return array<string, array<string, mixed>>
     */
    private function buildRowsFromSummaries(array $summaries)
    {
        $rows = array();

        foreach ($summaries as $summary) {
            $order = $this->hydrateSummaryDocument($summary);
            if (! $order) {
                continue;
            }

            self::$listIds[$this->buildDocumentListKey($order)] = $order;

            if (is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
                $level = \WcElectronInvoice\Wizard::popGetUserLevel();
                if (! \WcElectronInvoice\Functions\isCreditNotePlanEnabled($level)) {
                    continue;
                }
                $row = $this->getDataRefundOrder($order);
            } else {
                $row = $this->getDataOrder($order);
            }

            if (is_object($row)) {
                $row = get_object_vars($row);
            }

            $row['sort_date'] = isset($summary['sort_date']) ? $summary['sort_date'] : 0;
            $this->storeDocumentListEntry($rows, $order, (object)$row);
        }

        return $rows;
    }

    /**
     * Filter and normalize summary rows before pagination.
     *
     * @param array<int, array<string, mixed>> $summaries
     * @param string                           $type
     * @param string                           $provider
     *
     * @return array<string, array<string, mixed>>
     */
    private function filterOrderSummaries(array $summaries, $type = '', $provider = '')
    {
        $filtered        = array();
        $choiceTypeCheck = OptionPage::init()->getOptions('invoice_choice_type');

        foreach ($summaries as $summary) {
            $summaryProvider = isset($summary['provider']) ? (string)$summary['provider'] : '';
            $documentType    = isset($summary['document_type']) ? (string)$summary['document_type'] : (isset($summary['order_type']) ? (string)$summary['order_type'] : 'shop_order');
            $invoiceSent     = isset($summary['invoice_sent']) ? (string)$summary['invoice_sent'] : '';
            $invoiceSentBool = 'sent' === $invoiceSent;
            $choiceType      = isset($summary['choice_type']) ? (string)$summary['choice_type'] : '';
            $effectiveChoice = '' === $choiceType ? 'invoice' : $choiceType;
            $total           = isset($summary['total']) ? (float)$summary['total'] : 0;
            $totalRefunded   = isset($summary['total_refunded']) ? (float)$summary['total_refunded'] : 0;
            $status          = isset($summary['status']) ? (string)$summary['status'] : '';
            $hasInvoiceNo    = (isset($summary['invoice_number']) ? (int)$summary['invoice_number'] : 0) > 0;

            if (! empty($provider) && 'all' !== $provider && $provider !== $summaryProvider) {
                continue;
            }

            if ('receipt' === $type && 'receipt' !== $choiceType) {
                continue;
            }

            if ('invoice' === $type && 'invoice' !== $effectiveChoice) {
                continue;
            }

            if ('on' !== $choiceTypeCheck && 'receipt' === $choiceType) {
                continue;
            }

            if ('shop_order_refund' !== $documentType && ! $invoiceSentBool && $this->shouldHideZeroTotalSummary($summary)) {
                continue;
            }

            if (
                'shop_order_refund' !== $documentType &&
                ! $hasInvoiceNo &&
                abs($total) === abs($totalRefunded) &&
                'refunded' === $status
            ) {
                continue;
            }

            if ('shop_order_refund' === $documentType) {
                if ('invoice' === $type) {
                    continue;
                }

                $parentHasInvoice = (isset($summary['parent_invoice_number']) ? (int)$summary['parent_invoice_number'] : 0) > 0;
                $refundNumber     = isset($summary['refund_number']) ? (int)$summary['refund_number'] : (isset($summary['invoice_number']) ? (int)$summary['invoice_number'] : 0);
                if (! $parentHasInvoice || $refundNumber < 1) {
                    continue;
                }

                $parentSentTime = isset($summary['parent_invoice_sent_timestamp']) ? (int)$summary['parent_invoice_sent_timestamp'] : 0;
                $refundSentTime = isset($summary['refund_invoice_sent_timestamp']) ? (int)$summary['refund_invoice_sent_timestamp'] : 0;
                if ($parentSentTime > 0 && $refundSentTime > 0 && $parentSentTime > $refundSentTime) {
                    continue;
                }

                $parentSent     = 'sent' === (isset($summary['parent_invoice_sent']) ? (string)$summary['parent_invoice_sent'] : '');
                $refundSent     = 'sent' === $invoiceSent;
                $refundCount    = isset($summary['refund_count']) ? (int)$summary['refund_count'] : 0;
                $isLatestRefund = ! empty($summary['is_latest_refund']);

                if ($parentSent && ! $refundSent && $refundCount > 1 && ! $isLatestRefund) {
                    continue;
                }
            }

            $filtered[$this->buildSummaryListKey($summary)] = $summary;
        }

        return $filtered;
    }

    /**
     * Hide non-refund summaries with zero total when the option is enabled.
     *
     * @param array<string, mixed> $summary
     *
     * @return bool
     */
    private function shouldHideZeroTotalSummary(array $summary)
    {
        $options = OptionPage::init();
        $disableInvoiceNumber = $options->getOptions('disable_invoice_number_order_zero');

        if ('on' !== $disableInvoiceNumber) {
            return false;
        }

        return 0.00 === (isset($summary['total']) ? (float)$summary['total'] : 0) && 'refunded' !== (isset($summary['status']) ? (string)$summary['status'] : '');
    }

    /**
     * Compare two summary/row arrays using the active table ordering.
     *
     * @param array<string, mixed> $a
     * @param array<string, mixed> $b
     *
     * @return int
     */
    private function compareListItems(array $a, array $b)
    {
        $orderbyDefault = apply_filters('wc_el_inv-list-table-invoice-orderby_default', 'sort_date');
        $orderby        = (! empty($_GET['orderby'])) ? $_GET['orderby'] : $orderbyDefault;
        $order          = (! empty($_GET['order'])) ? $_GET['order'] : 'desc';
        $numericColumns = array('id', 'total', 'invoice_number', 'sort_date', 'date_created', 'date_modified', 'date_completed');

        $aValue = isset($a[$orderby]) ? $a[$orderby] : '';
        $bValue = isset($b[$orderby]) ? $b[$orderby] : '';

        if (in_array($orderby, $numericColumns, true)) {
            $aNumber = (float)$aValue;
            $bNumber = (float)$bValue;
            if ($aNumber === $bNumber) {
                $result = 0;
            } elseif ($aNumber < $bNumber) {
                $result = -1;
            } else {
                $result = 1;
            }
        } else {
            $result = strcmp((string)$aValue, (string)$bValue);
        }

        return ('asc' === strtolower((string)$order)) ? $result : -$result;
    }

    /**
     * Get Actions
     *
     * @param $id
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function actions($id, $item)
    {
        // Get key
        $key             = OptionPage::getK();
        $provider        = $item['provider'];
        $customerType    = isset($item['invoice_type']) ? $item['invoice_type'] : null;
        $output          = '';
        $nonce           = wp_create_nonce('wc_el_inv_pop_invoice');
        $shopCountry     = Integrations::getNormalizedShopCountry();
        $endpointActionsVisibility = resolveDocumentEndpointActionVisibility((string)$customerType, $shopCountry);

        // Check if invoice is sent
        $order = $this->resolveItemDocument($item);

        // Bail if the order got deleted.
        if (! $order) {
            return esc_html__('Order not found.', WC_EL_INV_TEXTDOMAIN);
        }

        // Bail if the associated invoices does not exist (yet?).
        if (! $order->get_order_number_invoice() && ! Integrations::haveIntegrationActive('fattureincloud')) {
            return sprintf("<div style='margin:10px 0 0 0;' class='pop-notice notice-warning font-size-tiny'>%s</div>",
                esc_html__('Invoice not generated - Invoice number not set for this order.', WC_EL_INV_TEXTDOMAIN));
        }

        $checkSent = $order->get_invoice_sent();
        $checkSent = isset($checkSent) && 'sent' === $checkSent ? true : false;

        // Choice type
        $choiceTypeModifier = isset($item['choice_type']) ? esc_attr($item['choice_type']) : null;

        /**
         * Filter Check sent by Choice type modifier
         */
        $checkSent = apply_filters('wc_el_inv-actions_choice_type_modifier', $checkSent, $id, $choiceTypeModifier);

        // Choice type
        $choiceTypeCheck = OptionPage::init()->getOptions('invoice_choice_type');
        $hiddenClass     = '';
        if ('on' !== $choiceTypeCheck) {
            $hiddenClass = 'hidden ';
        }

        $hiddenClassInvoiceReceiptInput = '';
        if ('shop_order_refund' === $item['order_type']) {
            $hiddenClassInvoiceReceiptInput = 'hidden ';
        }

        if ((! class_exists('WooPoPToFattureInCloud\Plugin') &&
             ! class_exists('WooPoPToFattureAruba\Plugin'))
        ) {
            if (true === apply_filters('wc_el_inv-actions_doc_type_invoice', true)) {
                $output .= sprintf('<div class="' . $hiddenClass . 'doc-type-wrap">' .
                                   '<label title="%6$s" for="doc_type_invoice-%1$s">%2$s ' .
                                   '<input class="doc-type-input" value="invoice" id="doc_type_invoice-%1$s" type="radio" name="doc_type-%1$s" %3$s></label>' .
                                   '<label class="' . $hiddenClassInvoiceReceiptInput . '" for="doc_type_receipt-%1$s">%4$s ' .
                                   '<input class="doc-type-input" value="receipt"  id="doc_type_receipt-%1$s" type="radio" name="doc_type-%1$s" %5$s></label>' .
                                   '</div><hr>',
                    $item['id'],
                    'shop_order' === $item['order_type'] ?
                        esc_html_x('Invoice', 'invoice_choice', WC_EL_INV_TEXTDOMAIN) :
                        esc_html_x('Refund', 'invoice_choice', WC_EL_INV_TEXTDOMAIN),
                    '' === $item['choice_type'] || 'invoice' === $item['choice_type'] || 'shop_order_refund' === $item['order_type'] ? 'checked' : '',
                    esc_html_x('Receipt', 'invoice_choice', WC_EL_INV_TEXTDOMAIN),
                    'receipt' === $item['choice_type'] && 'shop_order_refund' !== $item['order_type'] ? 'checked' : '',
                    esc_html__('Remember to check the data entered by the customer', WC_EL_INV_TEXTDOMAIN)
                );
            }
        }

        $output .= sprintf('<input class="choice_type--current" type="hidden" value="%s">', $item['choice_type']);

        if (true === apply_filters('wc_el_inv-actions_mark_trigger_invoice', true)) {
            $ajaxNonce = wp_create_nonce('wc_el_inv_ajax-ajax_nonce');
            if ($checkSent) {
                $urlUndo = esc_url_raw(add_query_arg([
                                                         'undo'  => 'true',
                                                         'nonce' => $ajaxNonce,
                                                     ] + $this->buildDocumentEndpointArgs($id, $item),
                    site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT)));
                $output  .= sprintf(
                    '<a id="mark_as_sent-%1$s" class="mark_trigger mark_undo button button-secondary" href="%2$s" title="%3$s">' .
                    '<span class="dashicons dashicons-undo"></span></a>',
                    $id,
                    $urlUndo,
                    esc_html__('Undo', WC_EL_INV_TEXTDOMAIN)
                );
            } else {
                $urlSent = esc_url_raw(add_query_arg([
                                                         'sent'  => 'true',
                                                         'nonce' => $ajaxNonce,
                                                     ] + $this->buildDocumentEndpointArgs($id, $item),
                    site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT)));
                $output  .= sprintf(
                    '<a id="mark_as_sent-%1$s" class="mark_trigger mark_as_sent button button-secondary" href="%2$s" title="%3$s">' .
                    '<span class="dashicons dashicons-yes"></span></a>',
                    $id,
                    $urlSent,
                    true === $checkSent ? esc_html__('Disabled', WC_EL_INV_TEXTDOMAIN) : esc_html__('Mark as Sent',
                        WC_EL_INV_TEXTDOMAIN),
                );
            }
        }

        $endpointActionUrls = [
            'xml' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'xml',
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
            'xml_view' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'xml',
                'view'   => 'true',
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
            'xml_save' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'xml',
                'save'   => 'true',
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
            'pdf' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'pdf',
                'nonce'  => $nonce,
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
            'zugferd' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'zugferd',
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
            'zugferd_zip' => esc_url_raw(add_query_arg([
                'key'    => $key,
                'format' => 'zugferd_zip',
                'v'      => time(),
            ] + $this->buildDocumentEndpointArgs($id, $item), site_url('/' . \WcElectronInvoice\EndPoint\Endpoints::ENDPOINT))),
        ];
        $output = appendDocumentEndpointActions($output, $endpointActionUrls, $checkSent, $endpointActionsVisibility);

        $output = Integrations::appendConfiguredActions($output, $id, $item, $provider);

        /**
         * Filter actions out put
         *
         * @since 1.0.0
         */
        return apply_filters('wc_el_inv-xml_order_list_table_actions', $output, $id, $item);
    }

    /**
     * Get Order Type string
     *
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function orderCustomer($item)
    {
        $name         = $item['billing']['first_name'] ?: '';
        $lastName     = $item['billing']['last_name'] ?: '';
        $company      = $item['billing']['company'] ?: '';
        $customerType = $item['invoice_type'] ?: 'private';

        if ('private' === $customerType && '' !== $name) {
            $fullName = $name . ' ' . ($lastName ?: '');
        } elseif ('private' !== $customerType && '' !== $company) {
            $fullName = $company;
        } else {
            $fullName = '' !== $name ? $name . ' ' . $lastName : $company;
        }

        // if user display name is empty in order data, try to get it from user ID
        if (empty($fullName) && $item['customer_id']) {
            $user = get_user_by('id', $item['customer_id']);
            if ($user) {
                $fullName = $user->display_name;
            }
        }

        // if customer_id is empty or user does not exist, return full name or 'Unknown customer'
        if (empty($item['customer_id']) || ! get_user_by('id', $item['customer_id'])) {
            return ucfirst($fullName) ?: __('Unknown customer', WC_EL_INV_TEXTDOMAIN);
        }

        // if we have a real user linked to the order, we can also link to the user edit page
        $order        = $this->resolveItemDocument($item);
        $customerLink = $order->get_edit_user_url();
        $edit         = esc_html__('Edit customer', WC_EL_INV_TEXTDOMAIN);

        return sprintf('%s %s',
            ucfirst($fullName),
            "<br><small class='edit'><a href='{$customerLink}' title='{$edit}'>{$edit}</a></small>"
        );
    }

    /**
     * Get Order Title
     *
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function orderTitle($item)
    {
        if (! isset($item['id'])) {
            return '';
        }

        switch ($item['order_type']) {
            case 'shop_order':
                $type = esc_html__('Order', WC_EL_INV_TEXTDOMAIN);
                break;
            case 'shop_order_refund':
                $type = esc_html__('Refund', WC_EL_INV_TEXTDOMAIN);
                break;
            default:
                $type = '';
                break;
        }

        try {
            $dateCreated   = isset($item['date_created']) && '' !== $item['date_created'] ? $item['date_created'] : $item['date_modified'];
            $dateCompleted = null;
            if (isset($item['date_completed']) && '' !== $item['date_completed']) {
                $dateCompleted = $item['date_completed'];
            }

            $timeZone = new TimeZone();
            $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());

            // Date created
            $dateCreated = new \DateTime($dateCreated);
            $dateCreated->setTimezone($timeZone);
            $dateTimeCreated = $dateCreated->format('Y-m-d H:i');

            // Date completed
            $dateTimeCompleted = null;
            if ($dateCompleted) {
                $dateCompleted = new \DateTime($dateCompleted);
                $dateCompleted->setTimezone($timeZone);
                $dateTimeCompleted = $dateCompleted->format('Y-m-d H:i');
            }

            // Refund edit link
            if ('shop_order_refund' === $item['order_type']) {
                $parent_id     = $item['parent_id'];
                $order         = OrderQuery::instance()->getProviderOrder($parent_id, $item['provider']);
                $editOrderLink = esc_url($order->get_edit_order_url() . '#order_refunds');
                $edit          = esc_html__('Edit refund', WC_EL_INV_TEXTDOMAIN);
            } else {
                $order         = OrderQuery::instance()->getProviderOrder($item['id'], $item['provider']);
                $editOrderLink = esc_url($order->get_edit_order_url());
                $edit          = esc_html__('Edit order', WC_EL_INV_TEXTDOMAIN);
            }

            switch ($order->get_status()) {
                case 'processing':
                    $status = esc_html__('Processing', WC_EL_INV_TEXTDOMAIN);
                    $color  = 'style="color:green"';
                    break;
                case 'completed':
                    $status = esc_html__('Completed', WC_EL_INV_TEXTDOMAIN);
                    $color  = 'style="color:dodgerblue"';
                    break;
                case 'refunded':
                    $status = esc_html__('Refunded', WC_EL_INV_TEXTDOMAIN);
                    $color  = 'style="color:red"';
                    break;
                default:
                    $status = $order->get_status();
                    $color  = '';
                    break;
            }

            $output = '';
            $output .= sprintf('%s<br>%s<br>%s%s',
                // Customer
                sprintf('<strong>%s:</strong> %s',
                    esc_html__('Customer', WC_EL_INV_TEXTDOMAIN),
                    $this->orderCustomer($item)
                ),
                // Order / Refund created
                sprintf('<strong>%s: %s</strong> <small>%s %s</small>',
                    $order->get_type_label($item['order_type']),
                    esc_html("#" . $item['id']),
                    esc_html__('of', WC_EL_INV_TEXTDOMAIN),
                    "{$dateTimeCreated}"
                ),
                // Order State
                'shop_order_refund' !== $item['order_type'] ? sprintf(
                    '<strong>%s:</strong> <span %s>%s</span> %s<br>',
                    esc_html__('Status', WC_EL_INV_TEXTDOMAIN),
                    $color,
                    $status,
                    $dateTimeCompleted && 'completed' === $order->get_status() ?
                        sprintf('<small>%s</small>', $dateTimeCompleted) : '') : '',
                // Edit Order / Refund
                "<small class='edit'><a href='{$editOrderLink}' title='{$edit}'>{$edit}</a></small>"
            );

            $nature = $order->get_nature_rc();
            $ref    = $order->get_ref_norm_rc();
            if ($nature) {
                $output .= sprintf('<hr><strong class="color_blue">%s</strong>',
                    esc_html__('Reverse Charge', WC_EL_INV_TEXTDOMAIN),
                );
                $output .= sprintf('<br><strong>%s:</strong> %s',
                    esc_html__('Operation\'s Nature', WC_EL_INV_TEXTDOMAIN),
                    $nature
                );
            }
            if ($ref && ReverseChargeSettings::shouldDisplayRefNormRc()) {
                $output .= sprintf('<br><strong>%s:</strong> %s',
                    esc_html__('Tax rule reference', WC_EL_INV_TEXTDOMAIN),
                    $ref
                );
            }

            return $output;
        } catch (\Exception $e) {
            echo esc_html__('Order title DateTime error: ', WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage());
        }
    }

    /**
     * Order Total
     *
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function orderTotal($item)
    {
        if (! isset($item['total'])) {
            return esc_html__('Error: no total order');
        }

        if (! empty($item['refunded']) && 'shop_order_refund' === $item['order_type']) {
            $provider = $item['provider'];
            $total    = $item['refunded']['total_refunded'];

            if (! empty($item['current_refund_items'])) {
                $total = 0;
                foreach ($item['current_refund_items'] as $item) {
                    $total += floatval(abs($item['total'])) + floatval(abs($item['total_tax']));
                }
            }

            // Check order_id and parent_id to define ref order
            $parentID = $item['order_id'] ?? null;
            if (! $parentID) {
                $parentID = $item['parent_id'] ?? null;
            }

            $editOrderLink = '';
            if ($parentID) {
                $order         = OrderQuery::instance()->getProviderOrder($parentID, $provider);
                $editOrderLink = esc_url($order->get_edit_order_url());
            }

            $total = sprintf('<strong>-%s %s</strong><br>%s: %s<b>#%s</b>%s',
                $this->numberFormat(abs($total)),
                isset($item['currency']) ? ($item['currency_symbol']) : '',
                esc_html__('Linked order', WC_EL_INV_TEXTDOMAIN),
                $editOrderLink ? '<a href="' . $editOrderLink . '">' : '<span>',
                $parentID ?? 'NaN',
                $editOrderLink ? '</a>' : '</span>',
            );
        } elseif (! empty($item['refunded']) && 'shop_order' === $item['order_type']) {

            // orderTotal for refunded
            $currentTotal = $item['refunded']['remaining_amount'];
            if (abs($item['refunded']['total_refunded']) > abs($item['refunded']['remaining_amount'])) {
                $currentTotal = $item['refunded']['total_refunded'];
            }

            $total = sprintf('<strong>%s %s</strong>',
                $this->numberFormat(abs($currentTotal)),
                isset($item['currency']) ? ($item['currency_symbol']) : ''
            );

            // check total refunded
            if (abs($item['refunded']['total_refunded']) === abs($item['total'])) {
                $total = sprintf('<strong>%s %s</strong>',
                    $this->numberFormat(abs($item['total'])),
                    isset($item['currency']) ? ($item['currency_symbol']) : ''
                );
            }

            // add refunded total
            if (0 !== abs($item['refunded']['total_refunded']) &&
                abs($item['refunded']['total_refunded']) !== abs($item['total'])
            ) {
                $total = $total . sprintf('<br>%s <strong>%s %s</strong>',
                        esc_html__('Refunded', WC_EL_INV_TEXTDOMAIN),
                        $this->numberFormat(abs($item['refunded']['total_refunded'])),
                        isset($item['currency']) ? ($item['currency_symbol']) : ''
                    );
            }
        } else {
            $total = sprintf('<strong>%s %s</strong>',
                $this->numberFormat(abs($item['total'])),
                isset($item['currency']) ? ($item['currency_symbol']) : ''
            );
        }

        return $total;
    }

    /**
     * Number Format
     *
     * @param int    $number
     * @param int    $decimal
     * @param bool   $abs
     * @param string $decSep
     * @param string $thSep
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function numberFormat($number = 0, $decimal = 2, $abs = true, $decSep = '.', $thSep = '')
    {
        if (null === $number) {
            return null;
        }

        if (is_string($number)) {
            $number = floatval($number);
        }

        if ($abs) {
            $number = abs($number);
        }

        return number_format($number, $decimal, $decSep, $thSep);
    }

    /**
     * Invoice Number
     *
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function invoiceNumber($item)
    {
        $options = OptionPage::init();

        $order = $this->resolveItemDocument($item);
        if (! $order) {
            return '';
        }
        $wcOrderClass       = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order');
        $wcOrderRefundClass = \WcElectronInvoice\Functions\wcOrderClassName($order, '\WC_Order_Refund');
        $orderClass         = is_subclass_of($order, '\WcElectronInvoice\Providers\Order');
        $orderRefundClass   = is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund');
        if ($order instanceof $wcOrderClass) {
            $order = OrderQuery::instance()->getProviderOrder($item['id'], $item['provider']);
        }

        if ($order instanceof $wcOrderRefundClass) {
            $order = OrderQuery::instance()->getProviderOrder($item['parent_id'], $item['provider']);
        }

        if (! $orderClass &&
            ! $orderRefundClass &&
            ! $order instanceof $wcOrderClass &&
            ! $order instanceof $wcOrderRefundClass) {
            return '';
        }

        // Shop order number
        $number = $item['invoice_number'] ?? '';

        // Numeration type
        $numerationType = $order->get_billing_choice_type() ?: 'invoice';

        if ('shop_order_refund' === $item['order_type']) {
            $refundOrder = $this->resolveItemDocument($item);
            $parentOrder = \WcElectronInvoice\Functions\resolveProviderDocument($item['parent_id'], $item['provider'],
                'shop_order', $item['parent_id']);
            if (! $refundOrder || ! $parentOrder) {
                return '';
            }
            $numerationType = $parentOrder->get_billing_choice_type();
            $number         = $refundOrder->get_meta("refund_number_invoice-{$item['id']}");
            if ('receipt' === $numerationType) {
                $numerationType = 'credit_note_receipt';
            } else {
                $numerationType = 'credit_note';
            }
            $order = $refundOrder;
        }

        // Number of digits
        $digits = $options->getOptions('number_digits_in_invoice');
        $digits = isset($digits) && '' !== $digits ? $digits : 2;
        // Prefix
        $prefix = $options->getOptions("prefix_{$numerationType}_number");

        /**
         * Invoice prefix filter
         */
        $prefix = apply_filters("wc_el_inv-prefix_{$numerationType}", $prefix, $order);

        // Suffix
        $suffix           = $options->getOptions('suffix_invoice_number');
        $suffixYear       = $options->getOptions('suffix_year_invoice_number');
        $suffixYearFormat = $options->getOptions('suffix_year_format_invoice_number') ?: 'Y';
        if ('on' === $suffixYear) {
            if ('shop_order' === $order->get_type()) {
                $created = $order->has_status('completed') ? $order->get_date_completed() : $order->get_date_created();
            } else {
                $created = $order->get_date_created();
            }
            $suffix = "/" . $created->format($suffixYearFormat);
        } else {
            $suffix = isset($suffix) && '' !== $suffix ? $suffix : '';
        }

        /**
         * Invoice suffix filter
         */
        $suffix          = apply_filters('wc_el_inv-suffix_number', $suffix, $order);
        $formattedNumber = $order->get_meta("{$order->get_type()}_formatted_number_{$order->get_id()}") ?? null;

        $invoiceNumber = $order->get_order_number_invoice();
        if (empty($invoiceNumber) && empty($formattedNumber)) {
            // Not invoice number
            return '<span class="color_red dashicons dashicons-dismiss"></span>';
        }

        if ($formattedNumber) {
            $normalizedNumber = apply_filters('wc_el_inv-normalize_formatted_number', $formattedNumber, $order);
            $rawNumber        = (int)$number;
            $number           = $normalizedNumber;

            // Update invoice formatted
            if ('shop_order' === $order->get_type() && ! containsNumber($formattedNumber, $invoiceNumber)) {
                $number = $order->get_order_number_invoice();
                setFormattedInvoiceNumber($order, $numerationType, true);
            }

            if ($rawNumber > 0 && ! containsNumber((string)$number, $rawNumber)) {
                $number = \WcElectronInvoice\Functions\buildFormattedInvoiceNumberValue($order, $numerationType,
                    $rawNumber);
            }

            if (is_numeric($number) && ($prefix || $suffix)) {
                $number = "{$prefix}{$number}{$suffix}";
            }

            return apply_filters('wc_el_inv-list-table-invoice-number', $number, $order, $prefix);
        } else {
            // Invoice number
            $invNumber = str_pad($number, $digits, '0', STR_PAD_LEFT);

            // Invoice number
            $number = isset($number) && 0 !== $number && '' !== $number ? "{$prefix}{$invNumber}{$suffix}" : '';

            $order->update_meta("{$order->get_type()}_formatted_number_{$order->get_id()}", $number);
            $order->save();
        }

        return apply_filters('wc_el_inv-list-table-invoice-number', $number, $order, $prefix);
    }

    /**
     * Customer Type
     *
     * @param $item
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerType($item)
    {
        if (! isset($item['invoice_type'])) {
            return '';
        }

        switch ($item['invoice_type']) {
            case 'company':
                $type = esc_html__('Company', WC_EL_INV_TEXTDOMAIN);
                break;
            case 'freelance':
                $type = esc_html__('Freelance', WC_EL_INV_TEXTDOMAIN);
                break;
            case 'private':
                $type = esc_html__('Private', WC_EL_INV_TEXTDOMAIN);
                break;
            default:
                $type = '';
                break;
        }

        if ('' !== $item['invoice_type'] &&
            ('company' !== strtolower($type) && 'freelance' !== strtolower($type) && 'private' !== strtolower($type))
        ) {
            // Sanitize custom invoice_type
            $item['invoice_type'] = str_replace(array('_', '-'), ' ', $item['invoice_type']);
            $type                 = esc_html__(ucfirst($item['invoice_type']), WC_EL_INV_TEXTDOMAIN);
        }

        // No UE
        if (! in_array($item['billing']['country'], InvoiceFields::$euVatCountry, true)) {
            return $type . esc_html__('* Non-EU customer *', WC_EL_INV_TEXTDOMAIN);
        }

        return $type;
    }

    /**
     * Choice Type
     *
     * @param $item
     *
     * @return string
     */
    private function choiceType($item)
    {
        switch ($item['choice_type']) {
            case 'receipt':
                if ('shop_order_refund' === $item['order_type']) {
                    $type = sprintf('<span style="color:red;" class="dashicons dashicons-media-text" title="%s"></span>',
                        esc_html_x('Refund', 'invoice_choice', WC_EL_INV_TEXTDOMAIN)
                    );
                } else {
                    $type = sprintf('<span style="color:dodgerblue;" class="dashicons dashicons-media-text" title="%s"></span>',
                        esc_html_x('Receipt', 'invoice_choice', WC_EL_INV_TEXTDOMAIN)
                    );
                }
                break;
            case 'invoice':
            case '':
                if ('shop_order' === $item['order_type']) {
                    $type = sprintf('<span style="color:green;" class="dashicons dashicons-media-text" title="%s"></span>',
                        esc_html_x('Invoice', 'invoice_choice', WC_EL_INV_TEXTDOMAIN)
                    );
                } else {
                    $type = sprintf('<span style="color:red;" class="dashicons dashicons-media-text" title="%s"></span>',
                        esc_html_x('Refund', 'invoice_choice', WC_EL_INV_TEXTDOMAIN)
                    );
                }
                break;
            default:
                $type = sprintf('<span style="color:green;" class="dashicons dashicons-media-text" title="%s"></span>',
                    esc_html_x('Invoice', 'invoice_choice', WC_EL_INV_TEXTDOMAIN)
                );
                break;
        }

        return $type;
    }

    /**
     * Custom VAT or SDI
     *
     * @param $item
     * @param $key
     *
     * @return string
     * @since 1.0.0
     *
     */
    private function customerVatOrSdi($item, $key)
    {
        if ('receipt' === $item['choice_type']) {
            return '';
        }

        if (Integrations::isPeppolContext() && in_array($key, ['tax_code', 'sdi_type'], true)) {
            return '';
        }

        $value = isset($item[$key]) && '' !== $item[$key] ? $item[$key] : null;

        if ('sdi_type' === $key && null === $value) {
            $value = '0000000';
        }

        if ('private' === $item['invoice_type'] && 'tax_code' !== $key) {
            $value = '';
        }

        if (! $value && ! in_array($item['billing']['country'], InvoiceFields::$euVatCountry, true)) {
            return esc_html__('Non-EU customer', WC_EL_INV_TEXTDOMAIN);
        }

        if (null === $value) {
            return esc_html__('(*) No data', WC_EL_INV_TEXTDOMAIN);
        }

        return $value;
    }

    /**
     * Sent Invoice icon
     *
     * @param $item
     *
     * @return string
     */
    private function sentInvoice($item): string
    {
        $invoiceSent = $item['invoice_sent'] ?? null;
        switch ($invoiceSent) {
            case 'sent':
                return '<i title="' . _x('Sent', 'icon',
                        WC_EL_INV_TEXTDOMAIN) . '" class="mark-yes dashicons dashicons-yes"></i>';
            default:
                return '<i title="' . _x('Not Sent', 'icon',
                        WC_EL_INV_TEXTDOMAIN) . '" class="mark-warning dashicons dashicons-warning"></i>';
        }
    }

    /**
     * Reorder
     *
     * @since 1.0.0
     */
    public function reorder($a, $b)
    {
        return $this->compareListItems((array)$a, (array)$b);
    }

    /**
     * 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(),
        );

        // Order Invoice number.
        $invoiceNumber = $order->get_order_number_invoice();
        $invoiceSent   = $order->get_invoice_sent();

        $refundedData = array(
            'remaining_amount'        => $order->get_remaining_refund_amount(),
            'remaining_items'         => $order->get_remaining_refund_items(),
            'total_qty_refunded'      => $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');
        $orderItemsShipping = $order->get_items('shipping');
        $orderItemsFee      = $order->get_items('fee');
        $itemsData          = array();
        $itemsDataTax       = array();
        $itemsDataShip      = array();
        $itemsDataFee       = array();
        $refundedItem       = array();

        foreach ($orderItems as $item) {

            $item_product = $item->get_product();
            $sku          = $item_product ? $item_product->get_sku() : null;

            $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' => $order->get_qty_refunded_for_item($item->get_id()),
                );
            }
        }

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

        // Shipping
        foreach ($orderItemsShipping as $itemID => $itemShip) {

            $dataShip   = $itemShip->get_data();
            $refundShip = $refundShipTax = 0;
            foreach ($dataShip as $key => $data) {
                if ('id' === $key) {
                    $taxRates = isset($dataShip['tax_class']) ? $order->get_tax_provider()->get_rates($dataShip['tax_class']) : array();
                    if (empty($taxRates)) {
                        $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                    }
                    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
        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 = $order->get_tax_provider()->get_rates($dataFee['tax_class']);
                    if (empty($taxRates)) {
                        $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                    }
                    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';
        // Get Ids.
        if (get_query_var('get_ids') === 'true') {
            $orderID  = $order->get_id();
            $filePath = '/inc/ordersIdsJsonArgs.php';
        }
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);
        if (is_object($data)) {
            $data->document_type = 'shop_order';
            $data->document_key  = $this->buildDocumentListKey($order);
        }

        return $data;
    }

    /**
     * Data Refund Order
     *
     * @param OrderAbstract $order
     *
     * @return array
     * @since 1.0.0
     *
     */
    public function getDataRefundOrder($order)
    {

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

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

        // Initialize Orders data and type.
        $orderType = $order->get_type();
        $orderData = $order->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(),
        );
        $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(),
        );
        // Order Refund Invoice number.
        $invoiceNumber = $order->get_meta("refund_number_invoice-{$refundID}");
        $invoiceSent   = $order->get_invoice_sent();

        // 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(),
        );

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

        // Current order refund item data
        // Use the refund entity already passed to this method.
        $refundOrder      = $order;
        $refundOrderItems = $refundOrder->get_items();
        $currentRefund    = array();
        // Items refunded
        $refundItemsShipping = $refundOrder->get_items('shipping');
        $refundItemsFee      = $refundOrder->get_items('fee');
        if (! empty($refundOrderItems)) {
            foreach ($refundOrderItems as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    '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'],
                );
            }
        }
        // Shipping
        if (! empty($refundItemsShipping) && false !== strpos($order->get_shipping_total(), '-')) {
            foreach ($orderItemsShipping as $item) {
                $data            = $item->get_data();
                $currentRefund[] = array(
                    'order_id'     => $order->get_parent_id(),
                    'refund_id'    => $order->get_id(),
                    '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) {
                        $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)) {
                            $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(
                    '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,
                );
            }
        }

        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();
                $sku                 = $item->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
        foreach ($orderItemsTaxes as $itemID => $itemTax) {
            $itemsRefundedDataTax[] = $itemTax->get_data();
            $itemsRefundedDataTax   = array_filter($itemsRefundedDataTax);
        }

        // Fee
        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 = $order->get_tax_provider()->get_rates($dataFee['tax_class']);
                    if (empty($taxRates)) {
                        $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                    }
                    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(
                    'total'     => $refundFee,
                    'total_tax' => $refundFeeTax,
                );
            }
            $itemsRefundedDataFee = array_filter($itemsRefundedDataFee);
        }

        // Shipping
        foreach ($orderItemsShipping 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)) {
                        $taxRates = $order->get_tax_provider()->get_base_tax_rates();
                    }
                    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);
        }

        $filePath = '/inc/ordersRefundedJsonArgs.php';
        // Get Ids.
        if (get_query_var('get_ids') === 'true') {
            $orderID  = $order->get_id();
            $filePath = '/inc/ordersIdsJsonArgs.php';
        }
        // @codingStandardsIgnoreLine
        $data = include Plugin::getPluginDirPath($filePath);
        if (is_object($data)) {
            $data->document_type = 'shop_order_refund';
            $data->document_key  = $this->buildDocumentListKey($order);
        }

        return $data;
    }

    /**
     * Get Orders
     *
     * @param bool $onlyCount
     *
     * @return array|int
     * @throws \Exception
     * @since 1.0.0
     */
    public function getOrdersList($onlyCount = false)
    {
        // @codingStandardsIgnoreLine
        $provider      = \WcElectronInvoice\Functions\filterInput($_GET, 'filter_provider', FILTER_UNSAFE_RAW);
        $state         = \WcElectronInvoice\Functions\filterInput($_GET, 'filter_state', FILTER_UNSAFE_RAW);
        $type          = \WcElectronInvoice\Functions\filterInput($_GET, 'filter_type', FILTER_UNSAFE_RAW);
        $dateIN        = \WcElectronInvoice\Functions\filterInput($_GET, 'date_in', FILTER_UNSAFE_RAW);
        $dateOUT       = \WcElectronInvoice\Functions\filterInput($_GET, 'date_out', FILTER_UNSAFE_RAW);
        $orderToSearch = \WcElectronInvoice\Functions\filterInput($_GET, 'order_search', FILTER_SANITIZE_NUMBER_INT);

        // prepare query args
        $args = $this->prepareQueryArgs($state, $type, $dateIN, $dateOUT);

        /**
         * Add filter for get receipt order
         */
        add_filter('woocommerce_order_data_store_cpt_get_orders_query',
            function ($query, $queryVars) use ($type, $orderToSearch) {
                if ('receipt' === $type && empty($queryVars['_billing_choice_type'])) {
                    $query['meta_query'][] = array(
                        'key'   => '_billing_choice_type',
                        'value' => esc_attr($type),
                    );
                }

                if ('invoice' === $type && empty($queryVars['_billing_choice_type'])) {
                    // Treat empty or missing choice_type as invoice to avoid excluding legacy orders.
                    $query['meta_query'][] = array(
                        'relation' => 'OR',
                        array(
                            'key'   => '_billing_choice_type',
                            'value' => esc_attr($type),
                        ),
                        array(
                            'key'     => '_billing_choice_type',
                            'compare' => 'NOT EXISTS',
                        ),
                        array(
                            'key'     => '_billing_choice_type',
                            'value'   => '',
                            'compare' => '=',
                        ),
                    );
                }

                return $query;
            }, 10, 2);

        $ordersList = array();
        try {
            $summaries = array();

            if ($orderToSearch) {
                if (! empty($provider) && 'all' !== $provider) {
                    $args = array_merge($args, ['provider' => $provider]);
                }

                $orders = $this->getOrdersForSearch($orderToSearch, $args);
                foreach ($orders as $order) {
                    if (! $order) {
                        continue;
                    }

                    $dateCreated = method_exists($order, 'get_date_created') && $order->get_date_created() ? $order->get_date_created()->getTimestamp() : 0;
                    $dateModified = method_exists($order, 'get_date_modified') && $order->get_date_modified() ? $order->get_date_modified()->getTimestamp() : 0;
                    $dateCompleted = method_exists($order, 'get_date_completed') && $order->get_date_completed() ? $order->get_date_completed()->getTimestamp() : 0;

                    $summary = array(
                        'id'             => (int)$order->get_id(),
                        'provider'       => $order->get_provider(),
                        'order_provider' => $order->get_provider(),
                        'provider_label' => $order->get_provider_label(),
                        'document_type'  => $order->get_type(),
                        'order_type'     => $order->get_type(),
                        'parent_id'      => (int)$order->get_parent_id(),
                        'status'         => (string)$order->get_status(),
                        'date_created'   => $dateCreated,
                        'date_modified'  => $dateModified,
                        'date_completed' => $dateCompleted,
                        'invoice_number' => (string)$order->get_order_number_invoice(),
                        'invoice_sent'   => $order->get_invoice_sent(),
                        'choice_type'    => (string)$order->get_billing_choice_type(),
                        'invoice_type'   => (string)$order->get_invoice_type(),
                        'total'          => (float)$order->get_total(),
                        'total_tax'      => method_exists($order, 'get_total_tax') ? (float)$order->get_total_tax() : 0.0,
                        'total_refunded' => (float)$order->get_total_refunded(),
                        'sort_date'      => $dateCompleted ?: $dateCreated ?: $dateModified,
                    );
                    $summary['document_key'] = $this->buildSummaryListKey($summary);

                    $summaries[$this->buildSummaryListKey($summary)] = $summary;
                }
            } else {
                $summaryResults = OrderQuery::instance()->getProvidersOrderSummaries($args);
                $summaries      = $this->filterOrderSummaries($summaryResults, $type, $provider);
            }

            $level = \WcElectronInvoice\Wizard::popGetUserLevel();
            if (\WcElectronInvoice\Wizard::isUserLevelGte($level, 'growth')) {
                $counterNotInCache = 0;
                if ($dateIN && $dateOUT) {
                    foreach ($summaries as $summary) {
                        $order = $this->hydrateSummaryDocument($summary);
                        if (! $order) {
                            continue;
                        }

                        $counterNotInCache = xmlNoInCache($order, $counterNotInCache);
                        if ($counterNotInCache > 100) {
                            break;
                        }
                    }
                }

                if ($dateIN && $dateOUT && $counterNotInCache > 100) {
                    \WcElectronInvoice\Functions\showAdminNotice(
                        sprintf(
                            __('<span class="dashicons dashicons-warning"></span> <b>%1$s documents</b> were detected as not present in the cache. They will be generated automatically during the bulk XML file download, consuming <b>%1$s credits</b>.'),
                            $counterNotInCache
                        ),
                        'warning',
                        '',
                        null,
                        true
                    );
                }
            }

            uasort($summaries, array($this, 'reorder'));
            self::$filteredListIndex = $summaries;
            self::$filteredListCount = count($summaries);
            $this->totalItems        = self::$filteredListCount;

            $persistedIndex = array();
            foreach (array_values($summaries) as $persistedSummary) {
                if (empty($persistedSummary['order_provider']) && ! empty($persistedSummary['provider'])) {
                    $persistedSummary['order_provider'] = $persistedSummary['provider'];
                }
                if (empty($persistedSummary['order_type']) && ! empty($persistedSummary['document_type'])) {
                    $persistedSummary['order_type'] = $persistedSummary['document_type'];
                }
                $persistedIndex[] = (object)$persistedSummary;
            }
            if ($dateIN || $dateOUT || $type || $state || $orderToSearch || (! empty($provider) && 'all' !== $provider)) {
                update_option('temp_order_filtered', $persistedIndex, false);
            } else {
                update_option('temp_order_filtered', ! empty($persistedIndex) ? $persistedIndex : array(), false);
            }

            if ($onlyCount) {
                return self::$filteredListCount;
            }

            $currentPage   = max(1, $this->get_pagenum());
            $offset        = ($currentPage - 1) * self::LIST_PAGINATION;
            $pageSummaries = array_slice(array_values($summaries), $offset, self::LIST_PAGINATION);
            $ordersList    = $this->buildRowsFromSummaries($pageSummaries);

            return apply_filters('wc_el_inv-list-table-orders-list', $ordersList);
        } catch (\Exception $e) {
            echo esc_html__("No orders found: ", WC_EL_INV_TEXTDOMAIN) . esc_html($e->getMessage());
            // echo more details, file, line...
            echo esc_html($e->getFile()) . ' ' . esc_html($e->getLine());

            return $ordersList;
        }
    }

    /**
     * Set orders for list
     *
     * @param $orders
     *
     * @return mixed
     */
    public function filterOrders(
        $orders,
        $state = '',
        $type = '',
        $dateIN = '',
        $dateOUT = '',
        $orderToSearch = '',
        $provider = ''
    ) {

        // search an order by order id or invoice number
        // and return the result
        if ($orderToSearch) {
            return $this->filterOrderToSearch($orders, $orderToSearch);
        }

        $orders = $this->filterNormalOrders($orders, $state, $type, $dateIN, $dateOUT, $orderToSearch, $provider);
        $orders = $this->filterRefundedOrders($orders, $state, $type, $dateIN, $dateOUT, $orderToSearch, $provider);

        return $orders;
    }

    /**
     * Prepare query args
     *
     * @param $state
     * @param $type
     * @param $dateIN
     * @param $dateOUT
     *
     * @return array|mixed|null
     * @throws \Exception
     */
    private function prepareQueryArgs($state, $type, $dateIN, $dateOUT)
    {
        // Order state
        if ($state) {
            $status = array($state); // completed, processing, refunded
            if ('completed' === $state) {
                $paramData = 'date_completed';
            } else {
                $paramData = 'date_created';
            }
        } else {
            // Default status
            $status    = OrdersProviderAbstract::ORDERS_DEFAULT_LIST_STATUSES;
            $paramData = 'date_created';
        }

        $args = array(
            'status'  => $status,
            'limit'   => apply_filters('wc_el_inv-list-orders-limit-max', 500),
            'orderby' => 'date',
            'order'   => 'DESC',
            'return'  => 'ids',
        );

        // Set args type
        if ($type && '' !== $type && 'receipt' !== $type && 'invoice' !== $type) {
            $args['type'] = "{$type}";
        }

        // Set dates args
        if ($dateIN || $dateOUT) {
            $timeZone = new TimeZone();
            $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
            $nowIn    = new \DateTime('now');
            $nowOut   = new \DateTime('now');
            $nowIn->setTimezone($timeZone);
            $nowOut->setTimezone($timeZone);

            if ('NaN' !== $dateIN) {
                $dateIN = $nowIn->setTimestamp($dateIN);
            }
            if ('NaN' !== $dateOUT) {
                $dateOUT = $nowOut->setTimestamp($dateOUT);
            }

            $dateINTime  = $dateIN instanceof \DateTime ? $dateIN->getTimestamp() : 0;
            $dateOUTTime = $dateOUT instanceof \DateTime ? $dateOUT->getTimestamp() : 0;

            if (isset($dateINTime) && 0 !== $dateINTime && ! $dateOUTTime) {
                $args[$paramData] = ">{$dateINTime}";
            } elseif (! $dateINTime && isset($dateOUTTime) && 0 !== $dateOUTTime) {
                $args[$paramData] = "<{$dateOUTTime}";
            } elseif (isset($dateINTime) && 0 !== $dateINTime && isset($dateOUTTime) && 0 !== $dateOUTTime) {
                $args[$paramData] = "{$dateINTime}...{$dateOUTTime}";
            }

            // Equal date
            if ($dateINTime === $dateOUTTime) {
                $date                 = date('Y-m-d', intval($dateINTime));
                $args['date_created'] = "{$date}";
            }
        }

        $args['limit'] = $this->resolveDynamicListQueryLimit(
            isset($dateINTime) ? (int)$dateINTime : 0,
            isset($dateOUTTime) ? (int)$dateOUTTime : 0
        );

        /**
         * Filters $args
         */
        $args = apply_filters('wc_el_inv-xml_list_query_args', $args);

        return $args;
    }

    /**
     * Resolve a bounded provider cap for the merged XML list query.
     *
     * The cap remains uniform across providers for the same request so the
     * cross-provider merge stays unbiased.
     *
     * @param int $dateINTime
     * @param int $dateOUTTime
     *
     * @return int
     */
    private function resolveDynamicListQueryLimit($dateINTime = 0, $dateOUTTime = 0)
    {
        $baseLimit         = (int) apply_filters('wc_el_inv-list-orders-limit-max', 500);
        $recentWindowDays  = max(1, (int) apply_filters('wc_el_inv-list-orders-recent-window-days', 12));
        $recentWindowLimit = max($baseLimit, (int) apply_filters('wc_el_inv-list-orders-recent-window-limit', 1000));
        $pageFactor        = max(1, (int) apply_filters('wc_el_inv-list-orders-page-limit-factor', 2));
        $hardMax           = max($recentWindowLimit, (int) apply_filters('wc_el_inv-list-orders-limit-hard-max', 1500));
        $currentPage       = max(1, (int) $this->get_pagenum());
        $pageLimit         = $currentPage * self::LIST_PAGINATION * $pageFactor;
        $limit             = max($baseLimit, $pageLimit);

        if ($dateINTime > 0 && $dateOUTTime > 0) {
            $rangeSeconds = abs($dateOUTTime - $dateINTime);
            if ($rangeSeconds <= ($recentWindowDays * DAY_IN_SECONDS)) {
                $limit = max($limit, $recentWindowLimit);
            }
        }

        $limit = min($limit, $hardMax);

        return max(self::LIST_PAGINATION, $limit);
    }

    /**
     * Filters order by order id or invoice number
     *
     * @param OrderAbstract[] $orders
     * @param                 $orderToSearch
     *
     * @return array
     */
    private function filterOrderToSearch($orders, $orderToSearch)
    {

        $ordersResult = [];

        if (empty($orderToSearch)) {
            return $orders;
        }

        $orderToSearch = is_numeric($orderToSearch) ? intval($orderToSearch) : $orderToSearch;

        // ordersToSearch is order id or invoice number
        foreach ($orders as $index => $order) {

            // search by order id
            if ((int)$order->get_id() === $orderToSearch) {
                $ordersResult[] = $order;
                continue;
            }

            // search by invoice number
            if ((int)$order->get_order_number_invoice() === $orderToSearch) {
                $ordersResult[] = $order;
                continue;
            }

            // search by meta refund_number_invoice-$orderToSearch
            if (
                is_subclass_of($order, '\WcElectronInvoice\Providers\Order')
                && ! is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')
                && method_exists($order, 'get_refunds')
            ) {
                $refunds = $order->get_refunds() ?: array();
                foreach ($refunds as $refund) {
                    if ((int)$refund->get_meta("refund_number_invoice-{$refund->get_id()}") === $orderToSearch) {
                        $ordersResult[] = $refund;
                        continue 2;
                    }
                }
            }

            // search by wfc-issued_invoice_number
            if ((int)$order->get_meta('wfc-issued_invoice_number') === $orderToSearch) {
                $ordersResult[] = $order;
                continue;
            }

            unset($orders[$index]);
        }

        return $ordersResult;
    }

    /**
     * Merge orders keeping a single entry per document key.
     *
     * @param array $ordersById
     * @param array $ordersToAdd
     *
     * @return array
     */
    private function mergeOrdersById(array $ordersById, array $ordersToAdd)
    {
        foreach ($ordersToAdd as $order) {
            $ordersById[$this->buildDocumentListKey($order)] = $order;
        }

        return $ordersById;
    }

    /**
     * Retrieve orders in search mode without loading the full list
     *
     * @param string|int $orderToSearch
     * @param array      $args
     *
     * @return array
     */
    private function getOrdersForSearch($orderToSearch, array $args = array())
    {
        $orders        = array();
        $ordersById    = array();
        $searchLimit   = apply_filters('wc_el_inv-list-orders-search-limit', 100);
        $searchMaxPage = apply_filters('wc_el_inv-list-orders-search-max-pages', 10);

        // Keep status/date filters but cap results per page.
        $args['limit'] = $searchLimit;

        // Try direct lookup by order id first.
        if (is_numeric($orderToSearch)) {
            $directArgs = array_merge($args, array(
                'include' => array((int)$orderToSearch),
            ));

            $orders     = OrderQuery::instance()->getProvidersOrders($directArgs);
            $orders     = $this->filterOrderToSearch($orders, $orderToSearch);
            $ordersById = $this->mergeOrdersById($ordersById, $orders);
        }

        // Try meta queries for known invoice numbers without scanning all orders.
        $metaArgs = array_merge($args, array(
            'meta_query' => array(
                'relation' => 'OR',
                array(
                    'key'     => 'order_number_invoice',
                    'value'   => $orderToSearch,
                    'compare' => '=',
                ),
                array(
                    'key'     => 'wfc-issued_invoice_number',
                    'value'   => $orderToSearch,
                    'compare' => '=',
                ),
            ),
        ));

        $orders     = OrderQuery::instance()->getProvidersOrders($metaArgs);
        $orders     = $this->filterOrderToSearch($orders, $orderToSearch);
        $ordersById = $this->mergeOrdersById($ordersById, $orders);
        if (! empty($ordersById)) {
            return array_values($ordersById);
        }

        // Fallback to paginated scan limited by filters to avoid timeouts.
        $page = 1;
        do {
            $pagedArgs = array_merge($args, array(
                'paged' => $page,
            ));
            $batch     = OrderQuery::instance()->getProvidersOrders($pagedArgs);

            if (empty($batch)) {
                break;
            }

            $filtered   = $this->filterOrderToSearch($batch, $orderToSearch);
            $ordersById = $this->mergeOrdersById($ordersById, $filtered);

            $page++;
        } while ($page <= $searchMaxPage);

        return array_values($ordersById);
    }

    /**
     * Filter Orders subclass of Order
     *
     * @param $orders
     * @param $state
     * @param $type
     * @param $dateIN
     * @param $dateOUT
     * @param $orderToSearch
     *
     * @return mixed
     * @throws \Exception
     */
    private function filterNormalOrders(
        $orders,
        $state = '',
        $type = '',
        $dateIN = '',
        $dateOUT = '',
        $orderToSearch = '',
        $provider = ''
    ) {

        foreach ($orders as $index => $order) {
            if (is_subclass_of($order, '\WcElectronInvoice\Providers\Order')) {
                // Remove different provider from list
                if (! empty($provider) && 'all' !== $provider && $provider !== $order->get_provider()) {
                    unset($orders[$index]);
                }
                $checkSent        = $order->get_invoice_sent() === 'sent' ? true : false;
                $hasIssuedInvoice = (int)$order->get_order_number_invoice() > 0;
                // Remove receipt if filter meta choice_type is't receipt
                // Get meta order choice type
                if ('receipt' === $type) {
                    $meta = $order->get_billing_choice_type();
                    if ('receipt' !== $meta) {
                        unset($orders[$index]);
                    }
                }

                // Remove receipt if filter choice_type is't invoice
                // Get meta order choice type
                elseif ('invoice' === $type) {
                    $meta = $order->get_billing_choice_type();
                    $meta = empty($meta) ? 'invoice' : $meta;
                    if ('invoice' !== $meta) {
                        unset($orders[$index]);
                    }
                }

                // Remove if disable choice type
                $choiceTypeCheck = OptionPage::init()->getOptions('invoice_choice_type');
                if ('on' !== $choiceTypeCheck) {
                    $meta = $order->get_billing_choice_type();
                    if ('receipt' === $meta) {
                        unset($orders[$index]);
                    }
                }

                // Disable invoice order whit total zero
                if (! $checkSent && disableInvoiceOnOrderTotalZero($order)) {
                    // Unset order
                    // Invoice order total is zero
                    unset($orders[$index]);
                }

                // Unset order
                // Hide fully refunded parent orders only when no emitted invoice exists.
                if (! $hasIssuedInvoice &&
                    floatval($order->get_total()) === floatval($order->get_total_refunded()) &&
                    'refunded' === $order->get_status()
                ) {
                    unset($orders[$index]);
                }

                // Unset refund
                // Check for remove order refund from list
                if (method_exists($order, 'get_refunds')) {
                    $refunds = $order->get_refunds();
                    if (! empty($refunds)) {
                        foreach ($refunds as $indexRefund => $refund) {

                            if (! $hasIssuedInvoice) {
                                // No sent Invoice remove refund from list
                                unset($refunds[$indexRefund]);
                            }

                            // No sent and zero is diff from order total and total refunded
                            // Order totally refunded and not sent
                            if (! $hasIssuedInvoice &&
                                0.00 === (abs($order->get_total()) - abs($refund->get_total()))
                            ) {
                                unset($refunds[$indexRefund]);
                            } // No issued invoice and order id equal refund id
                            elseif (! $hasIssuedInvoice && $refund->get_parent_id() === $order->get_id()) {
                                unset($refunds[$indexRefund]);
                            }
                        }
                    }
                }

                // Force date completed for filter invoice
                if (null === $order->get_date_completed()) {
                    $timeZone = new TimeZone();
                    $timeZone = new \DateTimeZone($timeZone->getTimeZone()->getName());
                    $date     = new \DateTime($order->get_date_modified());
                    $date->setTimezone($timeZone);

                    // Save date completed
                    $order->set_date_completed($date->getTimestamp());

                    // Save
                    $order->save();
                }
            }
        }

        return $orders;
    }

    /**
     * Filter Orders subclass of OrderRefund
     *
     * @param $orders
     * @param $state
     * @param $type
     * @param $dateIN
     * @param $dateOUT
     * @param $orderToSearch
     *
     * @return mixed
     */
    private function filterRefundedOrders(
        $orders,
        $state = '',
        $type = '',
        $dateIN = '',
        $dateOUT = '',
        $orderToSearch = '',
        $provider = ''
    ) {

        $incrementRefund = 0;
        foreach ($orders as $index => $order) {
            // Unset order refund
            // Check for remove order refund from list
            if (is_subclass_of($order, '\WcElectronInvoice\Providers\OrderRefund')) {
                // Remove different provider from list
                if (! empty($provider) && 'all' !== $provider && $provider !== $order->get_provider()) {
                    unset($orders[$index]);
                }
                // Get check order sent meta time
                $checkOrderSentTime = $order->get_invoice_sent_timestamp($order->get_parent_id());
                $checkOrderSentTime = isset($checkOrderSentTime) && '' !== $checkOrderSentTime ? $checkOrderSentTime : false;
                // Get check order sent meta
                $checkSent   = $order->get_invoice_sent($order->get_parent_id());
                $checkSent   = isset($checkSent) && 'sent' === $checkSent ? true : false;
                $parentOrder = \WcElectronInvoice\Functions\resolveProviderDocument(
                    $order->get_parent_id(),
                    $order->get_provider(),
                    'shop_order',
                    $order->get_parent_id()
                );
                if (! $parentOrder) {
                    unset($orders[$index]);
                    continue;
                }
                $parentHasIssuedInvoice = (int)$parentOrder->get_order_number_invoice() > 0;
                // Get refund sent meta
                $checkRefundSent = $order->get_invoice_sent($order->get_id());
                $checkRefundSent = isset($checkRefundSent) && 'sent' === $checkRefundSent ? true : false;
                // Get refund sent meta time
                $invoiceRefundSentTime = $order->get_invoice_sent_timestamp($order->get_id());
                $invoiceRefundSentTime = isset($invoiceRefundSentTime) && '' !== $invoiceRefundSentTime ? $invoiceRefundSentTime : false;
                $refundNumber          = (int)$order->get_meta("refund_number_invoice-{$order->get_id()}");

                // Remove refund when search invoice
                if ('invoice' === $type) {
                    unset($orders[$index]);
                }

                if (! $parentHasIssuedInvoice || $refundNumber < 1) {
                    // Without parent invoice emission or refund numeration, do not expose refund row.
                    unset($orders[$index]);
                }

                // If the invoice time stamp is greater than the repayment time, it means that the
                // refund was generated before the invoice was sent.
                if ($checkOrderSentTime && $invoiceRefundSentTime && (intval($checkOrderSentTime) > intval($invoiceRefundSentTime))) {
                    unset($orders[$index]);
                }

                $totalRefund = abs($parentOrder->get_total_refunded());
                $refunds     = $parentOrder->get_refunds() ?: [];
                if (! $parentHasIssuedInvoice && floatval(0) === (abs($parentOrder->get_total()) - $totalRefund)) {
                    // No sent Invoice and zero is diff from order total and total refunded
                    unset($orders[$index]);
                } elseif (! $parentHasIssuedInvoice && $order->get_parent_id() === $order->get_id()) {
                    // No sent Invoice and order id equal refund id
                    unset($orders[$index]);
                } elseif ((! $parentHasIssuedInvoice && ! $checkRefundSent) && floatval(0) === (abs($parentOrder->get_total()) - $totalRefund)) {
                    // No sent Invoice and Refund Total refund order
                    unset($orders[$index]);
                } elseif ($checkSent && ! $checkRefundSent) {
                    // If invoice sent and current refund not sent refund isset multi refund
                    $numberRefund = count($refunds);
                    if ($numberRefund > 1) {
                        // Last refund
                        $sent = $order->get_invoice_sent($order->get_id());
                        // If last refund not sent and isset other refunds continue else unset order
                        if (('no_sent' === $sent || '' === $sent) &&
                            isset($refunds[$index - $incrementRefund]) &&
                            (is_subclass_of($refunds[$index - $incrementRefund],
                                    '\WcElectronInvoice\Providers\OrderRefund') ||
                             is_subclass_of($refunds[$index - $incrementRefund], '\WC_Order_Refund'))) {
                            continue;
                        } else {
                            unset($orders[$index]);
                        }
                    } elseif ($numberRefund === 1) {
                        // Refund
                        $sent = $order->get_invoice_sent($order->get_id());
                        // Invoice
                        $sentParent = $order->get_invoice_sent($parentOrder->get_id());
                        if (! $sentParent && 'no_sent' === $sent &&
                            isset($refunds[$index - $incrementRefund]) && is_subclass_of($refunds[$index - $incrementRefund],
                                '\WcElectronInvoice\Providers\OrderRefund')
                        ) {
                            unset($orders[$index]);
                        }
                    }
                }
            }
        }

        return $orders;
    }

    /**
     * @inheritdoc
     */
    public function column_cb($item)
    {
        if (is_subclass_of($item['provider'], 'WcElectronInvoice\Providers\OrdersProviderAbstract')) {
            $provider = $item['provider']->name();
        } else {
            $provider = $item['provider'];
        }

        return sprintf('<input type="checkbox" name="%1$s[]" value="%2$s" />',
            $this->_args['singular'],
            sprintf(
                '%1$d,%2$s,%3$s,%4$d',
                $item['id'],
                $provider,
                isset($item['document_type']) ? $item['document_type'] : (isset($item['order_type']) ? $item['order_type'] : 'shop_order'),
                isset($item['parent_id']) ? (int)$item['parent_id'] : 0
            )
        );
    }

    /**
     * Process Bulk
     */
    public function processBulkAction()
    {
        if (isset($_POST['pop-invoice'])) {

            $action = \WcElectronInvoice\Functions\filterInput($_POST, 'bulk-sent', FILTER_UNSAFE_RAW);

            $args['pop-invoice'] = array(
                'filter' => array(FILTER_UNSAFE_RAW),
                'flags'  => FILTER_FORCE_ARRAY,
            );

            $invoice = filter_var_array($_POST, $args);

            if (! empty($invoice)) {
                foreach ($invoice as $cbs) {
                    foreach ($cbs as $cbIndex => $id) {

                        $id = explode(',', $id);
                        if (isset($id[0]) && isset($id[1])) {
                            $order_id     = intval($id[0]);
                            $provider     = $id[1];
                            $documentType = isset($id[2]) ? sanitize_key((string)$id[2]) : 'shop_order';
                            $parentId     = isset($id[3]) ? intval($id[3]) : 0;

                            $order = \WcElectronInvoice\Functions\resolveProviderDocument($order_id, $provider,
                                $documentType, $parentId);
                            if (! $order) {
                                continue;
                            }

                            switch ($action) {
                                case 'sent':
                                    $order->set_invoice_sent('sent');

                                    // Save
                                    $order->save();
                                    break;
                                case 'no_sent':
                                    $order->set_invoice_sent('no_sent');

                                    // Save
                                    $order->save();
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * @inheritdoc
     */
    public function get_columns()
    {
        $columns = array(
            'cb'             => '<input type="checkbox" />',
            'id'             => esc_html__('Order', WC_EL_INV_TEXTDOMAIN),
            'total'          => esc_html__('Order Total', WC_EL_INV_TEXTDOMAIN),
            'invoice_number' => esc_html__('Number', WC_EL_INV_TEXTDOMAIN),
            'provider_label' => esc_html__('Provider', WC_EL_INV_TEXTDOMAIN),
            'choice_type'    => esc_html__('Doc. required', WC_EL_INV_TEXTDOMAIN),
            'invoice_type'   => esc_html__('Customer Type', WC_EL_INV_TEXTDOMAIN),
            'data_order'     => esc_html__('Order data', WC_EL_INV_TEXTDOMAIN),
            'invoice_sent'   => esc_html__('Sent', WC_EL_INV_TEXTDOMAIN),
            'actions'        => esc_html__('Actions', WC_EL_INV_TEXTDOMAIN),
        );

        return apply_filters('wc_el_inv-xml_list_columns', $columns);
    }

    /**
     * @inheritdoc
     */
    public function get_sortable_columns()
    {
        $sortableColumns = array(
            'id'             => array('id', false),
            'total'          => array('total', false),
            'invoice_number' => array('invoice_number', true),
            'provider_label' => array('provider_label', true),
            'choice_type'    => array('choice_type', true),
            'invoice_type'   => array('invoice_type', true),
            'invoice_sent'   => array('invoice_sent', true),
        );

        $sortableColumns = apply_filters('wc_el_inv-xml_list_sortable_columns', $sortableColumns);

        return $sortableColumns;
    }

    /**
     * @inheritdoc
     */
    public function prepare_items()
    {
        $columns               = $this->get_columns();
        $hidden                = array();
        $sortable              = $this->get_sortable_columns();
        $this->_column_headers = array($columns, $hidden, $sortable);

        $this->items = array_map(function ($item, $index) {
            return array_merge($item, ['index' => $index]);
        }, $this->data, array_keys($this->data));

        $this->set_pagination_args(array(
            'total_items' => $this->totalItems,
            'per_page'    => self::LIST_PAGINATION,
        ));
    }

    /**
     * Generates the table navigation above or below the table
     *
     * @param string $which
     *
     * @since 3.1.0
     */
    protected function display_tablenav($which)
    {
        if ('top' === $which) {
            wp_nonce_field('bulk-' . $this->_args['plural']);
        }
        ?>
        <div class="tablenav <?php echo esc_attr($which); ?>">

            <?php if ($this->has_items()) : ?>
                <div class="alignleft actions bulkactions">
                    <?php XmlOrdersList::buttonBulkAction(); ?>
                </div>
            <?php
            endif;
            $this->extra_tablenav($which);
            $this->pagination($which);
            ?>

            <br class="clear"/>
        </div>
        <?php
    }

    /**
     * @inheritdoc
     */
    public function column_default($item, $columnName)
    {
        switch ($columnName) {
            case 'id':
                return $this->orderTitle($item);
            case 'total':
                return $this->orderTotal($item);
            case 'invoice_number':
                return $this->invoiceNumber($item);
            case 'invoice_type':
                return $this->customerType($item);
            case 'choice_type':
                return isset($item[$columnName]) ? $this->choiceType($item) : '';
            case 'provider_label':
                return isset($item[$columnName]) ? $item[$columnName] : ' - ';
            case 'data_order':
                $data = sprintf('<strong>%s:</strong> %s<br>',
                    esc_html__('VAT number', WC_EL_INV_TEXTDOMAIN),
                    $this->customerVatOrSdi($item, 'vat_number')
                );
                $data .= sprintf('<strong>%s:</strong> %s<br>',
                    esc_html__('Tax Code', WC_EL_INV_TEXTDOMAIN),
                    $this->customerVatOrSdi($item, 'tax_code')
                );
                $data .= sprintf('<strong>%s:</strong> %s<br>',
                    esc_html__('SdI or PEC', WC_EL_INV_TEXTDOMAIN),
                    $this->customerVatOrSdi($item, 'sdi_type')
                );
                $data .= sprintf('<hr><strong>%s:</strong> %s',
                    esc_html__('Payment method', WC_EL_INV_TEXTDOMAIN),
                    paymentMethodCode((object)$item)
                );

                return $data;
            case 'invoice_sent':
                return $this->sentInvoice($item);
            case 'actions':
                return isset($item['id']) ? $this->actions($item['id'], $item) : '';
            case $columnName:
                return apply_filters('wc_el_inv-xml_list_column', $item, $columnName);
            default:
                return print_r($item, true);
        }
    }
}
