<?php

namespace App\Controllers;

use App\Models\BarberProfileModel;
use App\Models\ItemModel;
use App\Models\MemberModel;
use App\Models\MemberPointModel;
use App\Models\RewardModel;
use App\Models\SettingModel;
use App\Models\TransactionModel;
use App\Models\UserModel;

class Dashboard extends BaseController
{
    public function owner()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $filters = [
            'range' => $this->request->getGet('range') ?? 'weekly',
            'barber' => $this->request->getGet('barber') ?? '',
        ];

        $data = $this->getOwnerData($filters);
        $data['selectedRange'] = $filters['range'];
        $data['selectedBarber'] = $filters['barber'];
        $data['hourlyChart'] = (new TransactionModel())->getHourlySalesToday();
        return $this->renderOwnerPage('owner/dashboard', $data, 'dashboard');
    }

    public function chartData()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $filters = [
            'range' => $this->request->getGet('range') ?? 'weekly',
            'barber' => $this->request->getGet('barber') ?? '',
        ];

        $transactionModel = new TransactionModel();
        $series = $transactionModel->getSalesSeries($filters['range'], $filters['barber']);
        return $this->response->setJSON([
            'labels' => $series['labels'],
            'data' => $series['data'],
        ]);
    }

    public function users()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $data = $this->getOwnerData();
        $data['editUser'] = $this->request->getGet('edit_user') ? (new UserModel())->find($this->request->getGet('edit_user')) : null;
        return $this->renderOwnerPage('owner/users', $data, 'users');
    }

    public function products()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $data = $this->getOwnerData();
        $data['editItem'] = $this->request->getGet('edit_item') ? (new ItemModel())->find($this->request->getGet('edit_item')) : null;

        $selectedType = $this->request->getGet('type') ?? '';
        $itemModel = new ItemModel();
        $data['items'] = $selectedType !== '' ? $itemModel->where('type', $selectedType)->findAll() : $itemModel->findAll();
        $data['selectedType'] = $selectedType;

        return $this->renderOwnerPage('owner/products', $data, 'products');
    }

    public function employees()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $data = $this->getOwnerData();
        $data['editBarber'] = $this->request->getGet('edit_barber') ? (new BarberProfileModel())->find($this->request->getGet('edit_barber')) : null;
        return $this->renderOwnerPage('owner/employees', $data, 'employees');
    }

    public function members()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $memberModel = new MemberModel();
        $data = [
            'title' => 'Manajemen Member',
            'user' => $this->session->get('user'),
            'members' => $memberModel->orderBy('name', 'ASC')->findAll(),
            'editMember' => $this->request->getGet('edit_member') ? $memberModel->find($this->request->getGet('edit_member')) : null,
        ];

        return $this->renderOwnerPage('owner/members', $data, 'members');
    }

    public function cashierDashboard()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $rewardModel = new RewardModel();
        $activePromosToday = array_values(array_filter(
            $rewardModel->getWithItemNames(),
            static fn (array $reward): bool => $rewardModel->isAvailableToday($reward)
        ));

        $data = [
            'title' => 'Dashboard',
            'user' => $this->session->get('user'),
            'activePromosToday' => $activePromosToday,
        ];

        return $this->renderOwnerPage('dashboard/cashier_dashboard', $data, 'cashier_dashboard');
    }

    public function memberPoints()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $filters = ['member_id' => $this->request->getGet('member_id') ?? ''];

        $data = $this->getOwnerData();
        $data['pointHistory'] = (new MemberPointModel())->getHistory($filters);
        $data['members'] = (new MemberModel())->orderBy('name', 'ASC')->findAll();
        $data['filters'] = $filters;

        return $this->renderOwnerPage('owner/member_points', $data, 'member_points');
    }

    public function deletePointEntry($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        (new MemberPointModel())->deleteEntry((int) $id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/member-points');
    }

    public function settingsPoints()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $settingModel = new SettingModel();
        $data = $this->getOwnerData();
        $data['rupiahPerPoint'] = (int) $settingModel->getValue('points_rupiah_per_point', '10000');
        $data['pointsFeatureEnabled'] = $settingModel->getValue('points_feature_enabled', '1') === '1';

        return $this->renderOwnerPage('owner/settings_points', $data, 'settings_points');
    }

    public function saveSettingsPoints()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $rupiahPerPoint = (int) $this->request->getPost('rupiah_per_point');
        if ($rupiahPerPoint < 1) {
            $rupiahPerPoint = 10000;
        }

        $pointsEnabled = $this->request->getPost('points_enabled') ? '1' : '0';

        $settingModel = new SettingModel();
        $settingModel->setValue('points_rupiah_per_point', (string) $rupiahPerPoint);
        $settingModel->setValue('points_feature_enabled', $pointsEnabled);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Pengaturan Berhasil Disimpan']);
        }

        return redirect()->to('/owner/settings-points');
    }

    public function rewards()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $rewardModel = new RewardModel();
        $data = $this->getOwnerData();
        $data['rewards'] = $rewardModel->getWithItemNames();
        $data['items'] = (new ItemModel())->orderBy('name', 'ASC')->findAll();
        $data['editReward'] = $this->request->getGet('edit_reward') ? $rewardModel->find($this->request->getGet('edit_reward')) : null;

        return $this->renderOwnerPage('owner/rewards', $data, 'rewards');
    }

    public function saveReward()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        (new RewardModel())->insert([
            'name' => $this->request->getPost('name'),
            'item_id' => (int) $this->request->getPost('item_id'),
            'discount_type' => $this->request->getPost('discount_type') === 'fixed' ? 'fixed' : 'percent',
            'discount_value' => (int) $this->request->getPost('discount_value'),
            'points_required' => (int) $this->request->getPost('points_required'),
            'start_date' => $this->request->getPost('start_date') ?: null,
            'end_date' => $this->request->getPost('end_date') ?: null,
            'is_active' => 1,
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Disimpan']);
        }

        return redirect()->to('/owner/rewards');
    }

    public function updateReward()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        (new RewardModel())->update((int) $this->request->getPost('id'), [
            'name' => $this->request->getPost('name'),
            'item_id' => (int) $this->request->getPost('item_id'),
            'discount_type' => $this->request->getPost('discount_type') === 'fixed' ? 'fixed' : 'percent',
            'discount_value' => (int) $this->request->getPost('discount_value'),
            'points_required' => (int) $this->request->getPost('points_required'),
            'start_date' => $this->request->getPost('start_date') ?: null,
            'end_date' => $this->request->getPost('end_date') ?: null,
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Diupdate']);
        }

        return redirect()->to('/owner/rewards');
    }

    public function toggleReward($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $rewardModel = new RewardModel();
        $reward = $rewardModel->find($id);
        if ($reward) {
            $rewardModel->update($id, ['is_active' => (int) $reward['is_active'] === 1 ? 0 : 1]);
        }

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Status Reward Diperbarui']);
        }

        return redirect()->to('/owner/rewards');
    }

    public function deleteReward($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        (new RewardModel())->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/rewards');
    }

    public function reports()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $filters = $this->getReportFilters();
        $transactionModel = new TransactionModel();
        $data = $this->getOwnerData($filters);
        $data['editTransaction'] = $this->request->getGet('edit_transaction') ? $transactionModel->find($this->request->getGet('edit_transaction')) : null;
        $data['filters'] = $filters;

        $allTransactions = $data['transactions'];
        $perPage = (int) ($this->request->getGet('per_page') ?? 10);
        if (!in_array($perPage, [10, 25, 50, 100], true)) {
            $perPage = 10;
        }
        $totalRows = count($allTransactions);
        $totalPages = max(1, (int) ceil($totalRows / $perPage));
        $currentPage = max(1, min((int) ($this->request->getGet('page') ?? 1), $totalPages));

        $data['transactions'] = array_slice($allTransactions, ($currentPage - 1) * $perPage, $perPage);
        $data['perPage'] = $perPage;
        $data['currentPage'] = $currentPage;
        $data['totalPages'] = $totalPages;
        $data['totalRows'] = $totalRows;
        $data['totalPointsRedeemed'] = (new MemberPointModel())->sumRedeemedPointsForFilters($filters);

        return $this->renderOwnerPage('owner/reports', $data, 'reports');
    }

    public function reportMonthly()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $year = (int) ($this->request->getGet('year') ?? date('Y'));
        $month = (int) ($this->request->getGet('month') ?? date('n'));
        $month = max(1, min(12, $month));

        $builder = (new TransactionModel())->builder();
        $builder->where('YEAR(created_at)', $year)->where('MONTH(created_at)', $month);
        $transactions = $builder->orderBy('created_at', 'ASC')->get()->getResultArray();

        $daysInMonth = (int) date('t', mktime(0, 0, 0, $month, 1, $year));
        $dailyBreakdown = [];
        for ($d = 1; $d <= $daysInMonth; $d++) {
            $dailyBreakdown[$d] = ['label' => sprintf('%02d/%02d', $d, $month), 'count' => 0, 'total' => 0];
        }

        $totalSales = 0;
        foreach ($transactions as $transaction) {
            $day = (int) date('j', strtotime($transaction['created_at']));
            $dailyBreakdown[$day]['count']++;
            $dailyBreakdown[$day]['total'] += (int) ($transaction['total'] ?? 0);
            $totalSales += (int) ($transaction['total'] ?? 0);
        }

        $data = $this->getOwnerData();
        $data['selectedYear'] = $year;
        $data['selectedMonth'] = $month;
        $data['breakdown'] = array_values($dailyBreakdown);
        $data['totalSales'] = $totalSales;
        $data['totalTransactions'] = count($transactions);
        $data['paymentBreakdown'] = $this->summarizePaymentMethods($transactions);
        $data['totalPointsRedeemed'] = (new MemberPointModel())->sumRedeemedPointsForFilters([
            'date_from' => sprintf('%04d-%02d-01', $year, $month),
            'date_to' => sprintf('%04d-%02d-%02d', $year, $month, $daysInMonth),
        ]);

        return $this->renderOwnerPage('owner/report_monthly', $data, 'report_monthly');
    }

    public function reportDaily()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $date = $this->request->getGet('date') ?: date('Y-m-d');

        $builder = (new TransactionModel())->builder();
        $builder->where('created_at >=', $date . ' 00:00:00')->where('created_at <=', $date . ' 23:59:59');
        $transactions = $builder->orderBy('created_at', 'ASC')->get()->getResultArray();

        $serviceCount = [];
        foreach ($transactions as $transaction) {
            $items = $transaction['items'] ?? [];
            if (is_string($items)) {
                $items = json_decode($items, true) ?? [];
            }
            if (!is_array($items)) {
                $items = [];
            }
            foreach ($items as $item) {
                $name = $item['name'] ?? 'Lainnya';
                $serviceCount[$name] = ($serviceCount[$name] ?? 0) + 1;
            }
        }
        arsort($serviceCount);
        $topServices = [];
        foreach (array_slice($serviceCount, 0, 5, true) as $name => $count) {
            $topServices[] = ['name' => $name, 'count' => $count];
        }

        $data = $this->getOwnerData();
        $data['selectedDate'] = $date;
        $data['dailyTransactions'] = $transactions;
        $data['summary'] = $this->summarizeTransactions($transactions);
        $data['paymentBreakdown'] = $this->summarizePaymentMethods($transactions);
        $data['hourly'] = (new TransactionModel())->getHourlySales($date);
        $data['topServices'] = $topServices;
        $data['totalPointsRedeemed'] = (new MemberPointModel())->sumRedeemedPointsForFilters(['date_from' => $date, 'date_to' => $date]);

        return $this->renderOwnerPage('owner/report_daily', $data, 'report_daily');
    }

    public function reportYearly()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $year = (int) ($this->request->getGet('year') ?? date('Y'));

        $builder = (new TransactionModel())->builder();
        $builder->where('YEAR(created_at)', $year);
        $transactions = $builder->orderBy('created_at', 'ASC')->get()->getResultArray();

        $monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
        $monthlyBreakdown = [];
        for ($m = 1; $m <= 12; $m++) {
            $monthlyBreakdown[$m] = ['label' => $monthNames[$m - 1], 'count' => 0, 'total' => 0];
        }

        $totalSales = 0;
        foreach ($transactions as $transaction) {
            $month = (int) date('n', strtotime($transaction['created_at']));
            $monthlyBreakdown[$month]['count']++;
            $monthlyBreakdown[$month]['total'] += (int) ($transaction['total'] ?? 0);
            $totalSales += (int) ($transaction['total'] ?? 0);
        }

        $data = $this->getOwnerData();
        $data['selectedYear'] = $year;
        $data['breakdown'] = array_values($monthlyBreakdown);
        $data['totalSales'] = $totalSales;
        $data['totalTransactions'] = count($transactions);
        $data['paymentBreakdown'] = $this->summarizePaymentMethods($transactions);
        $data['totalPointsRedeemed'] = (new MemberPointModel())->sumRedeemedPointsForFilters([
            'date_from' => sprintf('%04d-01-01', $year),
            'date_to' => sprintf('%04d-12-31', $year),
        ]);

        return $this->renderOwnerPage('owner/report_yearly', $data, 'report_yearly');
    }

    private function summarizePaymentMethods(array $transactions): array
    {
        $labels = ['cash' => 'Tunai', 'debit' => 'Kartu Debit', 'qris' => 'QRIS', 'transfer' => 'Transfer Bank'];
        $totals = ['cash' => 0, 'debit' => 0, 'qris' => 0, 'transfer' => 0];

        foreach ($transactions as $transaction) {
            $method = $transaction['payment_method'] ?? 'cash';
            if (!isset($totals[$method])) {
                $method = 'cash';
            }
            $totals[$method] += (int) ($transaction['total'] ?? 0);
        }

        $breakdown = [];
        foreach ($totals as $method => $amount) {
            $breakdown[] = ['method' => $method, 'label' => $labels[$method], 'total' => $amount];
        }

        return $breakdown;
    }

    public function exportReports()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $filters = $this->getReportFilters();
        $transactionModel = new TransactionModel();
        $transactions = $transactionModel->getFilteredTransactions($filters);
        $totalPointsRedeemed = (new MemberPointModel())->sumRedeemedPointsForFilters($filters);
        $type = strtolower($this->request->getGet('type') ?? 'excel');

        if ($type === 'pdf') {
            $content = $this->buildPdfReport($transactions, $filters, $totalPointsRedeemed);
            $this->response->setHeader('Content-Type', 'application/pdf');
            $this->response->setHeader('Content-Disposition', 'attachment; filename="laporan-penjualan.pdf"');
            return $this->response->setBody($content);
        }

        $content = $this->buildExcelReport($transactions, $filters, $totalPointsRedeemed);
        $this->response->setHeader('Content-Type', 'application/vnd.ms-excel; charset=utf-8');
        $this->response->setHeader('Content-Disposition', 'attachment; filename="laporan-penjualan.xls"');
        return $this->response->setBody($content);
    }

    public function transactionHistory()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $filters = [
            'date_from' => $this->request->getGet('date_from') ?? '',
            'date_to' => $this->request->getGet('date_to') ?? '',
            'barber' => $this->request->getGet('barber') ?? '',
        ];

        $transactionModel = new TransactionModel();
        $barberProfileModel = new BarberProfileModel();

        $allTransactions = $transactionModel->getFilteredTransactions($filters);
        $perPage = (int) ($this->request->getGet('per_page') ?? 10);
        if (!in_array($perPage, [10, 25, 50, 100], true)) {
            $perPage = 10;
        }
        $totalRows = count($allTransactions);
        $totalPages = max(1, (int) ceil($totalRows / $perPage));
        $currentPage = max(1, min((int) ($this->request->getGet('page') ?? 1), $totalPages));

        $pageTransactions = array_slice($allTransactions, ($currentPage - 1) * $perPage, $perPage);
        $transactionIds = array_column($pageTransactions, 'id');
        $memberPointModel = new MemberPointModel();
        $redeemedMap = $memberPointModel->getPointsByTransactionIds($transactionIds, 'redeem');
        $earnedMap = $memberPointModel->getPointsByTransactionIds($transactionIds, 'earn');
        foreach ($pageTransactions as &$transaction) {
            $transaction['points_redeemed'] = $redeemedMap[(int) $transaction['id']] ?? 0;
            $transaction['points_earned'] = $earnedMap[(int) $transaction['id']] ?? 0;
        }
        unset($transaction);

        $data = [
            'title' => 'Riwayat Transaksi',
            'user' => $this->session->get('user'),
            'transactions' => $pageTransactions,
            'barbers' => $barberProfileModel->findAll(),
            'filters' => $filters,
            'perPage' => $perPage,
            'currentPage' => $currentPage,
            'totalPages' => $totalPages,
            'totalRows' => $totalRows,
        ];

        $user = $this->session->get('user');
        if ($user['role'] === 'owner') {
            return $this->renderOwnerPage('owner/transaction_history', $data, 'history');
        }

        return $this->renderOwnerPage('dashboard/transaction_history', $data, 'history');
    }

    public function searchMember()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $query = trim((string) $this->request->getGet('q'));
        if ($query === '') {
            return $this->response->setJSON([]);
        }

        $results = (new MemberModel())
            ->groupStart()
                ->like('name', $query)
                ->orLike('email', $query)
                ->orLike('phone', $query)
            ->groupEnd()
            ->orderBy('name', 'ASC')
            ->findAll(8);

        return $this->response->setJSON($results);
    }

    public function cashier()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $barberProfileModel = new BarberProfileModel();
        $itemModel = new ItemModel();
        $rewardModel = new RewardModel();
        $pointsFeatureEnabled = (new SettingModel())->getValue('points_feature_enabled', '1') === '1';
        $itemServices = $itemModel->where('type', 'service')->findAll();
        $products = $itemModel->where('type', 'product')->findAll();
        $barbers = $barberProfileModel->findAll();
        $cart = $this->session->get('cart') ?? [];
        $customerName = $this->session->get('customer_name');
        $barberName = $this->session->get('barber_name');
        $memberId = $this->session->get('member_id');
        $receipt = $this->session->get('last_receipt');

        $member = $memberId ? (new MemberModel())->find($memberId) : null;

        $appliedReward = null;
        $appliedRewardId = $this->session->get('applied_reward_id');
        if (!$pointsFeatureEnabled && $appliedRewardId) {
            $this->session->remove('applied_reward_id');
            $appliedRewardId = null;
        }
        if ($pointsFeatureEnabled && $appliedRewardId) {
            $appliedReward = $rewardModel->find($appliedRewardId);
        }

        $cartItemIds = array_map('intval', array_column($cart, 'id'));
        $availableRewards = [];
        if ($pointsFeatureEnabled && $member && !empty($cartItemIds)) {
            foreach ($rewardModel->whereIn('item_id', $cartItemIds)->findAll() as $rewardOption) {
                if ($rewardModel->isAvailableToday($rewardOption) && (int) $member['points'] >= (int) $rewardOption['points_required']) {
                    $availableRewards[] = $rewardOption;
                }
            }
        }

        if ($appliedReward) {
            $itemInCart = in_array((int) $appliedReward['item_id'], $cartItemIds, true);
            $canAfford = $member && (int) $member['points'] >= (int) $appliedReward['points_required'];
            $stillAvailable = $rewardModel->isAvailableToday($appliedReward);
            if (!$itemInCart || !$canAfford || !$stillAvailable) {
                $this->session->remove('applied_reward_id');
                $appliedReward = null;
            }
        }

        $totals = $this->calculateCartTotals($cart, $appliedReward);

        $data = [
            'title' => 'Dashboard Kasir',
            'user' => $this->session->get('user'),
            'itemServices' => $itemServices,
            'products' => $products,
            'barbers' => $barbers,
            'member' => $member,
            'availableRewards' => $availableRewards,
            'appliedReward' => $appliedReward,
            'pointsFeatureEnabled' => $pointsFeatureEnabled,
            'cart' => $cart,
            'customerName' => $customerName,
            'barberName' => $barberName,
            'memberId' => $memberId,
            'subtotal' => $totals['subtotal'],
            'discount' => $totals['discount'],
            'total' => $totals['total'],
            'receipt' => $receipt,
        ];

        return $this->renderOwnerPage('dashboard/cashier', $data, 'cashier');
    }

    public function applyReward($id)
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $pointsFeatureEnabled = (new SettingModel())->getValue('points_feature_enabled', '1') === '1';
        $rewardModel = new RewardModel();
        $reward = $rewardModel->find($id);
        $memberId = $this->session->get('member_id');
        $member = $memberId ? (new MemberModel())->find($memberId) : null;
        $cart = $this->session->get('cart') ?? [];
        $cartItemIds = array_map('intval', array_column($cart, 'id'));

        if ($pointsFeatureEnabled && $reward && $rewardModel->isAvailableToday($reward) && $member && (int) $member['points'] >= (int) $reward['points_required'] && in_array((int) $reward['item_id'], $cartItemIds, true)) {
            $this->session->set('applied_reward_id', $reward['id']);
        }

        return redirect()->to('/cashier');
    }

    public function removeReward()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $this->session->remove('applied_reward_id');

        return redirect()->to('/cashier');
    }

    public function addService()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $selection = $this->request->getPost('service');
        [, $sourceId] = array_pad(explode('-', (string) $selection, 2), 2, null);

        $product = (new ItemModel())->find($sourceId);

        if ($product) {
            $cart = $this->session->get('cart') ?? [];
            $cart[] = ['id' => $product['id'], 'name' => $product['name'], 'price' => $product['price']];
            $this->session->set('cart', $cart);
        }

        $this->session->set('customer_name', $this->request->getPost('customer_name'));
        $this->session->set('barber_name', $this->request->getPost('barber_name'));
        $this->session->set('member_id', $this->request->getPost('member_id') ?: null);

        return redirect()->to('/cashier');
    }

    public function checkout()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $cart = $this->session->get('cart') ?? [];
        if (empty($cart)) {
            return redirect()->to('/cashier');
        }

        $pointsFeatureEnabled = (new SettingModel())->getValue('points_feature_enabled', '1') === '1';
        $memberId = $this->session->get('member_id') ?: null;
        $member = $memberId ? (new MemberModel())->find($memberId) : null;

        $appliedReward = null;
        $appliedRewardId = $this->session->get('applied_reward_id');
        if ($pointsFeatureEnabled && $appliedRewardId) {
            $rewardModel = new RewardModel();
            $reward = $rewardModel->find($appliedRewardId);
            $cartItemIds = array_map('intval', array_column($cart, 'id'));
            if ($reward && $rewardModel->isAvailableToday($reward) && $member && (int) $member['points'] >= (int) $reward['points_required'] && in_array((int) $reward['item_id'], $cartItemIds, true)) {
                $appliedReward = $reward;
            }
        }

        $totals = $this->calculateCartTotals($cart, $appliedReward);
        $total = $totals['total'];
        $discount = $totals['discount'];

        $paymentMethod = $this->request->getPost('payment_method') ?: 'cash';
        $paymentMethods = ['cash', 'debit', 'qris', 'transfer'];
        if (!in_array($paymentMethod, $paymentMethods, true)) {
            $paymentMethod = 'cash';
        }

        $amountPaid = (int) $this->request->getPost('amount_paid');
        if ($paymentMethod === 'cash') {
            if ($amountPaid < $total) {
                return redirect()->to('/cashier');
            }
        } else {
            $amountPaid = $total;
        }

        $customerName = $this->session->get('customer_name') ?: 'Pelanggan';
        $barberName = $this->session->get('barber_name') ?: 'Belum ada';

        $transaction = [
            'customer' => $member ? $member['name'] : $customerName,
            'cashier' => $this->session->get('user')['name'],
            'barber' => $barberName,
            'items' => $cart,
            'total' => $total,
        ];

        $transactionModel = new TransactionModel();
        $transactionModel->insert([
            'customer' => $transaction['customer'],
            'cashier' => $transaction['cashier'],
            'barber' => $transaction['barber'],
            'member_id' => $member ? $member['id'] : null,
            'total' => $transaction['total'],
            'discount_amount' => $discount > 0 ? $discount : null,
            'reward_name' => $appliedReward ? $appliedReward['name'] : null,
            'payment_method' => $paymentMethod,
            'amount_paid' => $amountPaid,
            'items' => json_encode($transaction['items']),
            'created_at' => date('Y-m-d H:i:s'),
        ]);
        $transactionId = $transactionModel->getInsertID();

        $memberPointModel = new MemberPointModel();

        if ($appliedReward) {
            $memberPointModel->addEntry($member['id'], 'redeem', (int) $appliedReward['points_required'], 'Promo: ' . $appliedReward['name'], $transactionId);
        }

        $pointsEarned = 0;
        if ($pointsFeatureEnabled && $member) {
            $rupiahPerPoint = max(1, (int) (new SettingModel())->getValue('points_rupiah_per_point', '10000'));
            $pointsEarned = intdiv($total, $rupiahPerPoint);
            if ($pointsEarned > 0) {
                $memberPointModel->addEntry($member['id'], 'earn', $pointsEarned, 'Transaksi #' . $transactionId, $transactionId);
            }
        }

        $this->session->set('last_receipt', [
            'id' => $transactionId,
            'customer' => $transaction['customer'],
            'cashier' => $transaction['cashier'],
            'barber' => $transaction['barber'],
            'items' => $transaction['items'],
            'total' => $transaction['total'],
            'discount' => $discount,
            'reward_name' => $appliedReward ? $appliedReward['name'] : null,
            'points_redeemed' => $appliedReward ? (int) $appliedReward['points_required'] : 0,
            'payment_method' => $paymentMethod,
            'amount_paid' => $amountPaid,
            'change' => $amountPaid - $total,
            'member_name' => $member ? $member['name'] : null,
            'points_earned' => $pointsEarned,
            'created_at' => date('d/m/Y H:i'),
        ]);
        $this->session->remove('cart');
        $this->session->remove('customer_name');
        $this->session->remove('barber_name');
        $this->session->remove('member_id');
        $this->session->remove('applied_reward_id');

        return redirect()->to('/cashier');
    }

    public function newTransaction()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $this->session->remove('last_receipt');

        return redirect()->to('/cashier');
    }

    public function removeCartItem($index)
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $cart = $this->session->get('cart') ?? [];
        $index = (int) $index;
        if (isset($cart[$index])) {
            unset($cart[$index]);
            $this->session->set('cart', array_values($cart));
        }

        return redirect()->to('/cashier');
    }

    public function clearCart()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $this->session->remove('cart');
        $this->session->remove('customer_name');

        return redirect()->to('/cashier');
    }

    public function saveUser()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $userModel = new UserModel();
        $username = $this->request->getPost('username');

        if ($userModel->where('username', $username)->first()) {
            if ($this->isAjaxRequest()) {
                return $this->jsonResponse(['success' => false, 'message' => 'Username sudah digunakan']);
            }

            $data = $this->getOwnerData();
            $data['errorUser'] = 'Username sudah digunakan';
            return $this->renderOwnerPage('owner/users', $data, 'users');
        }

        $userModel->insert([
            'username' => $username,
            'password' => password_hash($this->request->getPost('password'), PASSWORD_DEFAULT),
            'name' => $this->request->getPost('name'),
            'role' => $this->request->getPost('role'),
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Disimpan']);
        }

        return redirect()->to('/owner/users');
    }

    public function saveItem()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $itemModel = new ItemModel();
        $itemModel->insert([
            'name' => $this->request->getPost('name'),
            'price' => (int) $this->request->getPost('price'),
            'type' => $this->request->getPost('type'),
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Disimpan']);
        }

        return redirect()->to('/owner/products');
    }

    public function saveBarber()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $barberModel = new BarberProfileModel();
        $barberModel->insert([
            'name' => $this->request->getPost('name'),
            'address' => $this->request->getPost('address'),
            'join_date' => $this->request->getPost('join_date') ?: null,
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Disimpan']);
        }

        return redirect()->to('/owner/employees');
    }

    public function saveMember()
    {
        $auth = $this->ensureAuth(['cashier', 'owner']);
        if ($auth) {
            return $auth;
        }

        $memberModel = new MemberModel();
        $memberModel->insert([
            'name' => $this->request->getPost('name'),
            'phone' => $this->request->getPost('phone'),
            'email' => $this->request->getPost('email'),
            'points' => 0,
            'created_at' => date('Y-m-d H:i:s'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Disimpan']);
        }

        return redirect()->to('/owner/members');
    }

    public function updateMember()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $memberId = (int) $this->request->getPost('id');
        $memberModel = new MemberModel();
        $memberModel->update($memberId, [
            'name' => $this->request->getPost('name'),
            'phone' => $this->request->getPost('phone'),
            'email' => $this->request->getPost('email'),
        ]);

        if ($this->request->getPost('points') !== null) {
            $member = $memberModel->find($memberId);
            $newPoints = max(0, (int) $this->request->getPost('points'));
            $delta = $newPoints - (int) $member['points'];
            if ($delta !== 0) {
                (new MemberPointModel())->addEntry(
                    $memberId,
                    $delta > 0 ? 'earn' : 'redeem',
                    abs($delta),
                    'Penyesuaian Manual oleh Owner',
                    null
                );
            }
        }

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Diupdate']);
        }

        return redirect()->to('/owner/members');
    }

    public function deleteMember($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $memberModel = new MemberModel();
        $memberModel->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/members');
    }

    public function updateUser()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $userModel = new UserModel();
        $id = (int) $this->request->getPost('id');
        $username = $this->request->getPost('username');

        $existing = $userModel->where('username', $username)->first();
        if ($existing && (int) $existing['id'] !== $id) {
            if ($this->isAjaxRequest()) {
                return $this->jsonResponse(['success' => false, 'message' => 'Username sudah digunakan']);
            }

            $data = $this->getOwnerData();
            $data['errorUser'] = 'Username sudah digunakan';
            return $this->renderOwnerPage('owner/users', $data, 'users');
        }

        $data = [
            'username' => $username,
            'name' => $this->request->getPost('name'),
            'role' => $this->request->getPost('role'),
        ];

        $password = trim($this->request->getPost('password'));
        if ($password !== '') {
            $data['password'] = password_hash($password, PASSWORD_DEFAULT);
        }

        $userModel->update($id, $data);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Diupdate']);
        }

        return redirect()->to('/owner/users');
    }

    public function updateItem()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $itemModel = new ItemModel();
        $itemModel->update((int) $this->request->getPost('id'), [
            'name' => $this->request->getPost('name'),
            'price' => (int) $this->request->getPost('price'),
            'type' => $this->request->getPost('type'),
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Diupdate']);
        }

        return redirect()->to('/owner/products');
    }

    public function updateBarber()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $barberModel = new BarberProfileModel();
        $barberModel->update((int) $this->request->getPost('id'), [
            'name' => $this->request->getPost('name'),
            'address' => $this->request->getPost('address'),
            'join_date' => $this->request->getPost('join_date') ?: null,
        ]);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Diupdate']);
        }

        return redirect()->to('/owner/employees');
    }

    public function updateTransaction()
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $transactionModel = new TransactionModel();
        $itemsInput = $this->request->getPost('items');
        $items = [];
        foreach (explode(',', $itemsInput) as $itemName) {
            $name = trim($itemName);
            if ($name !== '') {
                $items[] = ['name' => $name];
            }
        }

        $transactionModel->update((int) $this->request->getPost('id'), [
            'customer' => $this->request->getPost('customer'),
            'cashier' => $this->request->getPost('cashier'),
            'barber' => $this->request->getPost('barber'),
            'total' => (int) $this->request->getPost('total'),
            'items' => json_encode($items),
        ]);

        return redirect()->to('/owner/reports');
    }

    public function deleteUser($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $id = (int) $id;
        $currentUser = $this->session->get('user');
        $userModel = new UserModel();
        $target = $userModel->find($id);

        if ($currentUser && (int) $currentUser['id'] === $id) {
            $message = 'Tidak bisa menghapus akun Anda sendiri yang sedang login.';
        } elseif ($target && $target['role'] === 'owner' && $userModel->where('role', 'owner')->countAllResults() <= 1) {
            $message = 'Minimal harus ada satu akun Owner.';
        } else {
            $message = null;
        }

        if ($message !== null) {
            if ($this->isAjaxRequest()) {
                return $this->jsonResponse(['success' => false, 'message' => $message]);
            }

            return redirect()->to('/owner/users');
        }

        $userModel->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/users');
    }

    public function deleteItem($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $itemModel = new ItemModel();
        $itemModel->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/products');
    }

    public function deleteBarber($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $barberModel = new BarberProfileModel();
        $barberModel->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/employees');
    }

    public function deleteTransaction($id)
    {
        $auth = $this->ensureAuth('owner');
        if ($auth) {
            return $auth;
        }

        $transactionModel = new TransactionModel();
        $transactionModel->delete($id);

        if ($this->isAjaxRequest()) {
            return $this->jsonResponse(['success' => true, 'message' => 'Data Berhasil Dihapus']);
        }

        return redirect()->to('/owner/reports');
    }

    private function renderOwnerPage(string $view, array $data, string $activeMenu = ''): string
    {
        $data['activeMenu'] = $activeMenu;
        $data['content'] = view($view, $data);

        return view('layouts/owner_sidebar', $data);
    }

    private function ensureAuth($roles)
    {
        $user = $this->session->get('user');
        if (!$user) {
            return redirect()->to('/login');
        }

        $roles = is_array($roles) ? $roles : [$roles];
        if (!in_array($user['role'], $roles, true)) {
            return redirect()->to('/login');
        }
    }

    private function isAjaxRequest(): bool
    {
        return $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
    }

    private function jsonResponse(array $payload): \CodeIgniter\HTTP\ResponseInterface
    {
        return $this->response->setJSON($payload);
    }

    private function getOwnerData(array $filters = []): array
    {
        $transactionModel = new TransactionModel();
        $userModel = new UserModel();
        $barberModel = new BarberProfileModel();
        $itemModel = new ItemModel();

        $transactions = $transactionModel->getFilteredTransactions($filters);
        $range = $filters['range'] ?? 'weekly';

        $rewardModel = new RewardModel();
        $memberModel = new MemberModel();
        $activePromosToday = array_values(array_filter(
            $rewardModel->getWithItemNames(),
            static fn (array $reward): bool => $rewardModel->isAvailableToday($reward)
        ));

        return [
            'title' => 'Dashboard Owner',
            'user' => $this->session->get('user'),
            'transactions' => $transactions,
            'stats' => $this->summarizeTransactions($transactions),
            'dashboardStats' => $this->buildDashboardStats($transactions),
            'salesChart' => $this->buildSalesChart($transactions, $range),
            'users' => $userModel->findAll(),
            'barbers' => $barberModel->findAll(),
            'items' => $itemModel->findAll(),
            'selectedRange' => $range,
            'selectedBarber' => $filters['barber'] ?? '',
            'activePromosToday' => $activePromosToday,
            'promoUsageToday' => $transactionModel->countPromoUsageToday(),
            'totalMembers' => $memberModel->countAllResults(),
        ];
    }

    private function getReportFilters(): array
    {
        return [
            'date_from' => $this->request->getGet('date_from') ?? '',
            'date_to' => $this->request->getGet('date_to') ?? '',
            'barber' => $this->request->getGet('barber') ?? '',
            'payment_method' => $this->request->getGet('payment_method') ?? '',
        ];
    }

    private function services(): array
    {
        return [
            ['id' => 'cut', 'name' => 'Potong Rambut', 'price' => 60000],
            ['id' => 'beard', 'name' => 'Cukur Jenggot', 'price' => 35000],
            ['id' => 'wash', 'name' => 'Hair Wash', 'price' => 25000],
            ['id' => 'color', 'name' => 'Coloring', 'price' => 150000],
            ['id' => 'cream', 'name' => 'Cream Bath', 'price' => 40000],
        ];
    }

    private function sumCart(array $cart): int
    {
        $total = 0;
        foreach ($cart as $item) {
            $total += $item['price'];
        }
        return $total;
    }

    private function calculateCartTotals(array $cart, ?array $appliedReward): array
    {
        $subtotal = $this->sumCart($cart);
        $discount = 0;

        if ($appliedReward) {
            foreach ($cart as $item) {
                if ((int) ($item['id'] ?? 0) === (int) $appliedReward['item_id']) {
                    $discount = (new RewardModel())->calculateDiscount($appliedReward, (int) $item['price']);
                    break;
                }
            }
        }

        return [
            'subtotal' => $subtotal,
            'discount' => $discount,
            'total' => max(0, $subtotal - $discount),
        ];
    }

    private function buildDashboardStats(array $transactions): array
    {
        $today = date('Y-m-d');
        $thisMonth = date('Y-m');
        $todaySales = 0;
        $monthlySales = 0;
        $serviceCount = [];
        $paymentLabels = ['cash' => 'Tunai', 'debit' => 'Kartu Debit', 'qris' => 'QRIS', 'transfer' => 'Transfer Bank'];
        $paymentTotals = ['cash' => 0, 'debit' => 0, 'qris' => 0, 'transfer' => 0];

        foreach ($transactions as $transaction) {
            $createdAt = $transaction['created_at'] ?? '';
            $date = date('Y-m-d', strtotime($createdAt ?: 'now'));
            $month = date('Y-m', strtotime($createdAt ?: 'now'));

            if ($date === $today) {
                $todaySales += (int) ($transaction['total'] ?? 0);
                $method = $transaction['payment_method'] ?? 'cash';
                if (!isset($paymentTotals[$method])) {
                    $method = 'cash';
                }
                $paymentTotals[$method] += (int) ($transaction['total'] ?? 0);
            }
            if ($month === $thisMonth) {
                $monthlySales += (int) ($transaction['total'] ?? 0);
            }

            $items = $transaction['items'] ?? [];
            if (is_string($items)) {
                $items = json_decode($items, true) ?? [];
            }
            if (!is_array($items)) {
                $items = [];
            }

            foreach ($items as $item) {
                $name = $item['name'] ?? 'Lainnya';
                $serviceCount[$name] = ($serviceCount[$name] ?? 0) + 1;
            }
        }

        $topServices = [];
        arsort($serviceCount);
        foreach (array_slice($serviceCount, 0, 5, true) as $name => $count) {
            $topServices[] = ['name' => $name, 'count' => $count];
        }

        $paymentBreakdown = [];
        foreach ($paymentTotals as $method => $amount) {
            $paymentBreakdown[] = ['method' => $method, 'label' => $paymentLabels[$method], 'total' => $amount];
        }

        return [
            'todaySales' => $todaySales,
            'monthlySales' => $monthlySales,
            'topServices' => $topServices,
            'paymentBreakdown' => $paymentBreakdown,
        ];
    }

    private function buildSalesChart(array $transactions, string $range = 'weekly'): array
    {
        $series = (new TransactionModel())->getSalesSeries($range);
        return [
            'points' => array_map(static fn ($label, $value) => ['label' => $label, 'value' => $value], $series['labels'], $series['data']),
        ];
    }

    private function buildPdfReport(array $transactions, array $filters, int $totalPointsRedeemed = 0): string
    {
        $filterParts = [];
        foreach ($filters as $key => $value) {
            if ($value !== '') {
                $filterParts[] = $key . '=' . $value;
            }
        }
        $filterText = $filterParts ? implode(', ', $filterParts) : 'Semua data';

        $columns = [
            ['label' => 'No', 'width' => 24, 'align' => 'left'],
            ['label' => 'Pelanggan', 'width' => 95, 'align' => 'left'],
            ['label' => 'Kasir', 'width' => 78, 'align' => 'left'],
            ['label' => 'Barber', 'width' => 62, 'align' => 'left'],
            ['label' => 'Total', 'width' => 80, 'align' => 'right'],
            ['label' => 'Bayar', 'width' => 60, 'align' => 'left'],
            ['label' => 'Waktu', 'width' => 116, 'align' => 'left'],
        ];

        $rows = [];
        $totalAll = 0;
        foreach ($transactions as $index => $transaction) {
            $total = (int) ($transaction['total'] ?? 0);
            $totalAll += $total;
            $rows[] = [
                (string) ($index + 1),
                $this->truncateText($transaction['customer'] ?? '-', 20),
                $this->truncateText($transaction['cashier'] ?? '-', 16),
                $this->truncateText($transaction['barber'] ?? '-', 13),
                number_format($total, 0, ',', '.'),
                $this->paymentLabel($transaction['payment_method'] ?? 'cash'),
                $transaction['created_at'] ?? '-',
            ];
        }

        return $this->generateReportPdf('LAPORAN PENJUALAN BARBER', $filterText, $columns, $rows, $totalAll, $totalPointsRedeemed);
    }

    private function truncateText(string $text, int $length): string
    {
        return mb_strlen($text) > $length ? mb_substr($text, 0, $length - 1) . '.' : $text;
    }

    private function paymentLabel(string $method): string
    {
        $labels = ['cash' => 'Tunai', 'debit' => 'Kartu Debit', 'qris' => 'QRIS', 'transfer' => 'Transfer Bank'];

        return $labels[$method] ?? 'Tunai';
    }

    private function escapePdfText(string $text): string
    {
        return str_replace(['\\', '(', ')'], ['\\\\', '\\(', '\\)'], $text);
    }

    private function pdfTextWidth(string $text, float $fontSize): float
    {
        return mb_strlen($text) * $fontSize * 0.5;
    }

    private function pdfText(float $x, float $y, string $text, string $font, float $size, float $gray = 0): string
    {
        $escaped = $this->escapePdfText($text);

        return "BT\n/{$font} {$size} Tf\n{$gray} g\n{$x} {$y} Td\n({$escaped}) Tj\nET\n";
    }

    private function pdfRect(float $x, float $y, float $w, float $h, float $gray): string
    {
        return "{$gray} g\n{$x} {$y} {$w} {$h} re f\n";
    }

    private function pdfLine(float $x1, float $y1, float $x2, float $y2, float $gray, float $width): string
    {
        return "{$gray} G\n{$width} w\n{$x1} {$y1} m {$x2} {$y2} l S\n";
    }

    private function generateReportPdf(string $title, string $filterText, array $columns, array $rows, int $totalAll, int $totalPointsRedeemed = 0): string
    {
        $pageWidth = 595;
        $pageHeight = 842;
        $marginLeft = 40;
        $marginRight = 40;
        $tableWidth = $pageWidth - $marginLeft - $marginRight;
        $rowHeight = 20;
        $headerRowHeight = 22;
        $bottomLimit = 55;
        $titleBlockHeight = 78;
        $continuationHeaderHeight = 40;

        $firstPageRows = max(1, (int) floor((($pageHeight - 40) - $titleBlockHeight - $headerRowHeight - $bottomLimit) / $rowHeight));
        $otherPageRows = max(1, (int) floor((($pageHeight - 40) - $continuationHeaderHeight - $headerRowHeight - $bottomLimit) / $rowHeight));

        $pages = [];
        if (empty($rows)) {
            $pages[] = [];
        } else {
            $remaining = $rows;
            $pages[] = array_splice($remaining, 0, $firstPageRows);
            while (!empty($remaining)) {
                $pages[] = array_splice($remaining, 0, $otherPageRows);
            }
        }

        $pageCount = count($pages);
        $catalogId = 1;
        $pagesId = 2;
        $firstPageId = 3;
        $firstContentId = $firstPageId + $pageCount;
        $regularFontId = $firstContentId + $pageCount;
        $boldFontId = $regularFontId + 1;

        $objects = [];
        $kids = [];
        for ($i = 0; $i < $pageCount; $i++) {
            $kids[] = ($firstPageId + $i) . ' 0 R';
        }
        $objects[$catalogId] = "<< /Type /Catalog /Pages {$pagesId} 0 R >>";
        $objects[$pagesId] = '<< /Type /Pages /Kids [' . implode(' ', $kids) . "] /Count {$pageCount} >>";

        foreach ($pages as $pageIndex => $pageRows) {
            $pageId = $firstPageId + $pageIndex;
            $contentId = $firstContentId + $pageIndex;
            $objects[$pageId] = "<< /Type /Page /Parent {$pagesId} 0 R /MediaBox [0 0 {$pageWidth} {$pageHeight}] /Contents {$contentId} 0 R /Resources << /Font << /F1 {$regularFontId} 0 R /F2 {$boldFontId} 0 R >> >> >>";

            $stream = '';
            $y = $pageHeight - 40;

            if ($pageIndex === 0) {
                $titleWidth = $this->pdfTextWidth($title, 16);
                $stream .= $this->pdfText(($pageWidth - $titleWidth) / 2, $y, $title, 'F2', 16, 0);
                $y -= 22;
                $stream .= $this->pdfText($marginLeft, $y, 'Filter: ' . $filterText, 'F1', 9, 0.35);
                $y -= 14;
                $stream .= $this->pdfText($marginLeft, $y, 'Dicetak: ' . date('d/m/Y H:i'), 'F1', 9, 0.35);
                $y -= 20;
            } else {
                $stream .= $this->pdfText($marginLeft, $y, $title . ' (lanjutan) - Halaman ' . ($pageIndex + 1), 'F2', 11, 0.35);
                $y -= 24;
            }

            $headerBottom = $y - $headerRowHeight;
            $stream .= $this->pdfRect($marginLeft, $headerBottom, $tableWidth, $headerRowHeight, 0.88);
            $colX = $marginLeft;
            foreach ($columns as $col) {
                $textY = $headerBottom + 6;
                if ($col['align'] === 'right') {
                    $w = $this->pdfTextWidth($col['label'], 9);
                    $stream .= $this->pdfText($colX + $col['width'] - 6 - $w, $textY, $col['label'], 'F2', 9, 0);
                } else {
                    $stream .= $this->pdfText($colX + 6, $textY, $col['label'], 'F2', 9, 0);
                }
                $colX += $col['width'];
            }
            $stream .= $this->pdfLine($marginLeft, $headerBottom, $marginLeft + $tableWidth, $headerBottom, 0.6, 1);
            $y = $headerBottom;

            foreach ($pageRows as $rowIndex => $row) {
                $rowBottom = $y - $rowHeight;
                if ($rowIndex % 2 === 1) {
                    $stream .= $this->pdfRect($marginLeft, $rowBottom, $tableWidth, $rowHeight, 0.96);
                }
                $colX = $marginLeft;
                $textY = $rowBottom + 6;
                foreach ($columns as $colIndex => $col) {
                    $cell = (string) ($row[$colIndex] ?? '');
                    if ($col['align'] === 'right') {
                        $w = $this->pdfTextWidth($cell, 9);
                        $stream .= $this->pdfText($colX + $col['width'] - 6 - $w, $textY, $cell, 'F1', 9, 0);
                    } else {
                        $stream .= $this->pdfText($colX + 6, $textY, $cell, 'F1', 9, 0);
                    }
                    $colX += $col['width'];
                }
                $y = $rowBottom;
            }

            $stream .= $this->pdfLine($marginLeft, $y, $marginLeft + $tableWidth, $y, 0.6, 1);

            if ($pageIndex === $pageCount - 1) {
                if (empty($rows)) {
                    $y -= 20;
                    $stream .= $this->pdfText($marginLeft, $y, 'Tidak ada data untuk filter yang dipilih.', 'F1', 10, 0.35);
                } else {
                    $y -= 24;
                    $totalLabel = 'Total Keseluruhan: Rp ' . number_format($totalAll, 0, ',', '.');
                    $w = $this->pdfTextWidth($totalLabel, 11);
                    $stream .= $this->pdfText($marginLeft + $tableWidth - $w, $y, $totalLabel, 'F2', 11, 0);

                    $y -= 18;
                    $pointsLabel = 'Total Poin Ditukar: ' . number_format($totalPointsRedeemed, 0, ',', '.') . ' poin';
                    $pointsWidth = $this->pdfTextWidth($pointsLabel, 10);
                    $stream .= $this->pdfText($marginLeft + $tableWidth - $pointsWidth, $y, $pointsLabel, 'F1', 10, 0.35);
                }
            }

            $objects[$contentId] = '<< /Length ' . strlen($stream) . " >>\nstream\n{$stream}\nendstream";
        }

        $objects[$regularFontId] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>';
        $objects[$boldFontId] = '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>';

        ksort($objects);

        $pdf = "%PDF-1.4\n";
        $offsets = [];
        foreach ($objects as $id => $body) {
            $offsets[$id] = strlen($pdf);
            $pdf .= "{$id} 0 obj\n{$body}\nendobj\n";
        }

        $xrefOffset = strlen($pdf);
        $totalObjects = count($objects) + 1;
        $pdf .= "xref\n0 {$totalObjects}\n0000000000 65535 f \n";
        for ($id = 1; $id <= count($objects); $id++) {
            $pdf .= str_pad((string) $offsets[$id], 10, '0', STR_PAD_LEFT) . " 00000 n \n";
        }
        $pdf .= "trailer\n<< /Size {$totalObjects} /Root {$catalogId} 0 R >>\nstartxref\n{$xrefOffset}\n%%EOF";

        return $pdf;
    }

    private function buildExcelReport(array $transactions, array $filters, int $totalPointsRedeemed = 0): string
    {
        $filterLabels = [];
        foreach ($filters as $key => $value) {
            if ($value !== '') {
                $filterLabels[] = esc($key) . ': ' . esc($value);
            }
        }

        $html = '<html><head><meta charset="UTF-8"></head><body>';
        $html .= '<table border="1">';
        $html .= '<tr><td colspan="6"><b>Laporan Penjualan Barber</b></td></tr>';
        $html .= '<tr><td colspan="6">Filter: ' . ($filterLabels ? implode(', ', $filterLabels) : 'Semua data') . '</td></tr>';
        $html .= '<tr><td colspan="6"></td></tr>';
        $html .= '<tr><th>Pelanggan</th><th>Kasir</th><th>Barber</th><th>Total</th><th>Metode Bayar</th><th>Waktu</th></tr>';

        $totalAll = 0;
        foreach ($transactions as $transaction) {
            $totalAll += (int) ($transaction['total'] ?? 0);
            $html .= '<tr>'
                . '<td>' . esc($transaction['customer'] ?? '-') . '</td>'
                . '<td>' . esc($transaction['cashier'] ?? '-') . '</td>'
                . '<td>' . esc($transaction['barber'] ?? '-') . '</td>'
                . '<td>' . (int) ($transaction['total'] ?? 0) . '</td>'
                . '<td>' . esc($this->paymentLabel($transaction['payment_method'] ?? 'cash')) . '</td>'
                . '<td>' . esc($transaction['created_at'] ?? '-') . '</td>'
                . '</tr>';
        }

        if (empty($transactions)) {
            $html .= '<tr><td colspan="6">Tidak ada data untuk filter yang dipilih.</td></tr>';
        } else {
            $html .= '<tr><td colspan="3"><b>Total</b></td><td><b>' . $totalAll . '</b></td><td colspan="2"></td></tr>';
        }
        $html .= '<tr><td colspan="3"><b>Total Poin Ditukar</b></td><td colspan="3">' . $totalPointsRedeemed . ' poin</td></tr>';

        $html .= '</table></body></html>';

        return $html;
    }

    private function summarizeTransactions(array $transactions): array
    {
        $totalSales = 0;
        $serviceCount = [];
        foreach ($transactions as $transaction) {
            $totalSales += $transaction['total'];
            $items = $transaction['items'] ?? [];
            if (is_string($items)) {
                $items = json_decode($items, true) ?? [];
            }
            if (!is_array($items)) {
                $items = [];
            }
            foreach ($items as $item) {
                $serviceCount[$item['name']] = ($serviceCount[$item['name']] ?? 0) + 1;
            }
        }

        $bestService = '';
        $bestCount = 0;
        foreach ($serviceCount as $name => $count) {
            if ($count > $bestCount) {
                $bestService = $name;
                $bestCount = $count;
            }
        }

        return [
            'totalSales' => $totalSales,
            'transactionCount' => count($transactions),
            'bestService' => $bestService ?: 'Belum ada',
        ];
    }
}
