<?php

if (! defined("ROOT_PATH")) {

    header("HTTP/1.1 403 Forbidden");

    exit;
}

class pjInvoice extends pjInvoiceAppController
{

    public $invoiceErrors = 'InvoiceErrors';

    private function sortTimezones(array $array)
    {

        $ordered = [];

        $orderArray = ['-43200', '-39600', '-36000', '-32400', '-28800', '-25200', '-21600', '-18000', '-14400', '-10800', '-7200', '-3600', '0', '3600', '7200', '10800', '14400', '18000', '21600', '25200', '28800', '32400', '36000', '39600', '43200', '46800'];

        foreach ($orderArray as $key) {

            if (array_key_exists($key, $array)) {

                $ordered[$key] = $array[$key];

                unset($array[$key]);
            }
        }

        return $ordered + $array;
    }

    public function pjActionCreate()
    {

        $params = $this->getParams();

        if (! isset($params['key']) || $params['key'] != md5($this->option_arr['private_key'] . PJ_SALT)) {

            return ['status' => 'ERR', 'code' => '101', 'text' => 'Key is not set or invalid'];
        }

        $locale_id = isset($params['locale_id']) && ! empty($params['locale_id']) ? $params['locale_id'] : $this->getLocaleId();

        $allowed = array(
            'uuid', 'order_id', 'foreign_id', 'locale_id', 'issue_date', 'due_date',
            'created', 'modified', 'status', 'payment_method', 'txn_id', 'processed_on',
            'price', 'discount', 'insurance', 'shipping', 'tax', 'total', 'currency', 'notes',
            'b_name', 'b_country_id', 'b_state', 'b_city', 'b_zip', 'b_address_1', 'b_address_2',
            's_name', 's_country_id', 's_state', 's_city', 's_zip', 's_address_1', 's_address_2'
        );
        $data = array();
        foreach ($allowed as $key) {
            if (array_key_exists($key, $params)) {
                $data[$key] = $params[$key];
            }
        }

        $invoice_id = pjInvoiceModel::factory($data)->insert()->getInsertId();

        if ($invoice_id !== false && (int) $invoice_id > 0) {

            if (isset($params['items']) && is_array($params['items']) && ! empty($params['items'])) {

                $pjInvoiceItemModel = pjInvoiceItemModel::factory();

                foreach ($params['items'] as $item) {

                    $item['invoice_id'] = $invoice_id;

                    $pjInvoiceItemModel->reset()->setAttributes($item)->insert();
                }
            }
            // die;
            return ['status' => 'OK', 'code' => '200', 'text' => 'Invoice has been created.', 'data' => array_merge($data, ['id' => $invoice_id])];
        } else {

            return ['status' => 'ERR', 'code' => '100', 'text' => 'Invoice has not been created.'];
        }
    }

    public function pjActionDelete()
    {

        $this->setAjax(true);

        if ($this->isXHR() && $this->isLoged() && $this->isInvoiceReady()) {

            $response = [];

            if (pjInvoiceModel::factory()->set('id', $_REQUEST['id'])->erase()->getAffectedRows() == 1) {

                pjInvoiceItemModel::factory()->where('invoice_id', $_REQUEST['id'])->eraseAll();

                // $response['code'] = 200;
                self::jsonResponse(['status' => 'OK', 'code' => 200, 'text' => 'Order has been deleted']);
            } else {
                self::jsonResponse(['status' => 'ERR', 'code' => 105, 'text' => 'Order has not been deleted.']);

                // $response['code'] = 100;

            }

            pjAppController::jsonResponse($response);
        }

        exit;
    }

    public function pjActionDeleteBulk()
    {

        $this->setAjax(true);

        if ($this->isXHR() && $this->isLoged() && $this->isInvoiceReady()) {

            if (isset($_REQUEST['record']) && count($_REQUEST['record']) > 0) {

                pjInvoiceModel::factory()->whereIn('id', $_REQUEST['record'])->eraseAll();

                pjInvoiceItemModel::factory()->whereIn('invoice_id', $_REQUEST['record'])->eraseAll();
                self::jsonResponse(['status' => 'OK', 'code' => 200, 'text' => 'Order has been deleted']);
            }
        }

        exit;
    }

    public function pjActionDeleteLogo()
    {

        $this->setAjax(true);

        if ($this->isXHR() && $this->isLoged() && $this->isInvoiceReady()) {

            $pjInvoiceConfigModel = pjInvoiceConfigModel::factory();

            $arr = $pjInvoiceConfigModel->find(1)->getData();

            if (! empty($arr) && ! empty($arr['y_logo'])) {

                @clearstatcache();

                if (is_file($arr['y_logo'])) {

                    @unlink($arr['y_logo']);
                }

                $pjInvoiceConfigModel->set('id', 1)->modify(['y_logo' => ':NULL']);
                self::jsonResponse(['status' => 'OK', 'code' => 200, 'text' => 'Order has been deleted']);
            }
        }

        exit;
    }

    public function pjActionGetInvoices()
    {
        // ini_set('display_errors', '1');
        // ini_set('display_startup_errors', '1');
        // error_reporting(E_ALL);
        $this->setAjax(true);

        if ($this->isXHR() && $this->isLoged() && $this->isInvoiceReady()) {

            $pjInvoiceModel = pjInvoiceModel::factory()
                ->select('t1.*, t2.id AS order_db_id, t2.client_id, t3.email AS client_email, t3.client_name')
                ->join('pjOrder', 't2.uuid=t1.order_id', 'left outer')
                ->join('pjClient', 't3.id=t2.client_id', 'left outer');

            if (isset($_REQUEST['foreign_id'])) {

                $foreign_arr = $this->get('foreign_arr');

                if ((int) $_REQUEST['foreign_id'] > 0 && $foreign_arr !== false && ! empty($foreign_arr)) {

                    $pjInvoiceModel->where('t1.foreign_id', $_REQUEST['foreign_id']);
                }
            }

            if (isset($_REQUEST['q']) && ! empty($_REQUEST['q'])) {

                $q = $pjInvoiceModel->escapeStr($_REQUEST['q']);

                $q = str_replace(['%', '_'], ['\%', '\_'], $q);

                $pjInvoiceModel

                    ->where('t1.uuid LIKE', "%$q%")

                    ->orWhere('t1.order_id LIKE', "%$q%")

                    ->orWhere('t1.b_name LIKE', "%$q%")

                    ->orWhere('t3.email LIKE', "%$q%")

                    ->orWhere('t3.client_name LIKE', "%$q%")

                    ->orWhere('t1.s_name LIKE', "%$q%")

                ;
            }

            // Pillar 5: status filter
            if (isset($_REQUEST['status']) && in_array($_REQUEST['status'], ['paid', 'not_paid', 'cancelled'], true)) {
                $pjInvoiceModel->where('t1.status', $_REQUEST['status']);
            }

            $column = 'created';

            $direction = 'DESC';

            if (isset($_REQUEST['direction']) && isset($_REQUEST['column']) && in_array(strtoupper($_REQUEST['direction']), ['ASC', 'DESC'])) {

                $column = $_REQUEST['column'];

                $direction = strtoupper($_REQUEST['direction']);
            }

            $total = $pjInvoiceModel->findCount()->getData();

            $rowCount = isset($_REQUEST['rowCount']) && (int) $_REQUEST['rowCount'] > 0 ? (int) $_REQUEST['rowCount'] : 10;

            $pages = ceil($total / $rowCount);

            $page = isset($_REQUEST['page']) && (int) $_REQUEST['page'] > 0 ? intval($_REQUEST['page']) : 1;

            $offset = ((int) $page - 1) * $rowCount;

            if ($page > $pages) {

                $page = $pages;
            }

            $data = $pjInvoiceModel->orderBy("`$column` $direction")->limit($rowCount, $offset)->findAll()->getData();

            foreach ($data as $k => $v) {

                $data[$k]['total_formated'] = pjCurrency::formatPrice($v['total']);

                // Customer display uses the current order/client relation, not copied invoice e-mail fields.
                $name = trim((string) ($v['b_name'] ?? ''));
                $clientName = trim((string) ($v['client_name'] ?? ''));
                $email = trim((string) ($v['client_email'] ?? ''));
                $data[$k]['b_email'] = $email;
                $data[$k]['customer_label'] = $name !== '' ? $name : $email;
                if ($data[$k]['customer_label'] === '' && $clientName !== '') {
                    $data[$k]['customer_label'] = $clientName;
                }
                $data[$k]['customer_sub']   = ($name !== '' || $clientName !== '') && $email !== '' ? $email : '';
            }

            pjAppController::jsonResponse([
                'data'        => $data,
                'total'       => $total,
                'pages'       => $pages,
                'page'        => $page,
                'rowCount'    => $rowCount,
                'column'      => $column,
                'direction'   => $direction,
                'hide_loader' => true, // 👈 custom flag
            ]);

            // pjAppController::jsonResponse(compact('data', 'total', 'pages', 'page', 'rowCount', 'column', 'direction'));

        }

        exit;
    }

    public function pjActionGetItems()
    {

        $this->setAjax(true);

        if ($this->isXHR() && $this->isLoged() && $this->isInvoiceReady()) {

            $pjInvoiceItemModel = pjInvoiceItemModel::factory();

            $column = 'id';

            $direction = 'ASC';

            if (isset($_REQUEST['direction']) && isset($_REQUEST['column']) && in_array(strtoupper($_REQUEST['direction']), ['ASC', 'DESC'])) {

                $column = $_REQUEST['column'];

                $direction = strtoupper($_REQUEST['direction']);
            }

            $pjInvoiceItemModel->where('t1.id', -1);

            if (isset($_REQUEST['invoice_id']) && (int) $_REQUEST['invoice_id'] > 0) {

                $pjInvoiceItemModel->reset()->where('t1.invoice_id', $_REQUEST['invoice_id']);
            }

            if (isset($_REQUEST['tmp']) && ! empty($_REQUEST['tmp'])) {

                $pjInvoiceItemModel->reset()->where('t1.tmp', $_REQUEST['tmp']);
            }

            $data = $pjInvoiceItemModel

                ->select('t1.*, t2.currency')

                ->join('pjInvoice', 't2.id=t1.invoice_id', 'left outer')

                ->orderBy("`$column` $direction")->findAll()->getData();

            foreach ($data as $k => $v) {

                $data[$k]['unit_price_formated'] = pjCurrency::formatPrice($v['unit_price']);

                $data[$k]['amount_formated'] = pjCurrency::formatPrice($v['amount']);
            }

            pjAppController::jsonResponse([
                'data'        => $data,

                'column'      => $column,
                'direction'   => $direction,
                'hide_loader' => true, // 👈 custom flag
            ]);
        }

        exit;
    }

    public function pjActionIndex()
    {

        $this->checkLogin();

        if (! $this->isInvoiceReady()) {

            $this->set('status', 2);

            return;
        }

        if (isset($_REQUEST['invoice_post'])) {
            // Pillar 1: business details + logo are managed on Profile, no longer here.
            // This page only saves invoice-specific options (invoice number + si/qty flags).
            $data = [];
            $data['invoice_number'] = isset($_REQUEST['invoice_number']) ? str_replace(' ', '', $_REQUEST['invoice_number']) : '';

            // Pillar 4: number of digits + reset per year
            $digits = isset($_REQUEST['invoice_number_digits']) ? (int) $_REQUEST['invoice_number_digits'] : 5;
            if ($digits < 2) { $digits = 2; }
            if ($digits > 10) { $digits = 10; }
            $data['invoice_number_digits'] = $digits;
            $data['invoice_number_reset_yearly'] = isset($_REQUEST['invoice_number_reset_yearly']) ? 1 : 0;

            // Other options (si_include + o_use_qty_unit_price are the only ones left in the UI)
            $data['si_include']           = isset($_REQUEST['si_include']) ? 1 : 0;
            $data['o_use_qty_unit_price'] = isset($_REQUEST['o_use_qty_unit_price']) ? 1 : 0;

            pjInvoiceConfigModel::factory()
                ->set('id', 1)
                ->modify($data);

            pjUtil::redirect(PJ_INSTALL_URL . "index.php?controller=pjInvoice&action=pjActionIndex&err=PIN02");
        }

        $arr = pjInvoiceConfigModel::factory()->find(1)->getData();

        $this
            ->set('arr', $arr)
            ->appendJs('jquery.validate.min.js', PJ_THIRD_PARTY_PATH . 'validate/')
            ->appendJs('pjInvoice.js?v=' . time(), $this->getConst('PLUGIN_JS_PATH'))
            ->appendJs('index.php?controller=pjBase&action=pjActionMessages', PJ_INSTALL_URL, true)
        ;
    }

    public function pjActionInvoices()
    {

        $this->checkLogin();

        if (! $this->isInvoiceReady()) {

            $this->set('status', 2);

            return;
        }

        $this

            ->set('invoice_config_arr', pjInvoiceConfigModel::factory()->getConfigData($this->getLocaleId()))

            ->appendJs('jquery.datagrid.js', PJ_FRAMEWORK_LIBS_PATH . 'pj/js/')

            ->appendJs('pjInvoice.js?v=' . time(), $this->getConst('PLUGIN_JS_PATH'))

            ->appendCss('plugin_invoice.css?v=' . time(), $this->getConst('PLUGIN_CSS_PATH'))
            ->appendCss('sweetalert.css?v=' . time(), PJ_THIRD_PARTY_PATH . 'sweetalert/1.0.0/')
            ->appendJs('sweetalert.min.js?v=' . time(), PJ_THIRD_PARTY_PATH . 'sweetalert/1.0.0/')
            ->appendCss('datepicker.css?v=' . time(), PJ_THIRD_PARTY_PATH . 'bootstrap_datepicker/')

            ->appendJs('bootstrap-datepicker.js?v=' . time(), PJ_THIRD_PARTY_PATH . 'bootstrap_datepicker/')
            ->appendJs('index.php?controller=pjBase&action=pjActionMessages', PJ_INSTALL_URL, true)

        ;
    }

    public function pjActionPrint()
    {

        $this->pjActionView();
    }

    public function pjActionSend()
    {
        $isAjax = $this->isXHR();
        $this->setAjax($isAjax);
        if (!$isAjax) {
            $this->setLayout('pjActionAdmin');
        }

        if ($this->isLoged() && $this->isInvoiceReady()) {

            if ($this->_get->check('uuid')) {

                $arr = pjInvoiceModel::factory()
                    ->join('pjOrder', 't2.uuid=t1.order_id', 'left outer')
                    ->join('pjClient', 't3.id=t2.client_id', 'left outer')
                    ->select('t1.*, t3.email as b_email, t3.client_name')
                    ->where('t1.uuid', $this->_get->toString('id'))
                    ->where('t1.order_id', $this->_get->toString('uuid'))
                    ->limit(1)->findAll()->getDataIndex(0);

                $this->set('arr', $arr);
                $this->set('config_arr', pjInvoiceConfigModel::factory()->getConfigData($this->getLocaleId()));
                return;
            }

            if ($this->_post->check('uuid')) {
                $orderUuid = $this->_post->toString('uuid');
                
                $AdminOrders = new pjAdminOrders();
                $AdminOrders->option_arr = $this->option_arr;
                $response = $AdminOrders->sendOrderEmail($orderUuid, 'payment');

                if ($this->isXHR()) {
                    pjAppController::jsonResponse($response);
                }
                $this->set('send_response', $response);
                return;
            }

            if ($isAjax) {
                $this->set('arr', array());
                $this->set('config_arr', array());
                return;
            }

            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices');
        }

        if ($isAjax) {
            pjAppController::jsonResponse(['status' => 'ERR', 'code' => 403, 'text' => 'Access denied.']);
        }
        pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjBase&action=pjActionLogin');
    }

    public function pjActionTokenizer($a, $locale_id = null)
    {
        if (! isset($a['id']) || (int) $a['id'] <= 0) {
            return '';
        }

        if (is_null($locale_id)) {
            $locale_id = ! empty($a['locale_id']) ? $a['locale_id'] : $this->getLocaleID();
        }

        $items = '<table style="width:100%" border="0">';
        $items .= '<thead><tr>';
        $items .= sprintf('<th style="text-align: left; border-bottom: 1px solid #000">%s</th>', __('plugin_invoice_i_item', true));
        $items .= sprintf('<th style="text-align: right; border-bottom: 1px solid #000">%s</th>', __('plugin_invoice_i_qty', true));
        $items .= sprintf('<th style="text-align: right; border-bottom: 1px solid #000">%s</th>', __('plugin_invoice_i_unit_price', true));
        $items .= sprintf('<th style="text-align: right; border-bottom: 1px solid #000">%s</th>', __('plugin_invoice_i_amount', true));
        $items .= '</tr></thead>';

        $invoice_items = pjInvoiceItemModel::factory()->where('t1.invoice_id', $a['id'])->findAll()->getData();
        if (! empty($invoice_items)) {
            $grand_total = 0;
            foreach ($invoice_items as $item) {
                $qty         = (fmod((float) $item['qty'], 1) !== 0.0 ? number_format((float) $item['qty'], 2, '.', '') : (int) $item['qty']);
                $unit_price  = (float) $item['unit_price'];
                $item_total  = (float) $item['amount'];

                $items .= '<tr>';
                $items .= sprintf('<td style="text-align: left">%s<br/><small>%s</small></td>', pjSanitize::html($item['name']), pjSanitize::html($item['description']));
                $items .= sprintf('<td style="text-align: right">%s</td>', $qty);
                $items .= sprintf('<td style="text-align: right">%s</td>', pjCurrency::formatPrice($unit_price));
                $items .= sprintf('<td style="text-align: right">%s</td>', pjCurrency::formatPrice($item_total));
                $items .= '</tr>';
                $grand_total += $item_total;
            }

            $items .= '<tr>';
            $items .= '<td colspan="3" style="border-top: solid 1px #000; text-align: right; font-weight: bold">' . __('plugin_invoice_i_total', true) . '</td>';
            $items .= sprintf('<td style="border-top: solid 1px #000; text-align: right; font-weight: bold">%s</td>', pjCurrency::formatPrice($grand_total));
            $items .= '</tr>';
        }
        $items .= '</table>';

        $config   = pjInvoiceConfigModel::factory()->getConfigData($locale_id);
        $statuses = __('plugin_invoice_statuses', true);

        $tokens = [
            '{uuid}'            => $a['uuid'],
            '{order_id}'        => $a['order_id'],
            '{issue_date}'      => pjDateTime::formatDate($a['issue_date'], 'Y-m-d', $this->option_arr['o_date_format']),
            '{due_date}'        => pjDateTime::formatDate($a['due_date'], 'Y-m-d', $this->option_arr['o_date_format']),
            '{created}'         => ! empty($a['created']) ? date($this->option_arr['o_date_format'] . " H:i:s", strtotime($a['created'])) : '',
            '{modified}'        => ! empty($a['modified']) ? date($this->option_arr['o_date_format'] . " H:i:s", strtotime($a['modified'])) : '',
            '{status}'          => $statuses[$a['status']] ?? $a['status'],
            '{price}'           => pjCurrency::formatPrice($a['price']),
            '{discount}'        => pjCurrency::formatPrice($a['discount']),
            '{insurance}'       => pjCurrency::formatPrice($a['insurance']),
            '{shipping}'        => pjCurrency::formatPrice($a['shipping']),
            '{tax}'             => pjCurrency::formatPrice($a['tax'] ?? 0),
            '{total}'           => pjCurrency::formatPrice($a['total']),
            '{currency}'        => $a['currency'],
            '{notes}'           => $a['notes'],
            '{y_logo}'          => $a['y_logo'],
            '{y_company}'       => $config['y_company'] ?? $a['y_company'] ?? '',
            '{y_name}'          => $config['y_name'] ?? $a['y_name'] ?? '',
            '{y_street_address}' => $config['y_street_address'] ?? $a['y_street_address'] ?? '',
            '{y_country}'       => $a['y_country_title'] ?? '',
            '{y_city}'          => $config['y_city'] ?? $a['y_city'] ?? '',
            '{y_state}'         => $config['y_state'] ?? $a['y_state'] ?? '',
            '{y_zip}'           => $config['y_zip'] ?? $a['y_zip'] ?? '',
            '{y_phone}'         => $config['y_phone'] ?? $a['y_phone'] ?? '',
            '{y_fax}'           => $config['y_fax'] ?? $a['y_fax'] ?? '',
            '{y_email}'         => $config['y_email'] ?? $a['y_email'] ?? '',
            '{y_url}'           => $config['y_url'] ?? $a['y_url'] ?? '',
            '{b_name}'          => $a['b_name'],
            '{b_address_1}'     => $a['b_address_1'],
            '{b_address_2}'     => $a['b_address_2'],
            '{b_country}'       => $a['b_country_title'] ?? '',
            '{b_city}'          => $a['b_city'],
            '{b_state}'         => $a['b_state'],
            '{b_zip}'           => $a['b_zip'],
            '{s_name}'          => $a['s_name'],
            '{s_address_1}'     => $a['s_address_1'],
            '{s_address_2}'     => $a['s_address_2'],
            '{s_country}'       => $a['s_country_title'] ?? '',
            '{s_city}'          => $a['s_city'],
            '{s_state}'         => $a['s_state'],
            '{s_zip}'           => $a['s_zip'],
            '{items}'           => $items,
            // Compatibility aliases
            '{OrderID}'         => $a['order_id'],
            '{OrderUUID}'       => $a['order_id'],
            '{BillingName}'     => $a['b_name'],
            '{BillingAddress1}' => $a['b_address_1'],
            '{BillingAddress2}' => $a['b_address_2'],
            '{BillingCity}'     => $a['b_city'],
            '{BillingState}'    => $a['b_state'],
            '{BillingZip}'      => $a['b_zip'],
            '{BillingCountry}'  => $a['b_country_title'] ?? '',
            '{ShippingName}'    => $a['s_name'],
            '{ShippingAddress1}' => $a['s_address_1'],
            '{ShippingAddress2}' => $a['s_address_2'],
            '{ShippingCity}'    => $a['s_city'],
            '{ShippingState}'   => $a['s_state'],
            '{ShippingZip}'     => $a['s_zip'],
            '{ShippingCountry}' => $a['s_country_title'] ?? '',
            '{Total}'           => pjCurrency::formatPrice($a['total']),
            '{Discount}'        => pjCurrency::formatPrice($a['discount']),
            '{Shipping}'        => pjCurrency::formatPrice($a['shipping']),
            '{Tax}'             => pjCurrency::formatPrice($a['tax'] ?? 0),
            '{Insurance}'       => pjCurrency::formatPrice($a['insurance']),
            '{Price}'           => pjCurrency::formatPrice($a['price']),
        ];
        foreach (pjAppController::getCompanyTokenMap($locale_id) as $key => $value) {
            $tokens[$key] = $value;
        }

        return str_replace(array_keys($tokens), array_values($tokens), $config['y_template']);
    }

    /**
     * Inline grid editor — only status column may be updated.
     * Previously this endpoint accepted any column, which posed a
     * write-vulnerability. Restricted to whitelisted statuses.
     */
    public function pjActionSaveInvoice()
    {
        $this->setAjax(true);

        if ($this->isXHR()) {
            $id     = (int) $this->_get->toInt('id');
            $column = $this->_post->toString('column');
            $value  = $this->_post->toString('value');

            if ($id > 0 && $column === 'status' && in_array($value, ['paid', 'not_paid', 'cancelled'], true)) {
                pjInvoiceModel::factory()
                    ->where('id', $id)
                    ->limit(1)
                    ->modifyAll(['status' => $value, 'modified' => ':NOW()']);
                pjAppController::jsonResponse(array('status' => 'OK'));
            }

            pjAppController::jsonResponse(array('status' => 'ERR', 'text' => 'Invalid invoice status.'));
        }

        exit;
    }

    public function pjActionView()
    {
        $this->setLayout('pjActionEmpty');
        //         $invoice_arr = $this->pjActionGenerateInvoice('132');
        //   echo "<pre>"; print_r($invoice_arr); echo "</pre>"; die;

        $arr = pjInvoiceModel::factory()

            ->join('pjMultiLang', sprintf("t2.model='pjBaseCountry' AND t2.foreign_id=t1.b_country_id AND t2.field='name' AND t2.locale='%u'", $this->getLocaleId()), 'left outer')

            ->join('pjMultiLang', sprintf("t3.model='pjBaseCountry' AND t3.foreign_id=t1.s_country_id AND t3.field='name' AND t3.locale='%u'", $this->getLocaleId()), 'left outer')

            ->join('pjOrder', 't4.uuid=t1.order_id', 'left outer')

            ->join('pjClient', 't5.id=t4.client_id', 'left outer')

            ->select("t1.*, t2.content as b_country_title, t3.content as s_country_title, t5.email as b_email, t5.client_name")

            ->where('t1.uuid', @$_REQUEST['id'])

            ->where('t1.order_id', @$_REQUEST['uuid'])

            ->limit(1)

            ->findAll()

            ->getDataIndex(0);
        // echo "<pre>"; print_r($arr); die;

        if ($arr === false || empty($arr)) {

            pjUtil::redirect(PJ_INSTALL_URL . "index.php?controller=pjInvoice&action=pjActionInvoices&err=PIN04");
        }

        $arr['items'] = pjInvoiceItemModel::factory()->where('t1.invoice_id', $arr['id'])->findAll()->getData();
        // echo "<pre>"; print_r($arr['items']); die();

        //die(print_r($arr['items']));

        foreach ($arr['items'] as $k => $v) {

            if ($v['name'] == "Insurance" && $v['unit_price'] == 0 && $v['amount'] == 0) {

                unset($arr['items'][$k]);
            }

        }
        //   echo "<pre>"; print_r($desc); echo "</pre>"; die;

        $locale_id = ! empty($arr['locale_id']) ? $arr['locale_id'] : $this->getLocaleId();

        $confi_arr = pjInvoiceConfigModel::factory()->getConfigData($locale_id);

        $arr['y_logo'] = '<img src="' . PJ_INSTALL_URL . $confi_arr['y_logo'] . '" />';

        $arr['o_use_qty_unit_price'] = $confi_arr['o_use_qty_unit_price'];

        // Build retry-payment URL for unpaid invoices — reuses the existing
        // retry_payment.php flow (same hash schema as emails and frontend).
        $retry_url = null;
        if ($arr['status'] === 'not_paid' && !empty($arr['order_id'])) {
            $order_row = pjOrderModel::factory()
                ->where('t1.uuid', $arr['order_id'])
                ->limit(1)
                ->findAll()
                ->getDataIndex(0);
            if (!empty($order_row) && !empty($order_row['created'])) {
                $hash = md5($order_row['uuid'] . $order_row['created'] . PJ_SALT);
                $retry_url = PJ_INSTALL_URL . 'retry_payment.php?uuid=' . urlencode($order_row['uuid']) . '&hash=' . $hash;
            }
        }

        $this

            ->set('arr', $arr)

            ->set('config_arr', $confi_arr)

            ->set('retry_url', $retry_url)

            ->set('template', $this->pjActionTokenizer($arr, $locale_id))

            ->resetCss()

            ->resetJs()

            ->appendJs('jquery.min.js', PJ_THIRD_PARTY_PATH . 'jquery/')

            ->appendJs('jquery.min.js', PJ_THIRD_PARTY_PATH . 'jquery/')
            ->appendJs('jquery.validate.min.js', PJ_THIRD_PARTY_PATH . 'validate/')

            ->appendJs('pjInvoice.js?v=' . time(), $this->getConst('PLUGIN_JS_PATH'))

            ->appendCss('invoice.css', $this->getConst('PLUGIN_CSS_PATH'))
            ->appendCss('datepicker.css', PJ_THIRD_PARTY_PATH . 'bootstrap_datepicker/')

            ->appendJs('bootstrap-datepicker.js', PJ_THIRD_PARTY_PATH . 'bootstrap_datepicker/')

        ;
    }
    /**
     * Generates the raw PDF bytes for one invoice.
     * Reused by generatePdf() (download), pjActionDownloadBulk() (ZIP),
     * and attachInvoiceIfConfigured() (email attachment — Pillar 7).
     *
     * @param string $uuid     Invoice number (invoice.uuid)
     * @param string $order_id Order ID (invoice.order_id)
     * @return string|false    PDF bytes, or false on error
     */
    public function renderInvoicePdf($uuid, $order_id)
    {
        $dm = new pjDependencyManager(PJ_INSTALL_PATH, PJ_THIRD_PARTY_PATH);
        $dm->load(PJ_CONFIG_PATH . 'dependencies.php')->resolve();

        require_once $dm->getPath('tcpdf') . 'tcpdf.php';

        $arr = pjInvoiceModel::factory()
            ->join('pjMultiLang', sprintf("t2.model='pjBaseCountry' AND t2.foreign_id=t1.b_country_id AND t2.field='name' AND t2.locale='%u'", $this->getLocaleId()), 'left outer')
            ->join('pjMultiLang', sprintf("t3.model='pjBaseCountry' AND t3.foreign_id=t1.s_country_id AND t3.field='name' AND t3.locale='%u'", $this->getLocaleId()), 'left outer')
            ->join('pjOrder', 't4.uuid=t1.order_id', 'left outer')
            ->join('pjClient', 't5.id=t4.client_id', 'left outer')
            ->select("t1.*,
                    t2.content as b_country_title,
                    t3.content as s_country_title,
                    t5.email as b_email,
                    t5.client_name")
            ->where('t1.uuid', $uuid)
            ->where('t1.order_id', $order_id)
            ->limit(1)
            ->findAll()
            ->getDataIndex(0);

        if (empty($arr)) {
            return false;
        }

        $items = pjInvoiceItemModel::factory()
            ->where('t1.invoice_id', $arr['id'])
            ->findAll()
            ->getData();

        $locale_id = !empty($arr['locale_id']) ? $arr['locale_id'] : $this->getLocaleId();

        $image_map = array();
        if (!empty($arr['order_id'])) {
            $order_row = pjAppController::fetchOrderForInvoicePdf($arr['order_id'], $locale_id);
            if (!empty($order_row)) {
                pjAppController::mergeOrderIntoInvoicePdfData($arr, $order_row);
                $arr['has_digital'] = pjAppController::pjActionCheckDigital($order_row['id']) ? 1 : 0;

                try {
                    $galleryTable = pjGalleryModel::factory()->getTable();
                    $mlTable      = pjMultiLangModel::factory()->getTable();
                    $stocks = pjOrderStockModel::factory()
                        ->select("t1.product_id,
                            (SELECT g.small_path FROM `{$galleryTable}` g WHERE g.foreign_id = t1.product_id AND g.model = 'pjProduct' ORDER BY g.id ASC LIMIT 1) AS img_path,
                            (SELECT ml.content FROM `{$mlTable}` ml WHERE ml.foreign_id = t1.product_id AND ml.model = 'pjProduct' AND ml.field = 'name' AND ml.locale = " . (int) $locale_id . " ORDER BY ml.id ASC LIMIT 1) AS prod_name")
                        ->where('t1.order_id', $order_row['id'])
                        ->groupBy('t1.product_id')
                        ->findAll()
                        ->getData();
                    foreach ($stocks as $s) {
                        if (!empty($s['img_path']) && !empty($s['prod_name'])) {
                            $image_map[$s['prod_name']] = $s['img_path'];
                        }
                    }
                } catch (Throwable $e) {
                }
            }
        }

        // Koppel image-paden aan items op basis van itemnaam
        foreach ($items as &$it) {
            if (!empty($image_map[$it['name']])) {
                $it['image'] = $image_map[$it['name']];
            }
        }
        unset($it);
        $arr['items'] = $items;

        // Pillar 2: use the new template system (buildInvoiceFromTemplate).
        // Fallback to legacy tokenizer if that new helper for some reason
        // yields an empty string (e.g. on a test installation without migration).
        $html = pjAppController::buildInvoiceFromTemplate($arr, $items, $this->option_arr, $locale_id);
        if (empty(trim($html))) {
            $config_arr = pjInvoiceConfigModel::factory()->getConfigData($locale_id);
            if (!empty($config_arr['y_logo'])) {
                $arr['y_logo'] = '<img src="' . PJ_INSTALL_URL . $config_arr['y_logo'] . '" />';
            }
            $arr['o_use_qty_unit_price'] = isset($config_arr['o_use_qty_unit_price']) ? $config_arr['o_use_qty_unit_price'] : 1;
            if (empty($config_arr['y_template'])) {
                $config_arr['y_template'] = pjAppController::getDefaultInvoiceYTemplate();
            }
            $arr['y_template'] = $config_arr['y_template'];
            $html = $this->pjActionTokenizer($arr, $locale_id);
        }

        // Footer is placed at the bottom of each page by pjInvoicePdf::Footer().
        $footerHtml = pjAppController::getInvoiceFooterHtml($this->option_arr, $locale_id);

        require_once PJ_INSTALL_PATH . 'app/classes/pjInvoicePdf.class.php';

        $pdf = new pjInvoicePdf(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
        $pdf->setInvoiceFooterHtml($footerHtml);
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(true);
        $pdf->setFooterMargin(15);
        $pdf->setFooterFont(array('dejavusans', '', 8));
        $pdf->SetMargins(10, 10, 10);
        $pdf->SetAutoPageBreak(true, 28); // 28mm bottom margin laat ruimte voor footer
        $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
        $pdf->SetFont('dejavusans', '', 9);
        $pdf->AddPage();
        $pdf->writeHTML($html, true, false, true, false, '');

        // 'S' = return as string (bytes)
        return $pdf->Output('invoice_' . $uuid . '.pdf', 'S');
    }

    public function generatePdf()
    {
        $uuid     = $this->_get->toString('id');
        $order_id = $this->_get->toString('uuid');

        $pdfBytes = $this->renderInvoicePdf($uuid, $order_id);

        if ($pdfBytes === false) {
            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices&err=PIN04');
            return;
        }

        if (ob_get_length()) { ob_end_clean(); }

        $filename = pjAppController::getInvoicePdfFilename($order_id, $uuid);
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . strlen($pdfBytes));
        echo $pdfBytes;
        exit;
    }

    /**
     * Bulk download: generates a ZIP with all selected invoice PDFs.
     * Called via the datagrid "Choose an action" dropdown.
     * POST param: record[] = list of invoice.id values.
     */
    public function pjActionDownloadBulk()
    {
        $this->checkLogin();

        if (!$this->isInvoiceReady()) {
            $this->set('status', 2);
            return;
        }

        $ids = $this->_post->toArray('record');
        if (empty($ids)) {
            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices');
            return;
        }

        $invoices = pjInvoiceModel::factory()
            ->select('t1.id, t1.uuid, t1.order_id')
            ->whereIn('t1.id', array_map('intval', $ids))
            ->findAll()
            ->getData();

        if (empty($invoices)) {
            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices&err=PIN04');
            return;
        }

        $tmpZip = tempnam(sys_get_temp_dir(), 'pjinv_');
        $zip = new ZipArchive();
        if ($zip->open($tmpZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
            @unlink($tmpZip);
            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices&err=PIN04');
            return;
        }

        $count = 0;
        foreach ($invoices as $inv) {
            $bytes = $this->renderInvoicePdf($inv['uuid'], $inv['order_id']);
            if ($bytes !== false) {
                // Sanitize filename — even "2025--01361" may be in a ZIP filename
                $safeName = preg_replace('/[^A-Za-z0-9._-]/', '_', $inv['uuid']);
                $zip->addFromString('invoice_' . $safeName . '.pdf', $bytes);
                $count++;
            }
        }
        $zip->close();

        if ($count === 0) {
            @unlink($tmpZip);
            pjUtil::redirect(PJ_INSTALL_URL . 'index.php?controller=pjInvoice&action=pjActionInvoices&err=PIN04');
            return;
        }

        if (ob_get_length()) { ob_end_clean(); }

        $filename = 'facturen_' . date('Ymd_His') . '.zip';
        header('Content-Type: application/zip');
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . filesize($tmpZip));
        readfile($tmpZip);
        @unlink($tmpZip);
        exit;
    }
}
