<?php
declare(strict_types=1);

namespace App\Modules\Orders;

use App\Core\Http\Request;
use App\Core\Http\Response;
use App\Core\I18n\Translator;
use App\Core\Security\Csrf;
use App\Core\View\Template;
use App\Core\Support\Flash;
use App\Core\Support\StringHelper;
use App\Modules\Accounting\InvoiceRepository;
use App\Modules\Accounting\ReceiptRepository;
use App\Modules\Settings\InvoiceConfigRepository;
use App\Modules\Auth\AuthService;
use App\Modules\Email\EmailSendingService;
use App\Modules\Settings\EmailMailboxRepository;
use App\Modules\Settings\EmailTemplateRepository;
use App\Modules\Settings\ReceiptConfigRepository;
use App\Modules\Automation\AutomationService;
use App\Modules\Settings\ShopproApiClient;
use App\Modules\Settings\ShopproIntegrationsRepository;
use App\Modules\Shipments\ShipmentPackageRepository;
use App\Modules\Sms\SmsConversationService;
use App\Modules\Sms\SmsMessageRepository;
use Throwable;

final class OrdersController
{
    public function __construct(
        private readonly Template $template,
        private readonly Translator $translator,
        private readonly AuthService $auth,
        private readonly OrdersRepository $orders,
        private readonly ?ShipmentPackageRepository $shipmentPackages = null,
        private readonly ?ReceiptRepository $receiptRepo = null,
        private readonly ?ReceiptConfigRepository $receiptConfigRepo = null,
        private readonly ?EmailSendingService $emailService = null,
        private readonly ?EmailTemplateRepository $emailTemplateRepo = null,
        private readonly ?EmailMailboxRepository $emailMailboxRepo = null,
        private readonly string $storagePath = '',
        private readonly ?\App\Modules\Printing\PrintJobRepository $printJobRepo = null,
        private readonly ?ShopproIntegrationsRepository $shopproIntegrations = null,
        private readonly ?AutomationService $automation = null,
        private readonly ?InvoiceRepository $invoiceRepo = null,
        private readonly ?InvoiceConfigRepository $invoiceConfigRepo = null,
        private readonly ?SmsMessageRepository $smsMessages = null,
        private readonly ?SmsConversationService $smsConversation = null
    ) {
    }

    public function index(Request $request): Response
    {
        $filters = [
            'search' => trim((string) $request->input('search', '')),
            'source' => trim((string) $request->input('source', '')),
            'status' => trim((string) $request->input('status', '')),
            'status_group' => trim((string) $request->input('status_group', '')),
            'payment_status' => trim((string) $request->input('payment_status', '')),
            'date_from' => trim((string) $request->input('date_from', '')),
            'date_to' => trim((string) $request->input('date_to', '')),
            'sort' => (string) $request->input('sort', 'ordered_at'),
            'sort_dir' => (string) $request->input('sort_dir', 'DESC'),
            'page' => max(1, (int) $request->input('page', 1)),
            'per_page' => max(1, min(100, (int) $request->input('per_page', 20)),
            ),
        ];

        $result = $this->orders->paginate($filters);
        $totalPages = max(1, (int) ceil(((int) $result['total']) / max(1, (int) $result['per_page'])));
        $sourceOptions = $this->orders->sourceOptions();
        $stats = $this->orders->quickStats();
        $statusCounts = $this->orders->statusCounts();
        $statusConfig = $this->orders->statusPanelConfig();
        $statusLabelMap = $this->statusLabelMap($statusConfig);
        $statusColorMap = $this->statusColorMap($statusConfig);
        $statusOptions = $this->buildStatusFilterOptions($this->orders->statusOptions(), $statusLabelMap);
        $statusPanel = $this->buildStatusPanel($statusConfig, $statusCounts, $filters['status'], $filters, $filters['status_group']);

        $tableRows = array_map(fn (array $row): array => $this->toTableRow($row, $statusLabelMap, $statusColorMap), (array) ($result['items'] ?? []));

        $tableListData = [
            'list_key' => 'orders',
            'base_path' => '/orders/list',
            'query' => $filters,
            'filters' => [
                [
                    'key' => 'search',
                    'label' => $this->translator->get('orders.filters.search'),
                    'type' => 'text',
                    'value' => $filters['search'],
                ],
                [
                    'key' => 'source',
                    'label' => $this->translator->get('orders.filters.source'),
                    'type' => 'select',
                    'value' => $filters['source'],
                    'options' => ['' => $this->translator->get('orders.filters.any')] + $sourceOptions,
                ],
                [
                    'key' => 'status',
                    'label' => $this->translator->get('orders.filters.status'),
                    'type' => 'select',
                    'value' => $filters['status'],
                    'options' => ['' => $this->translator->get('orders.filters.any')] + $statusOptions,
                ],
                [
                    'key' => 'payment_status',
                    'label' => $this->translator->get('orders.filters.payment_status'),
                    'type' => 'select',
                    'value' => $filters['payment_status'],
                    'options' => $this->paymentStatusFilterOptions(),
                ],
                [
                    'key' => 'date_from',
                    'label' => $this->translator->get('orders.filters.date_from'),
                    'type' => 'date',
                    'value' => $filters['date_from'],
                ],
                [
                    'key' => 'date_to',
                    'label' => $this->translator->get('orders.filters.date_to'),
                    'type' => 'date',
                    'value' => $filters['date_to'],
                ],
            ],
            'columns' => [
                ['key' => 'order_ref', 'label' => $this->translator->get('orders.fields.order_ref'), 'sortable' => true, 'sort_key' => 'source_order_id', 'raw' => true],
                ['key' => 'buyer', 'label' => $this->translator->get('orders.fields.buyer'), 'raw' => true],
                ['key' => 'status_badges', 'label' => $this->translator->get('orders.fields.status'), 'sortable' => true, 'sort_key' => 'status_code', 'raw' => true],
                ['key' => 'products', 'label' => $this->translator->get('orders.fields.products'), 'raw' => true],
                ['key' => 'totals', 'label' => $this->translator->get('orders.fields.totals'), 'sortable' => true, 'sort_key' => 'total_with_tax', 'raw' => true],
                ['key' => 'shipping', 'label' => $this->translator->get('orders.fields.shipping'), 'raw' => true],
                ['key' => 'ordered_at', 'label' => $this->translator->get('orders.fields.ordered_at'), 'sortable' => true, 'sort_key' => 'ordered_at'],
            ],
            'rows' => $tableRows,
            'pagination' => [
                'page' => (int) ($result['page'] ?? 1),
                'total_pages' => $totalPages,
                'total' => (int) ($result['total'] ?? 0),
                'per_page' => (int) ($result['per_page'] ?? 20),
            ],
            'per_page_options' => [20, 50, 100],
            'selectable' => true,
            'select_name' => 'selected_ids[]',
            'select_value_key' => 'id',
            'header_actions' => [],
            'empty_message' => $this->translator->get('orders.empty'),
            'show_actions' => false,
        ];

        if ($request->header('X-Requested-With') === 'XMLHttpRequest') {
            $tableHtml = $this->template->render('components/table-list', [
                'tableList' => $tableListData,
            ]);
            $panelHtml = $this->template->render('components/order-status-panel', [
                'statusPanelList' => $statusPanel,
                'statusPanelTitle' => 'Statusy',
            ]);

            return Response::json([
                'tableHtml' => $tableHtml,
                'panelHtml' => $panelHtml,
            ]);
        }

        $html = $this->template->render('orders/list', [
            'title' => $this->translator->get('orders.title'),
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'tableList' => $tableListData,
            'stats' => $stats,
            'statusPanel' => $statusPanel,
            'allStatuses' => $this->buildAllStatusOptions($statusConfig),
            'statusColorMap' => $statusColorMap,
            'errorMessage' => (string) ($result['error'] ?? ''),
        ], 'layouts/app');

        return Response::html($html);
    }

    public function show(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return Response::html('Not found', 404);
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $items = is_array($details['items'] ?? null) ? $details['items'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
        $payments = is_array($details['payments'] ?? null) ? $details['payments'] : [];
        $shipments = is_array($details['shipments'] ?? null) ? $details['shipments'] : [];
        $documents = is_array($details['documents'] ?? null) ? $details['documents'] : [];
        $notes = is_array($details['notes'] ?? null) ? $details['notes'] : [];
        $history = is_array($details['status_history'] ?? null) ? $details['status_history'] : [];
        $activityLog = is_array($details['activity_log'] ?? null) ? $details['activity_log'] : [];
        $statusCode = (string) (($order['effective_status_id'] ?? '') !== '' ? $order['effective_status_id'] : ($order['status_code'] ?? ''));
        $statusCounts = $this->orders->statusCounts();
        $statusConfig = $this->orders->statusPanelConfig();
        $statusLabelMap = $this->statusLabelMap($statusConfig);

        $resolvedHistory = $this->resolveHistoryLabels($history, $statusLabelMap);

        $allStatuses = $this->buildAllStatusOptions($statusConfig);

        $packages = $this->shipmentPackages !== null
            ? $this->shipmentPackages->findByOrderId($orderId)
            : [];

        if ($this->storagePath !== '') {
            foreach ($packages as &$pkg) {
                $lp = trim((string) ($pkg['label_path'] ?? ''));
                if ($lp !== '' && !file_exists($this->storagePath . '/' . $lp)) {
                    $pkg['label_path'] = '';
                }
            }
            unset($pkg);
        }

        $receipts = $this->receiptRepo !== null
            ? $this->receiptRepo->findByOrderId($orderId)
            : [];

        $activeReceiptConfigs = [];
        if ($this->receiptConfigRepo !== null) {
            $activeReceiptConfigs = array_filter(
                $this->receiptConfigRepo->listAll(),
                static fn(array $c): bool => (int) ($c['is_active'] ?? 0) === 1
            );
        }

        $emailTemplates = $this->emailTemplateRepo !== null ? $this->emailTemplateRepo->listActive() : [];
        $emailMailboxes = $this->emailMailboxRepo !== null ? $this->emailMailboxRepo->listActive() : [];

        $invoices = $this->invoiceRepo !== null
            ? $this->invoiceRepo->findByOrderId($orderId)
            : [];
        $activeInvoiceConfigs = [];
        if ($this->invoiceConfigRepo !== null) {
            $activeInvoiceConfigs = array_values(array_filter(
                $this->invoiceConfigRepo->listAll(),
                static fn (array $c): bool => (int) ($c['is_active'] ?? 0) === 1
            ));
        }

        $flashSuccess = (string) Flash::get('order.success', '');
        $flashError = (string) Flash::get('order.error', '');

        $customerRiskInfo = $this->buildCustomerRiskInfo($order, $orderId);
        $smsMessages = $this->smsMessages !== null ? $this->smsMessages->findByOrderId($orderId) : [];
        $smsPhone = $this->resolveSmsPhone($order, $addresses);
        $smsDefaultFooterConfigured = $this->smsConversation !== null && $this->smsConversation->hasDefaultFooter();

        $html = $this->template->render('orders/show', [
            'title' => $this->translator->get('orders.details.title') . ' #' . $orderId,
            'activeMenu' => 'orders',
            'activeOrders' => 'list',
            'user' => $this->auth->user(),
            'csrfToken' => Csrf::token(),
            'orderId' => $orderId,
            'order' => $order,
            'items' => $items,
            'addresses' => $addresses,
            'payments' => $payments,
            'shipments' => $shipments,
            'packages' => $packages,
            'pendingPrintPackageIds' => $this->printJobRepo !== null ? $this->printJobRepo->pendingPackageIds() : [],
            'documents' => $documents,
            'notes' => $notes,
            'history' => $resolvedHistory,
            'activityLog' => $activityLog,
            'statusLabel' => $this->statusLabel($statusCode, $statusLabelMap),
            'statusPanel' => $this->buildStatusPanel($statusConfig, $statusCounts, $statusCode),
            'allStatuses' => $allStatuses,
            'currentStatusCode' => $statusCode,
            'flashSuccess' => $flashSuccess,
            'flashError' => $flashError,
            'receipts' => $receipts,
            'receiptConfigs' => $activeReceiptConfigs,
            'invoices' => $invoices,
            'invoiceConfigs' => $activeInvoiceConfigs,
            'emailTemplates' => $emailTemplates,
            'emailMailboxes' => $emailMailboxes,
            'customerRiskInfo' => $customerRiskInfo,
            'smsMessages' => $smsMessages,
            'smsPhone' => $smsPhone,
            'smsDefaultFooterConfigured' => $smsDefaultFooterConfigured,
        ], 'layouts/app');

        return Response::html($html);
    }

    public function sendSms(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $redirectTo = '/orders/' . $orderId . '?tab=sms';
        if (!Csrf::validate((string) $request->input('_token', ''))) {
            Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect($redirectTo);
        }

        if ($orderId <= 0 || $this->smsConversation === null) {
            Flash::set('order.error', 'Modul SMS nie jest dostepny.');
            return Response::redirect($redirectTo);
        }

        try {
            $user = $this->auth->user();
            $userId = is_array($user) ? (int) ($user['id'] ?? 0) : 0;
            $result = $this->smsConversation->sendFromOrder(
                $orderId,
                (string) $request->input('phone', ''),
                (string) $request->input('message', ''),
                $userId > 0 ? $userId : null
            );

            if ($result['ok']) {
                Flash::set('order.success', 'SMS zostal wyslany.');
            } else {
                Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $result['message']);
            }
        } catch (Throwable $exception) {
            Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $exception->getMessage());
        }

        return Response::redirect($redirectTo);
    }

    /**
     * Sklada informacje o historii zwrotow klienta biezacego zamowienia.
     *
     * @param array<string, mixed> $order
     * @return array{count:int, orders:array<int, array<string, mixed>>, email:string, phone:string, name:string, text:string}
     */
    private function buildCustomerRiskInfo(array $order, int $orderId): array
    {
        $count = max(0, (int) ($order['customer_returned_count'] ?? 0));
        $email = trim((string) ($order['buyer_email'] ?? ''));
        $phone = trim((string) ($order['buyer_phone'] ?? ''));
        $name = trim((string) ($order['buyer_name'] ?? ''));

        $returnedOrders = [];
        if ($count > 0 && $this->shipmentPackages !== null) {
            $returnedOrders = $this->shipmentPackages->findReturnedByCustomer(
                ['email' => $email, 'phone' => $phone, 'name' => $name],
                $orderId
            );
        }

        return [
            'count' => $count,
            'orders' => $returnedOrders,
            'email' => $email,
            'phone' => $phone,
            'name' => $name,
            'text' => $this->composeCustomerRiskText($count, $email, $phone, $name),
        ];
    }

    /**
     * @param array<string, mixed> $order
     * @param array<int, array<string, mixed>> $addresses
     */
    private function resolveSmsPhone(array $order, array $addresses): string
    {
        $buyerPhone = trim((string) ($order['buyer_phone'] ?? ''));
        if ($buyerPhone !== '') {
            return $buyerPhone;
        }

        foreach (['customer', 'delivery', 'invoice'] as $wantedType) {
            foreach ($addresses as $address) {
                if ((string) ($address['address_type'] ?? '') !== $wantedType) {
                    continue;
                }
                $phone = trim((string) ($address['phone'] ?? ''));
                if ($phone !== '') {
                    return $phone;
                }
            }
        }

        return '';
    }

    private function composeCustomerRiskText(int $count, string $email, string $phone, string $name): string
    {
        if ($count <= 0) {
            return '';
        }

        $hasPhone = $phone !== '';
        $hasEmail = $email !== '';
        $hasName = $name !== '';

        if ($hasPhone && $hasEmail) {
            $subject = 'Osoba o numerze telefonu ' . $phone . ' oraz email ' . $email;
        } elseif ($hasEmail) {
            $subject = 'Osoba o emailu ' . $email;
        } elseif ($hasPhone) {
            $subject = 'Osoba o numerze telefonu ' . $phone;
        } elseif ($hasName) {
            $subject = 'Osoba o imieniu i nazwisku ' . $name;
        } else {
            $subject = 'Ten klient';
        }

        $noun = $count === 1 ? 'przesylke' : 'przesylek';
        return $subject . ' nie odebrala ' . $count . ' ' . $noun . '.';
    }

    public function updateDetails(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        if ($orderId <= 0) {
            return Response::html('Not found', 404);
        }

        $csrfToken = (string) $request->input('_token', '');
        if (!Csrf::validate($csrfToken)) {
            Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect('/orders/' . $orderId);
        }

        $details = $this->orders->findDetails($orderId);
        if (!is_array($details['order'] ?? null)) {
            return Response::html('Not found', 404);
        }

        $deliveryMethodRaw = trim((string) $request->input('delivery_method', ''));
        $paymentMethodRaw = trim((string) $request->input('payment_method', ''));
        $isCod = (string) $request->input('is_cod', '') === '1';

        if ($deliveryMethodRaw === '' && $paymentMethodRaw === '') {
            Flash::set('order.error', 'Podaj formę dostawy lub formę płatności.');
            return Response::redirect('/orders/' . $orderId);
        }

        $deliveryMethod = $deliveryMethodRaw !== '' ? $deliveryMethodRaw : null;
        $paymentMethod = $paymentMethodRaw !== '' ? $paymentMethodRaw : null;

        $externalPaymentTypeId = null;
        $currentOrder = (array) $details['order'];
        $currentExternal = strtoupper(trim((string) ($currentOrder['external_payment_type_id'] ?? '')));
        $currentlyCod = StringHelper::isCodPayment($currentExternal);
        if ($isCod && !$currentlyCod) {
            $externalPaymentTypeId = 'CASH_ON_DELIVERY';
        } elseif (!$isCod && $currentlyCod) {
            $externalPaymentTypeId = '';
        }

        $user = $this->auth->user();
        $actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;

        $changed = $this->orders->updateDeliveryAndPayment(
            $orderId,
            $deliveryMethod,
            $paymentMethod,
            $externalPaymentTypeId,
            'user',
            $actorName !== '' ? $actorName : null
        );

        if ($changed) {
            Flash::set('order.success', 'Dane zamówienia zostały zaktualizowane.');
        } else {
            Flash::set('order.error', 'Brak zmian do zapisania.');
        }

        return Response::redirect('/orders/' . $orderId);
    }

    public function updateStatus(Request $request): Response
    {
        $isAjax = strtolower($request->header('X-Requested-With')) === 'xmlhttprequest';
        $orderId = max(0, (int) $request->input('id', 0));
        if ($orderId <= 0) {
            return $isAjax
                ? Response::json(['success' => false, 'error' => 'Not found'], 404)
                : Response::html('Not found', 404);
        }

        $csrfToken = (string) $request->input('_token', '');
        if (!Csrf::validate($csrfToken)) {
            if ($isAjax) {
                return Response::json(['success' => false, 'error' => $this->translator->get('auth.errors.csrf_expired')], 403);
            }
            Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
            return Response::redirect('/orders/' . $orderId);
        }

        $newStatus = trim((string) $request->input('new_status', ''));
        if ($newStatus === '') {
            if ($isAjax) {
                return Response::json(['success' => false, 'error' => $this->translator->get('orders.details.status_change.status_required')], 422);
            }
            Flash::set('order.error', $this->translator->get('orders.details.status_change.status_required'));
            return Response::redirect('/orders/' . $orderId);
        }

        $user = $this->auth->user();
        $actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;

        $oldDetails = $this->orders->findDetails($orderId);
        $oldOrder = is_array($oldDetails['order'] ?? null) ? $oldDetails['order'] : [];
        $oldStatus = strtolower(trim((string) ($oldOrder['status_code'] ?? '')));

        $success = $this->orders->updateOrderStatus($orderId, $newStatus, 'user', $actorName !== '' ? $actorName : null);

        if ($success) {
            $normalizedNew = strtolower(trim($newStatus));
            if ($oldStatus !== $normalizedNew) {
                try {
                    $this->automation?->trigger('order.status_changed', $orderId, [
                        'old_status' => $oldStatus,
                        'new_status' => $normalizedNew,
                    ]);
                } catch (\Throwable) {
                }
            }
        }

        if ($isAjax) {
            if (!$success) {
                return Response::json(['success' => false, 'error' => $this->translator->get('orders.details.status_change.failed')], 500);
            }
            $statusConfig = $this->orders->statusPanelConfig();
            $statusLabelMap = $this->statusLabelMap($statusConfig);
            $statusColorMap = $this->statusColorMap($statusConfig);
            $normalizedCode = strtolower(trim($newStatus));
            return Response::json([
                'success' => true,
                'status_code' => $normalizedCode,
                'status_label' => $this->statusLabel($normalizedCode, $statusLabelMap),
                'status_color' => $statusColorMap[$normalizedCode] ?? '',
            ]);
        }

        if ($success) {
            Flash::set('order.success', $this->translator->get('orders.details.status_change.success'));
        } else {
            Flash::set('order.error', $this->translator->get('orders.details.status_change.failed'));
        }

        return Response::redirect('/orders/' . $orderId);
    }

    public function toggleInvoiceRequested(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        if ($orderId <= 0) {
            return Response::json(['success' => false, 'error' => 'Not found'], 404);
        }

        if (!Csrf::validate((string) $request->input('_token', ''))) {
            return Response::json(['success' => false, 'error' => $this->translator->get('auth.errors.csrf_expired')], 403);
        }

        $value = (int) $request->input('invoice_requested', 0) === 1;
        $this->orders->setInvoiceRequested($orderId, $value);

        $user = $this->auth->user();
        $actorName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : null;
        $this->orders->recordActivity(
            $orderId,
            'invoice_requested_changed',
            'Klient prosi o fakture: ' . ($value ? 'tak' : 'nie'),
            ['invoice_requested' => $value ? 1 : 0],
            'user',
            $actorName !== '' ? $actorName : null
        );

        return Response::json(['success' => true, 'invoice_requested' => $value ? 1 : 0]);
    }

    /**
     * @param array<string, mixed> $row
     * @return array<string, mixed>
     */
    private function toTableRow(array $row, array $statusLabelMap, array $statusColorMap = []): array
    {
        $internalOrderNumber = trim((string) ($row['internal_order_number'] ?? ''));
        $sourceOrderId = trim((string) ($row['source_order_id'] ?? ''));
        $externalOrderId = trim((string) ($row['external_order_id'] ?? ''));
        $source = trim((string) ($row['source'] ?? ''));
        $integrationName = trim((string) ($row['integration_name'] ?? ''));
        $buyerName = trim((string) ($row['buyer_name'] ?? ''));
        $buyerEmail = trim((string) ($row['buyer_email'] ?? ''));
        $buyerCity = trim((string) ($row['buyer_city'] ?? ''));
        $status = trim((string) (($row['effective_status_id'] ?? '') !== '' ? $row['effective_status_id'] : ($row['status_code'] ?? '')));
        $currency = trim((string) ($row['currency'] ?? ''));
        $totalWithTax = $row['total_with_tax'] !== null ? number_format((float) $row['total_with_tax'], 2, '.', ' ') : '-';
        $totalPaid = $row['total_paid'] !== null ? number_format((float) $row['total_paid'], 2, '.', ' ') : '-';
        $paymentType = strtoupper(trim((string) ($row['external_payment_type_id'] ?? '')));
        $isCod = StringHelper::isCodPayment($paymentType);
        $paymentStatus = isset($row['payment_status']) ? (int) $row['payment_status'] : null;
        $isUnpaid = !$isCod && $paymentStatus === 0;
        $itemsCount = max(0, (int) ($row['items_count'] ?? 0));
        $itemsQty = $this->formatQuantity((float) ($row['items_qty'] ?? 0));
        $shipments = max(0, (int) ($row['shipments_count'] ?? 0));
        $documents = max(0, (int) ($row['documents_count'] ?? 0));
        $itemsPreview = is_array($row['items_preview'] ?? null) ? $row['items_preview'] : [];
        $projectsDone = max(0, (int) ($row['projects_done'] ?? 0));
        $projectsTotal = max(0, (int) ($row['projects_total'] ?? 0));
        $returnedCount = max(0, (int) ($row['customer_returned_count'] ?? 0));
        $returnedBadge = $returnedCount >= 1
            ? ' <span class="risk-return-badge" title="Klient nie odebral ' . $returnedCount . ' przesylek w historii">zwroty: ' . $returnedCount . '</span>'
            : '';

        $previewBtn = '<button type="button" class="btn-icon js-order-preview-btn" data-order-id="' . (int) ($row['id'] ?? 0) . '" title="Podglad">'
            . '<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>'
            . '</button>';

        return [
            'id' => (int) ($row['id'] ?? 0),
            'order_ref' => '<div class="orders-ref">'
                . '<div class="orders-ref__main">' . $previewBtn . '<a href="/orders/' . (int) ($row['id'] ?? 0) . '">'
                . htmlspecialchars($internalOrderNumber !== '' ? $internalOrderNumber : ('#' . (string) ($row['id'] ?? 0)), ENT_QUOTES, 'UTF-8')
                . '</a></div>'
                . '<div class="orders-ref__meta">'
                . '<span>' . htmlspecialchars($integrationName !== '' ? $integrationName : $this->sourceLabel($source), ENT_QUOTES, 'UTF-8') . '</span>'
                . '<span>ID: ' . htmlspecialchars($sourceOrderId !== '' ? $sourceOrderId : $externalOrderId, ENT_QUOTES, 'UTF-8') . '</span>'
                . '</div>'
                . '</div>',
            'buyer' => '<div class="orders-buyer">'
                . '<div class="orders-buyer__name">' . htmlspecialchars($buyerName !== '' ? $buyerName : '-', ENT_QUOTES, 'UTF-8') . $returnedBadge . '</div>'
                . '<div class="orders-buyer__meta">'
                . '<span>' . htmlspecialchars($buyerEmail, ENT_QUOTES, 'UTF-8') . '</span>'
                . '<span>' . htmlspecialchars($buyerCity, ENT_QUOTES, 'UTF-8') . '</span>'
                . '</div>'
                . '</div>',
            'status_badges' => '<div class="orders-status-wrap" data-order-id="' . (int) ($row['id'] ?? 0) . '" data-current-status="' . htmlspecialchars($status, ENT_QUOTES, 'UTF-8') . '">'
                . $this->statusBadge($status, $this->statusLabel($status, $statusLabelMap), $statusColorMap[strtolower(trim($status))] ?? '')
                . '</div>',
            'products' => $this->productsHtml($itemsPreview, $itemsCount, $itemsQty, $projectsDone, $projectsTotal),
            'totals' => '<div class="orders-money">'
                . '<div class="orders-money__main">' . htmlspecialchars($totalWithTax . ' ' . $currency, ENT_QUOTES, 'UTF-8') . ($isUnpaid ? ' <span class="order-tag is-unpaid">Nieopłacone</span>' : '') . '</div>'
                . '<div class="orders-money__meta">' . ($isCod ? '<span class="order-tag is-cod">Za pobraniem</span>' : 'oplacono: ' . htmlspecialchars($totalPaid . ' ' . $currency, ENT_QUOTES, 'UTF-8')) . '</div>'
                . '</div>',
            'shipping' => $this->shippingHtml(
                trim((string) ($row['external_carrier_id'] ?? '')),
                $shipments,
                $documents
            ),
            'ordered_at' => (string) ($row['ordered_at'] ?? ''),
            '_row_class' => trim($this->agedRowClass((string) ($row['ordered_at'] ?? '')) . ($returnedCount >= 1 ? ' is-risk-return' : '')),
        ];
    }

    private function agedRowClass(string $orderedAt): string
    {
        if ($orderedAt === '') {
            return '';
        }
        $ts = strtotime($orderedAt);
        if ($ts === false) {
            return '';
        }
        $ageDays = (int) floor((time() - $ts) / 86400);
        if ($ageDays < 4) {
            return '';
        }
        $level = $ageDays >= 7 ? 7 : $ageDays;

        return 'order-row-aged order-row-aged-' . $level;
    }

    private function statusBadge(string $statusCode, string $statusLabel, string $colorHex = ''): string
    {
        $label = $statusLabel !== '' ? $statusLabel : '-';
        $code = strtolower(trim($statusCode));

        if ($colorHex !== '') {
            $style = 'background-color:' . htmlspecialchars($colorHex, ENT_QUOTES, 'UTF-8') . ';color:#fff';
            return '<span class="order-tag" style="' . $style . '">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</span>';
        }

        $class = 'is-neutral';
        if (in_array($code, ['shipped', 'delivered'], true)) {
            $class = 'is-success';
        } elseif (in_array($code, ['cancelled', 'returned'], true)) {
            $class = 'is-danger';
        } elseif (in_array($code, ['new', 'confirmed'], true)) {
            $class = 'is-info';
        } elseif (in_array($code, ['processing', 'packed', 'paid'], true)) {
            $class = 'is-warn';
        }

        return '<span class="order-tag ' . $class . '">' . htmlspecialchars($label, ENT_QUOTES, 'UTF-8') . '</span>';
    }

    private function sourceLabel(string $source): string
    {
        return match (strtolower(trim($source))) {
            'allegro' => 'Allegro',
            'shoppro' => 'shopPRO',
            'erli' => 'Erli',
            default => ucfirst(strtolower(trim($source))),
        };
    }

    private function statusLabel(string $statusCode, array $statusLabelMap = []): string
    {
        $key = strtolower(trim($statusCode));
        if ($key === '') {
            return '-';
        }

        if (isset($statusLabelMap[$key])) {
            return (string) $statusLabelMap[$key];
        }

        $normalized = str_replace(['_', '-'], ' ', $key);
        return ucfirst($normalized);
    }

    /**
     * @param array<int, array{name:string,color_hex:string,items:array<int, array{code:string,name:string}>}> $config
     * @param array<string, int> $counts
     * @return array<int, array<string, mixed>>
     */
    private function buildStatusPanel(array $config, array $counts, string $currentStatusCode, array $query = [], string $currentStatusGroup = ''): array
    {
        $allCount = 0;
        foreach ($counts as $count) {
            $allCount += (int) $count;
        }

        $result = [[
            'name' => '',
            'items' => [[
                'code' => '',
                'label' => 'Wszystkie',
                'count' => $allCount,
                'is_active' => trim($currentStatusCode) === '' && trim($currentStatusGroup) === '',
                'tone' => 'neutral',
                'color_hex' => '#64748b',
                'url' => $this->statusFilterUrl($query, ''),
            ]],
        ]];

        foreach ($config as $group) {
            $items = [];
            $groupColor = StringHelper::normalizeColorHex((string) ($group['color_hex'] ?? '#64748b'));
            $groupId = (string) ((int) ($group['id'] ?? 0));
            $groupItems = is_array($group['items'] ?? null) ? $group['items'] : [];
            $isActiveGroup = $currentStatusGroup !== '' && $currentStatusGroup === $groupId;
            $groupCount = 0;
            foreach ($groupItems as $status) {
                $code = strtolower(trim((string) ($status['code'] ?? '')));
                if ($code === '') {
                    continue;
                }
                $statusCount = (int) ($counts[$code] ?? 0);
                $groupCount += $statusCount;
                $items[] = [
                    'code' => $code,
                    'label' => (string) ($status['name'] ?? $code),
                    'count' => $statusCount,
                    'is_active' => !$isActiveGroup && trim(strtolower($currentStatusCode)) === $code,
                    'tone' => $this->statusTone($code),
                    'color_hex' => $groupColor,
                    'url' => $this->statusFilterUrl($query, $code),
                ];
            }
            if ($items === []) {
                continue;
            }
            $result[] = [
                'name' => (string) ($group['name'] ?? ''),
                'color_hex' => $groupColor,
                'group_id' => $groupId,
                'group_url' => $this->groupFilterUrl($query, $groupId),
                'group_count' => $groupCount,
                'is_active_group' => $isActiveGroup,
                'items' => $items,
            ];
        }

        $usedCodes = [];
        foreach ($result as $group) {
            $items = is_array($group['items'] ?? null) ? $group['items'] : [];
            foreach ($items as $item) {
                $code = strtolower(trim((string) ($item['code'] ?? '')));
                if ($code !== '') {
                    $usedCodes[$code] = true;
                }
            }
        }

        $extraItems = [];
        foreach ($counts as $code => $count) {
            $normalizedCode = strtolower(trim((string) $code));
            if ($normalizedCode === '' || $normalizedCode === '_empty' || isset($usedCodes[$normalizedCode])) {
                continue;
            }
            $extraItems[] = [
                'code' => $normalizedCode,
                'label' => $this->statusLabel($normalizedCode),
                'count' => (int) $count,
                'is_active' => trim(strtolower($currentStatusCode)) === $normalizedCode,
                'tone' => $this->statusTone($normalizedCode),
                'color_hex' => '#64748b',
                'url' => $this->statusFilterUrl($query, $normalizedCode),
            ];
        }
        if ($extraItems !== []) {
            $result[] = [
                'name' => 'Pozostale',
                'color_hex' => '#64748b',
                'items' => $extraItems,
            ];
        }

        return $result;
    }

    /**
     * @param array<string, mixed> $query
     */
    private function statusFilterUrl(array $query, string $statusCode): string
    {
        $params = $query;
        unset($params['status_group']);
        if ($statusCode === '') {
            unset($params['status']);
        } else {
            $params['status'] = $statusCode;
        }
        $params['page'] = 1;

        $clean = [];
        foreach ($params as $key => $value) {
            if ($value === '' || $value === null) {
                continue;
            }
            $clean[(string) $key] = (string) $value;
        }

        $qs = http_build_query($clean);
        return $qs === '' ? '/orders/list' : '/orders/list?' . $qs;
    }

    private function groupFilterUrl(array $query, string $groupId): string
    {
        $params = $query;
        unset($params['status']);
        if ($groupId === '' || $groupId === '0') {
            unset($params['status_group']);
        } else {
            $params['status_group'] = $groupId;
        }
        $params['page'] = 1;

        $clean = [];
        foreach ($params as $key => $value) {
            if ($value === '' || $value === null) {
                continue;
            }
            $clean[(string) $key] = (string) $value;
        }

        $qs = http_build_query($clean);
        return $qs === '' ? '/orders/list' : '/orders/list?' . $qs;
    }

    private function statusTone(string $statusCode): string
    {
        $code = strtolower(trim($statusCode));
        if (in_array($code, ['new', 'confirmed'], true)) {
            return 'info';
        }
        if (in_array($code, ['paid', 'processing', 'packed'], true)) {
            return 'warn';
        }
        if (in_array($code, ['shipped', 'delivered'], true)) {
            return 'success';
        }
        if (in_array($code, ['cancelled', 'returned'], true)) {
            return 'danger';
        }

        return 'neutral';
    }

    /**
     * @param array<int, array{name:string,color_hex:string,items:array<int, array{code:string,name:string}>}> $config
     * @return array<string, string>
     */
    private function statusLabelMap(array $config): array
    {
        $map = [];
        foreach ($config as $group) {
            $items = is_array($group['items'] ?? null) ? $group['items'] : [];
            foreach ($items as $item) {
                $code = strtolower(trim((string) ($item['code'] ?? '')));
                if ($code === '') {
                    continue;
                }
                $map[$code] = (string) ($item['name'] ?? $code);
            }
        }

        return $map;
    }

    /**
     * @param array<int, array{name:string,color_hex:string,items:array<int, array{code:string,name:string}>}> $config
     * @return array<string, string>
     */
    private function statusColorMap(array $config): array
    {
        $map = [];
        foreach ($config as $group) {
            $groupColor = StringHelper::normalizeColorHex((string) ($group['color_hex'] ?? ''));
            if ($groupColor === '') {
                continue;
            }
            $items = is_array($group['items'] ?? null) ? $group['items'] : [];
            foreach ($items as $item) {
                $code = strtolower(trim((string) ($item['code'] ?? '')));
                if ($code !== '') {
                    $map[$code] = $groupColor;
                }
            }
        }

        return $map;
    }

    /**
     * @param array<string, string> $statusCodes
     * @param array<string, string> $statusLabelMap
     * @return array<string, string>
     */
    private function buildStatusFilterOptions(array $statusCodes, array $statusLabelMap): array
    {
        $options = [];
        foreach ($statusCodes as $code => $value) {
            $rawCode = trim((string) ($code !== '' ? $code : $value));
            if ($rawCode === '') {
                continue;
            }
            $normalizedCode = strtolower($rawCode);
            $options[$normalizedCode] = $this->statusLabel($normalizedCode, $statusLabelMap);
        }

        return $options;
    }

    /**
     * @param array<int, array<string, mixed>> $itemsPreview
     */
    private function productsHtml(array $itemsPreview, int $itemsCount, string $itemsQty, int $projectsDone = 0, int $projectsTotal = 0): string
    {
        if ($itemsPreview === []) {
            return '<div class="orders-products">'
                . '<div class="orders-products__meta">0 pozycji / 0.000 szt.</div>'
                . '</div>';
        }

        $html = '<div class="orders-products">';
        foreach ($itemsPreview as $item) {
            $name = trim((string) ($item['name'] ?? ''));
            $qty = $this->formatQuantity((float) ($item['quantity'] ?? 0));
            $mediaUrl = trim((string) ($item['media_url'] ?? ''));

            $thumb = $mediaUrl !== ''
                ? '<span class="orders-image-hover-wrap">'
                    . '<img class="orders-product__thumb" src="' . htmlspecialchars($mediaUrl, ENT_QUOTES, 'UTF-8') . '" alt="">'
                    . '<img class="orders-image-hover-popup" src="' . htmlspecialchars($mediaUrl, ENT_QUOTES, 'UTF-8') . '" alt="">'
                    . '</span>'
                : '<span class="orders-product__thumb orders-product__thumb--empty"></span>';

            $html .= '<div class="orders-product">'
                . $thumb
                . '<div class="orders-product__txt">'
                . '<div class="orders-product__name"' . ($name !== '' ? ' title="' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '"' : '') . '>' . htmlspecialchars($name !== '' ? $name : '-', ENT_QUOTES, 'UTF-8') . '</div>'
                . '<div class="orders-product__qty">' . htmlspecialchars($qty, ENT_QUOTES, 'UTF-8') . ' szt.</div>'
                . '</div>'
                . '</div>';
        }

        if ($itemsCount > count($itemsPreview)) {
            $html .= '<div class="orders-products__more">+' . ($itemsCount - count($itemsPreview)) . ' pozycji</div>';
        }
        $html .= '<div class="orders-products__meta">' . $itemsCount . ' pozycji / ' . htmlspecialchars($itemsQty, ENT_QUOTES, 'UTF-8') . ' szt.'
            . $this->projectBadge($projectsDone, $projectsTotal)
            . '</div>';
        $html .= '</div>';

        return $html;
    }

    private function projectBadge(int $done, int $total): string
    {
        if ($total === 0) {
            return '';
        }

        if ($done === $total) {
            return ' <span class="project-badge project-badge--done" title="Wszystkie projekty wygenerowane (' . $done . '/' . $total . ')">'
                . '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>'
                . '</span>';
        }

        if ($done > 0) {
            return ' <span class="project-badge project-badge--partial" title="Projekty: ' . $done . '/' . $total . '">'
                . $done . '/' . $total
                . '</span>';
        }

        return ' <span class="project-badge project-badge--none" title="Brak wygenerowanych projektow (0/' . $total . ')">'
            . '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>'
            . '</span>';
    }

    private function shippingHtml(string $deliveryMethod, int $shipments, int $documents): string
    {
        $deliveryMethod = trim(html_entity_decode(strip_tags($deliveryMethod), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        $html = '<div class="orders-mini">';
        if ($deliveryMethod !== '' && !preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $deliveryMethod)) {
            $html .= '<div class="orders-mini__delivery">' . htmlspecialchars($deliveryMethod, ENT_QUOTES, 'UTF-8') . '</div>';
        }
        $html .= '<div>wys.: <strong>' . $shipments . '</strong> dok.: <strong>' . $documents . '</strong></div>';
        $html .= '</div>';

        return $html;
    }

    private function formatQuantity(float $value): string
    {
        $rounded = round($value, 3);
        if (abs($rounded - round($rounded)) < 0.0005) {
            return (string) (int) round($rounded);
        }

        $formatted = number_format($rounded, 3, '.', '');
        return rtrim(rtrim($formatted, '0'), '.');
    }

    /**
     * @return array<string, string>
     */
    private function paymentStatusFilterOptions(): array
    {
        return [
            '' => $this->translator->get('orders.filters.any'),
            '0' => 'nieoplacone',
            '1' => 'czesciowo oplacone',
            '2' => 'oplacone',
            '3' => 'zwrocone',
        ];
    }

    /**
     * @param array<int, array{name:string,color_hex:string,items:array<int, array{code:string,name:string}>}> $config
     * @return array<int, array{code:string, name:string, group:string}>
     */
    private function buildAllStatusOptions(array $config): array
    {
        $options = [];
        foreach ($config as $group) {
            $groupName = trim((string) ($group['name'] ?? ''));
            $items = is_array($group['items'] ?? null) ? $group['items'] : [];
            foreach ($items as $item) {
                $code = strtolower(trim((string) ($item['code'] ?? '')));
                if ($code === '') {
                    continue;
                }
                $options[] = [
                    'code' => $code,
                    'name' => (string) ($item['name'] ?? $code),
                    'group' => $groupName,
                ];
            }
        }

        return $options;
    }

    /**
     * @param array<int, array<string, mixed>> $history
     * @param array<string, string> $statusLabelMap
     * @return array<int, array<string, mixed>>
     */
    private function resolveHistoryLabels(array $history, array $statusLabelMap): array
    {
        return array_map(function (array $entry) use ($statusLabelMap): array {
            $fromCode = trim((string) ($entry['from_status_id'] ?? ''));
            $toCode = trim((string) ($entry['to_status_id'] ?? ''));
            $entry['from_label'] = $fromCode !== '' ? $this->statusLabel($fromCode, $statusLabelMap) : '-';
            $entry['to_label'] = $toCode !== '' ? $this->statusLabel($toCode, $statusLabelMap) : '-';

            return $entry;
        }, $history);
    }

    public function sendEmail(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        if ($orderId <= 0) {
            return Response::json(['success' => false, 'message' => 'Nieprawidlowe zamowienie'], 400);
        }

        $csrfToken = (string) $request->input('_token', '');
        if (!Csrf::validate($csrfToken)) {
            return Response::json(['success' => false, 'message' => 'Sesja wygasla, odswiez strone'], 403);
        }

        if ($this->emailService === null) {
            return Response::json(['success' => false, 'message' => 'Modul e-mail nie jest skonfigurowany'], 500);
        }

        $templateId = max(0, (int) $request->input('template_id', 0));
        if ($templateId <= 0) {
            return Response::json(['success' => false, 'message' => 'Wybierz szablon'], 400);
        }

        $mailboxId = (int) $request->input('mailbox_id', 0);
        $user = $this->auth->user();
        $userName = is_array($user) ? trim((string) ($user['name'] ?? $user['email'] ?? '')) : '';
        $result = $this->emailService->send($orderId, $templateId, $mailboxId > 0 ? $mailboxId : null, $userName !== '' ? $userName : null);

        return Response::json([
            'success' => $result['success'],
            'message' => $result['success'] ? 'E-mail wyslany pomyslnie' : ('Blad wysylki: ' . ($result['error'] ?? 'nieznany')),
        ]);
    }

    public function emailPreview(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $templateId = max(0, (int) $request->input('template_id', 0));

        if ($orderId <= 0 || $templateId <= 0 || $this->emailService === null) {
            return Response::json(['subject' => '', 'body_html' => '', 'attachments' => []], 400);
        }

        $preview = $this->emailService->preview($orderId, $templateId);

        return Response::json($preview);
    }

    public function addPayment(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        if ($orderId <= 0) {
            return Response::json(['ok' => false, 'error' => 'Nieprawidłowe ID zamówienia.'], 400);
        }

        if (!Csrf::validate((string) $request->input('_token', ''))) {
            return Response::json(['ok' => false, 'error' => 'Nieprawidłowy token CSRF.'], 403);
        }

        $amount = (float) $request->input('amount', 0);
        $paymentTypeId = trim((string) $request->input('payment_type_id', ''));
        $paymentDate = trim((string) $request->input('payment_date', ''));
        $comment = trim((string) $request->input('comment', ''));

        if ($amount <= 0) {
            return Response::json(['ok' => false, 'error' => 'Kwota musi być większa od 0.'], 422);
        }
        if ($paymentTypeId === '') {
            return Response::json(['ok' => false, 'error' => 'Wybierz typ płatności.'], 422);
        }

        try {
            $result = $this->orders->addPayment($orderId, [
                'amount' => $amount,
                'payment_type_id' => $paymentTypeId,
                'payment_date' => $paymentDate !== '' ? $paymentDate . ' ' . date('H:i:s') : '',
                'comment' => $comment,
            ]);
        } catch (\Throwable $ex) {
            return Response::json(['ok' => false, 'error' => 'Błąd zapisu: ' . $ex->getMessage()], 500);
        }

        if ($result === null) {
            return Response::json(['ok' => false, 'error' => 'Nie udało się zapisać płatności.'], 500);
        }

        $this->orders->recordActivity(
            $orderId,
            'payment',
            'Dodano płatność: ' . number_format($amount, 2, '.', ' ') . ' PLN (' . $paymentTypeId . ')',
            ['payment_id' => $result['id'], 'amount' => $amount, 'type' => $paymentTypeId],
            'user',
            ($this->auth->user() ?? [])['name'] ?? null
        );

        try {
            $this->automation?->trigger('payment.status_changed', $orderId, [
                'new_payment_status' => (string) $result['payment_status'],
                'total_paid' => $result['total_paid'],
                'payment_type_id' => $paymentTypeId,
            ]);
        } catch (\Throwable) {
        }

        $this->pushPaymentToShoppro($orderId, $result['payment_status']);

        return Response::json([
            'ok' => true,
            'payment_id' => $result['id'],
            'payment_status' => $result['payment_status'],
            'total_paid' => $result['total_paid'],
        ]);
    }

    private function pushPaymentToShoppro(int $orderId, int $paymentStatus): void
    {
        if ($paymentStatus !== 2 || $this->shopproIntegrations === null) {
            return;
        }

        try {
            $orderStmt = $this->orders->findOrderSourceInfo($orderId);
            if ($orderStmt === null || ($orderStmt['source'] ?? '') !== 'shoppro') {
                return;
            }

            $integrationId = (int) ($orderStmt['integration_id'] ?? 0);
            $sourceOrderId = trim((string) ($orderStmt['source_order_id'] ?? ''));
            if ($integrationId <= 0 || $sourceOrderId === '') {
                return;
            }

            $integration = $this->shopproIntegrations->findIntegration($integrationId);
            if ($integration === null || empty($integration['is_active']) || empty($integration['has_api_key'])) {
                return;
            }

            $baseUrl = trim((string) ($integration['base_url'] ?? ''));
            $apiKey = $this->shopproIntegrations->getApiKeyDecrypted($integrationId);
            if ($baseUrl === '' || $apiKey === null || trim($apiKey) === '') {
                return;
            }

            $client = new ShopproApiClient();
            $pushResult = $client->setOrderPaid($baseUrl, $apiKey, 10, $sourceOrderId);

            $this->orders->recordActivity(
                $orderId,
                'sync',
                $pushResult['ok']
                    ? 'Wysłano status płatności do shopPRO (opłacone)'
                    : 'Błąd push płatności do shopPRO: ' . ($pushResult['message'] ?? 'unknown'),
                ['direction' => 'push', 'target' => 'shoppro', 'ok' => $pushResult['ok']],
                'system'
            );
        } catch (\Throwable) {
        }
    }

    public function quickSearch(Request $request): Response
    {
        $query = trim((string) $request->input('q', ''));
        if ($query === '' || mb_strlen($query) < 2) {
            return Response::json(['results' => []]);
        }

        $limit = min((int) $request->input('limit', 10), 20);
        $results = $this->orders->quickSearch($query, $limit);

        return Response::json(['results' => $results]);
    }

    public function preview(Request $request): Response
    {
        $orderId = max(0, (int) $request->input('id', 0));
        $details = $this->orders->findDetails($orderId);
        if ($details === null) {
            return Response::html('<div class="order-preview-error">Zamowienie nie znalezione.</div>', 404);
        }

        $order = is_array($details['order'] ?? null) ? $details['order'] : [];
        $items = is_array($details['items'] ?? null) ? $details['items'] : [];
        $addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
        $notes = is_array($details['notes'] ?? null) ? $details['notes'] : [];

        $addressByType = ['customer' => null, 'delivery' => null, 'invoice' => null];
        foreach ($addresses as $address) {
            $type = (string) ($address['address_type'] ?? '');
            if ($type !== '' && array_key_exists($type, $addressByType) && $addressByType[$type] === null) {
                $addressByType[$type] = $address;
            }
        }

        $html = $this->template->render('orders/partials/preview-content', [
            'order' => $order,
            'items' => $items,
            'addressByType' => $addressByType,
            'notes' => $notes,
        ]);

        return Response::html($html);
    }

}
