<?php
require_once 'config.php';
if (!function_exists('requireLogin')) {
    function requireLogin() {
        if (session_status() === PHP_SESSION_NONE) session_start();
        if (empty($_SESSION['user'])) {
            header('Location: login.php');
            exit;
        }
    }
}
requireLogin();

// Bill images double as OCR.Space's direct input (it doesn't accept WebP), so
// image_path itself always stays JPEG/PNG. A sibling .webp file (same
// basename, generated in api.php's upload_bill action) is used for on-screen
// display only when present - existing bills uploaded before this existed
// simply fall back to their original file.
function displayImageSrc(string $imagePath): string {
    $webpPath = preg_replace('/\.[^.]+$/', '.webp', $imagePath);
    return is_file($webpPath) ? $webpPath : $imagePath;
}

$bills = $pdo->query("SELECT * FROM bills ORDER BY id DESC")->fetchAll();

$total_outlay = 0; $winning_count = 0; $total_prize_won = 0; 
$true_monthly_cost = 0; $cat_totals = [];
$bimonthly_cats = ['Electricity', 'Electric bill', 'Water', 'Water Bill', 'Gas', 'Gas Bill', '台灣電力公司', '自來水', '天然氣', '瓦斯'];

$duplicate_count = 0;
$unpaid_total = 0;
foreach($bills as $b) {
    if (!empty($b['is_duplicate'])) { $duplicate_count++; continue; } // excluded from every total below - it's already counted via the original it duplicates
    $amt = floatval($b['total_amount']);
    $total_outlay += $amt;

    if(in_array($b['category'], $bimonthly_cats) || in_array($b['title'], $bimonthly_cats)) {
        $true_monthly_cost += ($amt / 2);
    } else {
        $true_monthly_cost += $amt;
    }

    if ($b['is_lottery_winning']) { $winning_count++; $total_prize_won += $b['lottery_prize_amount']; }
    $cat = $b['category'] ?: 'General';
    if (!isset($cat_totals[$cat])) $cat_totals[$cat] = ['count' => 0, 'sum' => 0];
    $cat_totals[$cat]['count']++; $cat_totals[$cat]['sum'] += $amt;

    if (($b['document_type'] ?: 'Bill') !== 'Receipt' && intval($b['payment_status']) !== 1) {
        $unpaid_total += $amt;
    }
}

// Display ordering: unpaid bills first (soonest deadline first, so the most
// urgent is at the very top), then everything else grouped by month - using
// whichever date is actually meaningful for that bill (invoice/issue date,
// falling back to payment deadline, then the upload date as a last resort).
function bill_effective_date($b) {
    return $b['invoice_date'] ?: ($b['due_date'] ?: substr($b['created_at'], 0, 10));
}

// Distinct calendar days that have at least one bill (dot markers on the
// sidebar calendar) - keyed by the same effective-date used for grouping/
// sorting above, so a day marked on the calendar is guaranteed to actually
// match rows once clicked.
$datesWithBills = [];
foreach ($bills as $b) {
    if (!empty($b['is_duplicate'])) continue;
    $ed = bill_effective_date($b);
    if ($ed) $datesWithBills[$ed] = true;
}

// Expense-analysis aggregates (支出分析 tab: day/month/year totals + the
// insight cards) - reuses the same bill_effective_date() used for grouping/
// sorting above so every view of "when" a bill counts stays consistent.
$dailyTotals = []; $monthlyTotals = []; $yearlyTotals = [];
foreach ($bills as $b) {
    if (!empty($b['is_duplicate'])) continue;
    $ed = bill_effective_date($b);
    if (!$ed) continue;
    $amt = floatval($b['total_amount']);
    $dailyTotals[$ed] = ($dailyTotals[$ed] ?? 0) + $amt;
    $monthlyTotals[substr($ed, 0, 7)] = ($monthlyTotals[substr($ed, 0, 7)] ?? 0) + $amt;
    $yearlyTotals[substr($ed, 0, 4)] = ($yearlyTotals[substr($ed, 0, 4)] ?? 0) + $amt;
}
ksort($dailyTotals); ksort($monthlyTotals); ksort($yearlyTotals);

$todayDt = new DateTime();
$curMonthKey = $todayDt->format('Y-m');
$prevMonthKey = (clone $todayDt)->modify('first day of last month')->format('Y-m');
$curYearKey = $todayDt->format('Y');
$prevYearKey = (string)((int)$curYearKey - 1);
$todayMonthDay = $todayDt->format('m-d');

$curMonthTotal = $monthlyTotals[$curMonthKey] ?? 0;
$prevMonthTotal = $monthlyTotals[$prevMonthKey] ?? 0;
$momChangePct = $prevMonthTotal > 0 ? (($curMonthTotal - $prevMonthTotal) / $prevMonthTotal * 100) : null;

$curYearTotal = $yearlyTotals[$curYearKey] ?? 0;
// Year-over-year compares like-for-like (same Jan-1-to-today window last
// year), not last year's full total - otherwise "this year so far" would
// always look artificially small next to a completed prior year.
$prevYearToDateTotal = 0;
foreach ($dailyTotals as $d => $v) {
    if (substr($d, 0, 4) === $prevYearKey && substr($d, 5) <= $todayMonthDay) $prevYearToDateTotal += $v;
}
$yoyChangePct = $prevYearToDateTotal > 0 ? (($curYearTotal - $prevYearToDateTotal) / $prevYearToDateTotal * 100) : null;

$topCategoryName = null; $topCategorySum = -1;
foreach ($cat_totals as $catName => $d) {
    if ($d['sum'] > $topCategorySum) { $topCategorySum = $d['sum']; $topCategoryName = $catName; }
}

$peakDayDate = null; $peakDayAmount = -1;
foreach ($dailyTotals as $d => $v) {
    if ($v > $peakDayAmount) { $peakDayAmount = $v; $peakDayDate = $d; }
}

$avgMonthlySpend = count($monthlyTotals) > 0 ? array_sum($monthlyTotals) / count($monthlyTotals) : 0;
$nonDupBillCount = count($bills) - $duplicate_count;
$avgPerBill = $nonDupBillCount > 0 ? $total_outlay / $nonDupBillCount : 0;

$unpaidBills = []; $restBills = [];
foreach ($bills as $b) {
    $isReceiptRow = (($b['document_type'] ?: 'Bill') === 'Receipt');
    if (empty($b['is_duplicate']) && !$isReceiptRow && intval($b['payment_status']) !== 1) {
        $unpaidBills[] = $b;
    } else {
        $restBills[] = $b;
    }
}
usort($unpaidBills, function($a, $b) { return strcmp($a['due_date'] ?: '9999-99-99', $b['due_date'] ?: '9999-99-99'); });
usort($restBills, function($a, $b) { return strcmp(bill_effective_date($b), bill_effective_date($a)); });
$orderedBills = array_merge($unpaidBills, $restBills);

// Was a hardcoded HTML table (whatever numbers happened to be typed in when
// the lottery tab was first built) - now pulled live from lottery_periods,
// which sync_lottery.py populates from the real government feed. See the
// fetch_internet_lottery fix in api.php for why that table used to stay
// empty even though this card showed numbers.
$latestLotteryPeriod = $pdo->query("SELECT * FROM lottery_periods ORDER BY period_code DESC LIMIT 1")->fetch();

$currentUser = $_SESSION['user'] ?? ['username' => 'nan', 'role' => 'ROLE_ADMIN'];
$csrfToken = $_SESSION['csrf_token'] ?? bin2hex(random_bytes(16));
?>
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="<?= $csrfToken ?>">
    <title>智能家庭帳單 Dashboard</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.css">
    <style>
        body { background-color: #f4f6f9; font-family: -apple-system, sans-serif; }
        .card { border: none; border-radius: 12px; box-shadow: 0 2px 10px rgba(0,0,0,0.04); }
        .bill-thumb { width: 64px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid #dee2e6; cursor: pointer; }
        .nav-pills .nav-link.active { background-color: #0d6efd; font-weight: 600; }
        .log-box { background-color: #1e1e1e; color: #00ff66; font-family: monospace; font-size: 0.8rem; padding: 12px; border-radius: 8px; max-height: 400px; overflow-y: auto; white-space: pre-wrap; }
        .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
        .calendar-grid .cal-weekday { text-align: center; font-size: 0.72rem; font-weight: 600; color: #6c757d; padding: 2px 0; }
        .calendar-grid .cal-day { position: relative; aspect-ratio: 1 / 1; display: flex; align-items: center; justify-content: center; border: none; background: transparent; border-radius: 6px; font-size: 0.85rem; cursor: pointer; }
        .calendar-grid .cal-day:hover { background-color: #e9ecef; }
        .calendar-grid .cal-day.cal-empty { cursor: default; }
        .calendar-grid .cal-day.cal-empty:hover { background-color: transparent; }
        .calendar-grid .cal-day.cal-today { border: 1px solid #0d6efd; }
        .calendar-grid .cal-day.cal-selected { background-color: #0d6efd; color: #fff; font-weight: 600; }
        .calendar-grid .cal-day .cal-dot { position: absolute; bottom: 3px; left: 50%; transform: translateX(-50%); width: 4px; height: 4px; border-radius: 50%; background-color: #dc3545; }
        .calendar-grid .cal-day.cal-selected .cal-dot { background-color: #fff; }
        #billsTable { min-width: 1000px; }
        #pinnedHeader { position: sticky; top: 0; z-index: 1030; background-color: #f4f6f9; padding-top: 1rem; margin-top: -1rem; padding-bottom: 0.75rem; }
        #calendarSidebar { position: sticky; top: var(--header-height, 140px); }
        @media print {
            body { background: white !important; }
            .no-print, .btn, .nav, .modal, .card-header, .alert, .badge, #daemonStatusBar, .btn-group { display: none !important; }
            .card { box-shadow: none !important; border: none !important; }
            table { border-collapse: collapse; width: 100%; font-size: 11px; }
            th, td { border: 1px solid #ddd !important; padding: 6px !important; }
            img.bill-thumb { width: 45px; height: 45px; }
            @page { size: landscape; margin: 1cm; }
        }
    </style>
</head>
<body>
<div class="container py-4" style="max-width: 1600px;">
    
    <div id="pinnedHeader">
    <div class="d-flex flex-wrap justify-content-between align-items-center mb-3 gap-3 bg-white p-3 rounded-3 shadow-sm no-print">
        <div>
            <h3 class="fw-bold m-0 text-primary"><i class="fa-solid fa-file-invoice-dollar me-2"></i>智能家庭帳單 Dashboard</h3>
            <small class="text-muted">使用者: <strong class="text-dark"><?= htmlspecialchars($currentUser['username']) ?></strong> (<?= htmlspecialchars($currentUser['role']) ?>)</small>
        </div>
        
        <div class="d-flex gap-2 flex-wrap bg-light p-2 rounded border border-light-subtle shadow-sm">
            <button class="btn btn-primary fw-bold" data-bs-toggle="modal" data-bs-target="#batchUploadModal"><i class="fa-solid fa-cloud-arrow-up me-1"></i> 上傳單據</button>

            <div class="dropdown">
                <button class="btn btn-outline-secondary fw-bold dropdown-toggle" data-bs-toggle="dropdown"><i class="fa-solid fa-gear me-1"></i> 系統</button>
                <ul class="dropdown-menu">
                    <li><button class="dropdown-item" onclick="openDocsModal('changelog')"><i class="fa-solid fa-book me-2"></i>系統文件 (Docs)</button></li>
                    <li><button class="dropdown-item" onclick="openSettingsModal()"><i class="fa-solid fa-key me-2"></i>API 金鑰設定</button></li>
                    <li><button class="dropdown-item" onclick="openUserModal()"><i class="fa-solid fa-users-gear me-2"></i>使用者管理</button></li>
                    <?php if(($currentUser['role'] ?? '') === 'ROLE_ADMIN'): ?>
                    <li><hr class="dropdown-divider"></li>
                    <li><h6 class="dropdown-header">PC 端背景處理安裝檔</h6></li>
                    <li><a class="dropdown-item" href="api.php?action=download_installer&file=bat"><i class="fa-solid fa-download me-2"></i>下載 Setup-BillDaemon.bat</a></li>
                    <li><a class="dropdown-item" href="api.php?action=download_installer&file=ps1"><i class="fa-solid fa-download me-2"></i>下載 Setup-BillDaemon.ps1</a></li>
                    <?php endif; ?>
                </ul>
            </div>

            <div class="dropdown">
                <button class="btn btn-outline-dark fw-bold dropdown-toggle" data-bs-toggle="dropdown"><i class="fa-solid fa-robot me-1"></i> OCR 背景服務</button>
                <ul class="dropdown-menu">
                    <li><button class="dropdown-item" onclick="startDaemon()"><i class="fa-solid fa-play me-2"></i>啟動 (NAS)</button></li>
                    <li><button class="dropdown-item" onclick="triggerLocalPcDaemon()"><i class="fa-solid fa-desktop me-2"></i>本機 PC 立即處理</button></li>
                    <li><button class="dropdown-item text-danger" onclick="stopDaemon()"><i class="fa-solid fa-power-off me-2"></i>強制終止 (Kill)</button></li>
                    <li><button class="dropdown-item" onclick="openLogModal()"><i class="fa-solid fa-terminal me-2"></i>查看日誌</button></li>
                </ul>
            </div>

            <div class="dropdown">
                <button class="btn btn-outline-warning fw-bold dropdown-toggle" data-bs-toggle="dropdown"><i class="fa-solid fa-toolbox me-1"></i> 資料工具</button>
                <ul class="dropdown-menu">
                    <li><button class="dropdown-item" onclick="rescanFailed()"><i class="fa-solid fa-rotate-right me-2"></i>重新掃描失敗項目</button></li>
                    <li><button class="dropdown-item" onclick="scanDuplicates()" title="比對所有單據，找出可能被漏掉的重複項目"><i class="fa-solid fa-clone me-2"></i>掃描重複</button></li>
                    <li><button class="dropdown-item" onclick="rescanDates()" title="用已儲存的 OCR 文字重新解析日期，不重新呼叫 OCR.Space"><i class="fa-solid fa-calendar-check me-2"></i>重新解析日期</button></li>
                    <li><button class="dropdown-item" onclick="reformatFields()" title="用已儲存的 OCR 文字重新整理詳情中的欄位對照表，不重新呼叫 OCR.Space"><i class="fa-solid fa-table-list me-2"></i>重新整理欄位</button></li>
                    <?php if(($currentUser['role'] ?? '') === 'ROLE_ADMIN'): ?>
                    <li><hr class="dropdown-divider"></li>
                    <li><button class="dropdown-item text-danger" onclick="purgeDatabase()"><i class="fa-solid fa-trash-can me-2"></i>一鍵清空資料庫</button></li>
                    <?php endif; ?>
                </ul>
            </div>

            <div class="btn-group">
                <button class="btn btn-success fw-bold text-white" onclick="exportToExcel()"><i class="fa-solid fa-file-excel me-1"></i> 匯出 Excel</button>
                <button class="btn btn-outline-secondary fw-bold" onclick="window.print()"><i class="fa-solid fa-print me-1"></i> 列印</button>
                <button class="btn btn-success fw-bold" onclick="fetchInternetLottery()"><i class="fa-solid fa-wifi me-1"></i> 聯網對獎</button>
            </div>

            <button class="btn btn-secondary fw-bold ms-auto" onclick="logout()"><i class="fa-solid fa-right-from-bracket me-1"></i> 登出</button>
        </div>
    </div>

    <ul class="nav nav-pills nav-fill mb-0 no-print bg-white p-2 rounded-3 shadow-sm" id="mainTabs">
        <li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#tab-bills"><i class="fa-solid fa-list me-1"></i> 所有帳單單據 (<?= count($bills) - $duplicate_count ?><?= $duplicate_count > 0 ? " + {$duplicate_count} 重複" : '' ?>)</button></li>
        <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#tab-analysis"><i class="fa-solid fa-chart-line me-1"></i> 支出分析</button></li>
        <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#tab-summary"><i class="fa-solid fa-chart-pie me-1"></i> 類別花費統計</button></li>
        <li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#tab-lottery"><i class="fa-solid fa-trophy me-1"></i> 統一發票對獎中心</button></li>
    </ul>
    </div>

    <div class="card p-2 px-3 mb-4 bg-white d-flex flex-row align-items-center justify-content-between border no-print" id="daemonStatusBar">
        <div class="d-flex align-items-center">
            <div id="daemonStatusIndicator" class="me-3">
                <span class="badge bg-secondary"><i class="fa-solid fa-spinner fa-spin me-1"></i>檢測 OCR 背景狀態中...</span>
            </div>
            <div id="daemonStatusDetails" class="small text-muted">連線中...</div>
        </div>
        <button class="btn btn-xs btn-outline-secondary py-0" onclick="checkDaemonStatus()"><i class="fa-solid fa-rotate-right me-1"></i>重新整理</button>
    </div>

    <div class="row g-3 mb-4 no-print row-cols-2 row-cols-md-5">
        <div class="col"><div class="card p-3 border-start border-4 border-danger"><small class="text-muted fw-bold">總累計花費</small><div class="fs-4 fw-bold text-danger mt-1">$<?= number_format($total_outlay) ?></div></div></div>
        <div class="col"><div class="card p-3 border-start border-4 border-info"><small class="text-muted fw-bold">真實月平均 (雙月攤提)</small><div class="fs-4 fw-bold text-info mt-1">$<?= number_format($true_monthly_cost) ?></div></div></div>
        <div class="col"><div class="card p-3 border-start border-4 border-danger bg-danger bg-opacity-10"><small class="text-muted fw-bold">未繳費總額</small><div class="fs-4 fw-bold text-danger mt-1">$<?= number_format($unpaid_total) ?></div></div></div>
        <div class="col"><div class="card p-3 border-start border-4 border-warning"><small class="text-muted fw-bold">中獎發票筆數</small><div class="fs-4 fw-bold text-warning mt-1"><?= $winning_count ?> 筆</div></div></div>
        <div class="col"><div class="card p-3 border-start border-4 border-success"><small class="text-muted fw-bold">中獎累計總額</small><div class="fs-4 fw-bold text-success mt-1">$<?= number_format($total_prize_won) ?></div></div></div>
    </div>

    <div class="tab-content">
        <div class="tab-pane fade show active" id="tab-bills">
            <div class="row g-3">
                <div class="col-12 col-lg-3 col-xl-2 no-print" id="calendarSidebar">
                    <div class="card p-3">
                        <div class="d-flex justify-content-between align-items-center mb-2">
                            <button type="button" class="btn btn-sm btn-outline-secondary" onclick="calChangeMonth(-1)"><i class="fa-solid fa-chevron-left"></i></button>
                            <span class="fw-bold" id="calLabel"></span>
                            <button type="button" class="btn btn-sm btn-outline-secondary" onclick="calChangeMonth(1)"><i class="fa-solid fa-chevron-right"></i></button>
                        </div>
                        <div class="calendar-grid" id="calGrid"></div>
                        <button type="button" class="btn btn-sm btn-outline-danger w-100 mt-2" id="calClearBtn" onclick="clearDateFilter()" style="display:none;">
                            <i class="fa-solid fa-xmark me-1"></i>清除日期篩選 (<span id="calSelectedLabel"></span>)
                        </button>
                    </div>
                </div>
                <div class="col-12 col-lg-9 col-xl-10">
            <div class="card p-3">
                <div class="row g-2 mb-3 no-print">
                    <div class="col-md-4">
                        <div class="input-group">
                            <span class="input-group-text bg-white"><i class="fa-solid fa-magnifying-glass text-muted"></i></span>
                            <input type="text" id="searchInput" class="form-control" placeholder="搜尋標題、發票號碼、統編、車牌號碼、單據編號、用戶編號..." onkeyup="filterTable()">
                        </div>
                    </div>
                    <div class="col-md-2">
                        <select id="categoryFilter" class="form-select" onchange="filterTable()">
                            <option value="">所有類別 (All Categories)</option>
                            <option value="Electric bill">Electric bill (電費)</option>
                            <option value="Water Bill">Water Bill (水費)</option>
                            <option value="Gas Bill">Gas Bill (瓦斯/天然氣費)</option>
                            <option value="Telecom Bill">Telecom Bill (電信費)</option>
                            <option value="Parking Ticket">Parking Ticket (停車單)</option>
                            <option value="Building Management fees">Building Management fees (社區管理費)</option>
                            <option value="Tax">Tax (稅單)</option>
                            <option value="Receipt">Receipt (購物發票/收據)</option>
                            <option value="General">General (其他)</option>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <select id="typeFilter" class="form-select" onchange="filterTable()">
                            <option value="">所有類型 (All Types)</option>
                            <option value="Bill">Bill (繳費帳單)</option>
                            <option value="Receipt">Receipt (消費發票/收據)</option>
                            <option value="Ticket">Ticket (停車/通行通知單)</option>
                        </select>
                    </div>
                    <div class="col-md-2">
                        <select id="monthFilter" class="form-select" onchange="filterTable()">
                            <option value="">所有月份 (All Months)</option>
                            <?php
                                $monthOptions = [];
                                foreach ($bills as $b) {
                                    if (!empty($b['is_duplicate'])) continue;
                                    $ed = bill_effective_date($b);
                                    if ($ed) $monthOptions[substr($ed, 0, 7)] = true;
                                }
                                krsort($monthOptions);
                                foreach (array_keys($monthOptions) as $m):
                                    $label = date('Y年n月', strtotime($m . '-01'));
                            ?>
                            <option value="<?= htmlspecialchars($m) ?>"><?= htmlspecialchars($label) ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                    <div class="col-md-1">
                        <select id="deadlineFilter" class="form-select" onchange="filterTable()" title="依繳費期限篩選">
                            <option value="">全部期限</option>
                            <option value="overdue">已逾期</option>
                            <option value="7">7天內到期</option>
                            <option value="30">30天內到期</option>
                        </select>
                    </div>
                    <div class="col-md-1">
                        <button class="btn btn-outline-secondary w-100" onclick="resetFilters()" title="重置篩選"><i class="fa-solid fa-rotate-left"></i></button>
                    </div>
                </div>

                <div class="table-responsive">
                    <table class="table table-hover align-middle" id="billsTable">
                        <thead class="table-light small">
                            <tr>
                                <th style="width: 45px;" class="text-center">#</th>
                                <th class="text-center">預覽</th>
                                <th>單據名稱 / 類型</th>
                                <th>發票 / 統編 / 單號</th>
                                <th>用戶/客戶編號</th>
                                <th>金額</th>
                                <th>發票/收據日期</th>
                                <th>繳費期限</th>
                                <th>繳費狀態</th>
                                <th class="text-center no-print">完整資訊</th>
                                <th class="text-center no-print">操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php
                                $seq = 1; $lastGroup = null; $unpaidHeaderShown = false; $unpaidCount = count($unpaidBills);
                                foreach($orderedBills as $b):
                                $details = json_decode($b['details_json'], true) ?? [];
                                $bill_no = $details['單據編號/電號'] ?? ($details['車牌號碼'] ?? '');
                                $docType = $b['document_type'] ?: 'Bill';
                                $isReceipt = ($docType === 'Receipt');
                                $isTicket = ($docType === 'Ticket');
                                $badgeClass = $isReceipt ? 'bg-success' : ($isTicket ? 'bg-info text-dark' : 'bg-primary');
                                $isUnpaidRow = (!$isReceipt && intval($b['payment_status']) !== 1 && empty($b['is_duplicate']));

                                // Suffix Highlighting for Lottery
                                $inv = htmlspecialchars($b['invoice_number'] ?? '');
                                $inv_html = $inv;
                                if ($b['is_lottery_winning'] && !empty($inv)) {
                                    $pInfo = $b['lottery_prize_info'] ?? '';
                                    $hl = 8;
                                    if (mb_strpos($pInfo, '二獎') !== false) $hl = 7;
                                    elseif (mb_strpos($pInfo, '三獎') !== false) $hl = 6;
                                    elseif (mb_strpos($pInfo, '四獎') !== false) $hl = 5;
                                    elseif (mb_strpos($pInfo, '五獎') !== false) $hl = 4;
                                    elseif (mb_strpos($pInfo, '六獎') !== false) $hl = 3;
                                    if (strlen($inv) >= $hl) {
                                        $pre = substr($inv, 0, strlen($inv) - $hl);
                                        $suf = substr($inv, -$hl);
                                        $inv_html = $pre . '<span class="bg-warning text-danger fw-bold rounded px-1 border border-danger">' . $suf . '</span>';
                                    }
                                }

                                // Group headers: an "unpaid" section first (already sorted by
                                // soonest deadline), then the rest grouped by month.
                                if ($isUnpaidRow && !$unpaidHeaderShown): $unpaidHeaderShown = true; ?>
                                <tr class="table-danger"><td colspan="11" class="fw-bold text-danger"><i class="fa-solid fa-triangle-exclamation me-1"></i>未繳費 (<?= $unpaidCount ?> 筆，依繳費期限排序)</td></tr>
                                <?php elseif (!$isUnpaidRow):
                                    $effDate = bill_effective_date($b);
                                    $group = $effDate ? substr($effDate, 0, 7) : '未知日期';
                                    if ($group !== $lastGroup):
                                        $lastGroup = $group;
                                        $groupLabel = ($group === '未知日期') ? $group : date('Y年n月', strtotime($group . '-01'));
                                ?>
                                <tr class="table-light"><td colspan="11" class="fw-bold text-secondary"><?= htmlspecialchars($groupLabel) ?></td></tr>
                                <?php endif; endif; ?>
                            <tr data-category="<?= htmlspecialchars($b['category']) ?>" data-type="<?= htmlspecialchars($docType) ?>" data-date="<?= htmlspecialchars(bill_effective_date($b) ?: '') ?>" data-month="<?= htmlspecialchars(substr(bill_effective_date($b) ?: '', 0, 7)) ?>" data-due-date="<?= htmlspecialchars($b['due_date'] ?: '') ?>" data-payment-status="<?= (int)$b['payment_status'] ?>" class="<?= $isUnpaidRow ? 'table-danger bg-opacity-25' : '' ?>">
                                <td class="text-center fw-bold text-muted"><?= $seq++ ?></td>
                                <td class="text-center"><img src="<?= htmlspecialchars(displayImageSrc($b['image_path'])) ?>" class="bill-thumb" onclick="viewImage('<?= htmlspecialchars(displayImageSrc($b['image_path'])) ?>')"></td>
                                <td>
                                    <span class="badge <?= $badgeClass ?> mb-1"><?= htmlspecialchars($docType) ?></span>
                                    <span class="badge bg-secondary mb-1"><?= htmlspecialchars($b['category']) ?></span>
                                    <?php if($b['ocr_provider'] === 'claude'): ?><span class="badge bg-dark mb-1" title="由 Claude Vision 辨識"><i class="fa-solid fa-wand-magic-sparkles"></i></span>
                                    <?php elseif($b['ocr_provider'] === 'ocrspace'): ?><span class="badge bg-info text-dark mb-1" title="由 OCR.Space 辨識"><i class="fa-solid fa-eye"></i></span>
                                    <?php endif; ?>
                                    <?php if(!empty($b['is_duplicate'])): ?><span class="badge bg-warning text-dark mb-1"><i class="fa-solid fa-triangle-exclamation me-1"></i>疑似重複 (原始 #<?= (int)$b['duplicate_of_id'] ?>)</span> <a href="javascript:void(0)" class="small no-print" onclick="unmarkDuplicate(<?= $b['id'] ?>)" title="若判斷錯誤，點此取消重複標記">取消標記</a><?php endif; ?>
                                    <?php if(!empty($b['qr_payment_url'])): ?><a href="<?= htmlspecialchars($b['qr_payment_url']) ?>" target="_blank" rel="noopener" class="badge bg-primary text-decoration-none mb-1" title="此單據金額需至繳費網站查詢 (QR Code 解析連結)"><i class="fa-solid fa-qrcode me-1"></i>查詢/繳費連結</a><?php endif; ?>
                                    <div class="fw-bold title-col"><?= htmlspecialchars($b['title']) ?></div>
                                </td>
                                <td>
                                    <?php if(!empty($b['invoice_number'])): ?><div class="small text-muted">發票: <code class="text-dark fw-bold inv-col"><?= $inv_html ?></code></div><?php endif; ?>
                                    <?php if(!empty($b['tax_id'])): ?><div class="small text-muted">統編: <code class="text-dark fw-bold tax-col"><?= htmlspecialchars($b['tax_id']) ?></code></div><?php endif; ?>
                                    <?php if(!empty($bill_no) && $bill_no !== '無'): ?><div class="small text-muted">單號: <code class="text-primary fw-bold billno-col"><?= htmlspecialchars($bill_no) ?></code></div>
                                    <?php elseif(empty($b['invoice_number']) && empty($b['tax_id'])): ?><div class="small text-muted">單號: <code class="text-secondary billno-col">#<?= $b['id'] ?></code></div><?php endif; ?>
                                </td>
                                <td class="small text-muted custno-col"><?= !empty($b['customer_number']) ? '<code>'.htmlspecialchars($b['customer_number']).'</code>' : '-' ?></td>
                                <td>
                                    <div class="fw-bold text-danger fs-5">$<?= number_format($b['total_amount']) ?></div>
                                    <?php if(!empty($b['discounted_amount']) && $b['discounted_amount'] < $b['total_amount']): ?><div class="small text-success" title="特定付款方式可享優惠，實際金額以您選擇的付款方式為準">優惠 $<?= number_format($b['discounted_amount']) ?></div><?php endif; ?>
                                </td>
                                <td class="small text-muted"><?= htmlspecialchars($b['invoice_date'] ?: '-') ?></td>
                                <td class="small <?= empty($b['due_date']) ? 'text-muted' : 'text-dark fw-bold' ?>"><?= htmlspecialchars($b['due_date'] ?: '-') ?></td>
                                <td>
                                    <?php if($isReceipt || $b['payment_status'] == 1): ?>
                                        <span class="badge bg-success"><i class="fa-solid fa-check me-1"></i>已結清</span>
                                        <div class="small text-muted mt-1"><?= htmlspecialchars($b['payment_method'] ?: '已繳納') ?></div>
                                    <?php else: ?>
                                        <span class="badge bg-danger"><i class="fa-solid fa-clock me-1"></i>未繳費</span>
                                    <?php endif; ?>
                                </td>
                                <td class="text-center no-print" style="width: 120px;">
                                    <button class="btn btn-sm btn-outline-info fw-bold" type="button" onclick='showDetailsModal(<?= json_encode($b["details_json"]) ?>)'><i class="fa-solid fa-file-lines me-1"></i>詳情</button>
                                </td>
                                <td class="text-center no-print" style="width: 110px;">
                                    <button class="btn btn-sm btn-outline-dark w-100 mb-1" onclick="openEditModal(<?= htmlspecialchars(json_encode($b)) ?>)"><i class="fa-solid fa-pen"></i> 編輯</button>
                                    <?php if($currentUser['role'] === 'ROLE_ADMIN'): ?>
                                    <button class="btn btn-sm btn-outline-danger w-100" onclick="deleteBill(<?= $b['id'] ?>)"><i class="fa-solid fa-trash"></i> 刪除</button>
                                    <?php endif; ?>
                                </td>
                            </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            </div>
                </div>
            </div>
        </div>

        <div class="tab-pane fade" id="tab-analysis">
            <div class="row g-3 mb-3 row-cols-2 row-cols-md-3 row-cols-lg-6">
                <div class="col"><div class="card p-3 border-start border-4 border-primary h-100">
                    <small class="text-muted fw-bold">本月支出</small>
                    <div class="fs-5 fw-bold text-primary mt-1">$<?= number_format($curMonthTotal) ?></div>
                    <?php if ($momChangePct === null): ?>
                        <small class="text-muted">上月無資料可比較</small>
                    <?php else: ?>
                        <small class="<?= $momChangePct > 0 ? 'text-danger' : ($momChangePct < 0 ? 'text-success' : 'text-muted') ?>">
                            <i class="fa-solid fa-arrow-<?= $momChangePct >= 0 ? 'up' : 'down' ?> me-1"></i><?= number_format(abs($momChangePct), 1) ?>% 較上月
                        </small>
                    <?php endif; ?>
                </div></div>
                <div class="col"><div class="card p-3 border-start border-4 border-info h-100">
                    <small class="text-muted fw-bold">今年累計 (年初至今)</small>
                    <div class="fs-5 fw-bold text-info mt-1">$<?= number_format($curYearTotal) ?></div>
                    <?php if ($yoyChangePct === null): ?>
                        <small class="text-muted">去年同期無資料</small>
                    <?php else: ?>
                        <small class="<?= $yoyChangePct > 0 ? 'text-danger' : ($yoyChangePct < 0 ? 'text-success' : 'text-muted') ?>">
                            <i class="fa-solid fa-arrow-<?= $yoyChangePct >= 0 ? 'up' : 'down' ?> me-1"></i><?= number_format(abs($yoyChangePct), 1) ?>% 較去年同期
                        </small>
                    <?php endif; ?>
                </div></div>
                <div class="col"><div class="card p-3 border-start border-4 border-warning h-100">
                    <small class="text-muted fw-bold">花費最高類別</small>
                    <div class="fs-5 fw-bold text-warning mt-1 text-truncate" title="<?= htmlspecialchars($topCategoryName ?? '') ?>"><?= $topCategoryName ? htmlspecialchars($topCategoryName) : '無資料' ?></div>
                    <small class="text-muted"><?= $topCategoryName ? ('$' . number_format($topCategorySum)) : '' ?></small>
                </div></div>
                <div class="col"><div class="card p-3 border-start border-4 border-danger h-100">
                    <small class="text-muted fw-bold">花費最高單日</small>
                    <div class="fs-5 fw-bold text-danger mt-1"><?= $peakDayDate ? htmlspecialchars($peakDayDate) : '無資料' ?></div>
                    <small class="text-muted"><?= $peakDayDate ? ('$' . number_format($peakDayAmount)) : '' ?></small>
                </div></div>
                <div class="col"><div class="card p-3 border-start border-4 border-secondary h-100">
                    <small class="text-muted fw-bold">平均每月支出</small>
                    <div class="fs-5 fw-bold text-secondary mt-1">$<?= number_format($avgMonthlySpend) ?></div>
                    <small class="text-muted">共 <?= count($monthlyTotals) ?> 個月</small>
                </div></div>
                <div class="col"><div class="card p-3 border-start border-4 border-success h-100">
                    <small class="text-muted fw-bold">平均單筆金額</small>
                    <div class="fs-5 fw-bold text-success mt-1">$<?= number_format($avgPerBill) ?></div>
                    <small class="text-muted">共 <?= $nonDupBillCount ?> 筆單據</small>
                </div></div>
            </div>

            <div class="card p-3">
                <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3">
                    <h5 class="fw-bold m-0"><i class="fa-solid fa-chart-line me-2 text-primary"></i>支出趨勢</h5>
                    <div class="d-flex gap-2 align-items-center">
                        <select id="analysisMonthPicker" class="form-select form-select-sm" onchange="renderAnalysisChart()"></select>
                        <div class="btn-group btn-group-sm" role="group" id="analysisRangeToggle">
                            <button type="button" class="btn btn-outline-primary active" onclick="setAnalysisView('day', this)">日</button>
                            <button type="button" class="btn btn-outline-primary" onclick="setAnalysisView('month', this)">月</button>
                            <button type="button" class="btn btn-outline-primary" onclick="setAnalysisView('year', this)">年</button>
                        </div>
                    </div>
                </div>
                <canvas id="analysisChart" height="90"></canvas>
            </div>
        </div>

        <div class="tab-pane fade" id="tab-summary">
            <div class="row g-3">
                <?php foreach($cat_totals as $cat => $data): ?>
                <div class="col-md-4">
                    <div class="card p-3 border-start border-4 border-primary" style="cursor:pointer" onclick="viewCategoryBills('<?= htmlspecialchars($cat, ENT_QUOTES) ?>')" title="點擊查看此類別所有單據">
                        <h5 class="fw-bold mb-1 text-dark"><?= htmlspecialchars($cat) ?></h5>
                        <div class="text-muted small mb-2">共計 <?= $data['count'] ?> 筆單據</div>
                        <h4 class="text-primary fw-bold m-0">$<?= number_format($data['sum']) ?> NTD</h4>
                    </div>
                </div>
                <?php endforeach; ?>
            </div>
        </div>

        <div class="tab-pane fade" id="tab-lottery">
            <div class="row g-3">
                <div class="col-md-5">
                    <div class="card p-3 bg-light mb-3">
                        <h5 class="fw-bold text-primary mb-2"><i class="fa-solid fa-trophy me-2"></i>最新開獎期數與完整獎項號碼</h5>
                        <div class="mb-3">
                            <?php if ($latestLotteryPeriod): ?>
                            <h6 class="fw-bold text-dark border-bottom pb-2"><?= htmlspecialchars($latestLotteryPeriod['period_name']) ?> 統一發票中獎號碼</h6>
                            <table class="table table-sm table-bordered bg-white text-center mb-0 small">
                                <tbody>
                                    <tr><th class="bg-light" style="width:30%;">特別獎 (1000萬)</th><td><code class="fw-bold fs-6 text-danger"><?= htmlspecialchars($latestLotteryPeriod['super_prize']) ?></code></td></tr>
                                    <tr><th class="bg-light">特獎 (200萬)</th><td><code class="fw-bold fs-6 text-primary"><?= htmlspecialchars($latestLotteryPeriod['grand_prize']) ?></code></td></tr>
                                    <tr><th class="bg-light">頭獎 (20萬)</th><td><code class="fw-bold text-dark"><?= htmlspecialchars(str_replace(',', ', ', $latestLotteryPeriod['first_prizes'])) ?></code></td></tr>
                                    <tr><th class="bg-light">二獎 (4萬元)</th><td>同期頭獎末7位數相同者</td></tr>
                                    <tr><th class="bg-light">三獎 (1萬元)</th><td>同期頭獎末6位數相同者</td></tr>
                                    <tr><th class="bg-light">四獎 (4000元)</th><td>同期頭獎末5位數相同者</td></tr>
                                    <tr><th class="bg-light">五獎 (1000元)</th><td>同期頭獎末4位數相同者</td></tr>
                                    <tr><th class="bg-light">六獎 (200元)</th><td>同期頭獎末3位數相同者</td></tr>
                                </tbody>
                            </table>
                            <?php else: ?>
                            <div class="text-muted small"><i class="fa-solid fa-circle-info me-1"></i>尚未同步過任何開獎資料，請點擊右側「聯網對獎」以從財政部電子發票網站抓取最新開獎號碼。</div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>

                <div class="col-md-7">
                    <div class="card p-3">
                        <h5 class="fw-bold mb-3">發票自動對獎清單</h5>
                        <div class="small text-muted mb-2"><i class="fa-solid fa-circle-info me-1"></i>統一發票每兩個月開獎一次，僅該期間內開立的發票才能對獎；發票日期尚未落在已知開獎期別內者會顯示「尚未開獎」。</div>
                        <table class="table table-bordered align-middle">
                            <thead class="table-dark small"><tr><th>預覽</th><th>單據名稱</th><th>發票日期</th><th>發票號碼</th><th>對獎結果</th><th>中獎金額</th></tr></thead>
                            <tbody>
                                <?php foreach($bills as $b): if(empty($b['invoice_number']) || !empty($b['is_duplicate'])) continue;
                                    $pInfo = $b['lottery_prize_info'] ?? '';
                                    if ($b['is_lottery_winning']) {
                                        $resultBadge = '<span class="badge bg-warning text-dark">🏆 ' . htmlspecialchars($pInfo) . '</span>';
                                    } elseif ($pInfo === '尚未開獎') {
                                        $resultBadge = '<span class="badge bg-info text-dark"><i class="fa-solid fa-hourglass-half me-1"></i>尚未開獎</span>';
                                    } elseif ($pInfo === '未中獎') {
                                        $resultBadge = '<span class="badge bg-light text-secondary border">未中獎</span>';
                                    } else {
                                        $resultBadge = '<span class="badge bg-light text-muted border">尚未對獎</span>';
                                    }
                                ?>
                                <tr>
                                    <td class="text-center"><img src="<?= htmlspecialchars(displayImageSrc($b['image_path'])) ?>" class="bill-thumb" onclick="viewImage('<?= htmlspecialchars(displayImageSrc($b['image_path'])) ?>')"></td>
                                    <td class="fw-bold"><?= htmlspecialchars($b['title']) ?></td>
                                    <td class="small text-muted"><?= htmlspecialchars($b['invoice_date'] ?: '未知') ?></td>
                                    <td><code><?= htmlspecialchars($b['invoice_number']) ?></code></td>
                                    <td><?= $resultBadge ?></td>
                                    <td class="fw-bold <?= $b['lottery_prize_amount'] > 0 ? 'text-danger fs-5' : 'text-muted' ?>">$<?= number_format($b['lottery_prize_amount']) ?></td>
                                </tr>
                                <?php endforeach; ?>
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<div class="modal fade" id="detailsModal" tabindex="-1"><div class="modal-dialog modal-dialog-centered modal-lg"><div class="modal-content border-0 shadow-lg"><div class="modal-header bg-info text-white"><h5 class="modal-title fw-bold"><i class="fa-solid fa-file-lines me-2"></i>單據完整解析資訊</h5><button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button></div><div class="modal-body bg-light p-4"><div class="card border-0 shadow-sm"><div class="card-body p-0"><table class="table table-striped table-hover mb-0"><tbody id="detailsModalBody"></tbody></table></div></div></div><div class="modal-footer bg-light border-top-0"><button type="button" class="btn btn-secondary fw-bold" data-bs-dismiss="modal">關閉視窗</button></div></div></div></div>
<div class="modal fade" id="settingsModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><form id="settingsForm"><div class="modal-header"><h5 class="modal-title fw-bold"><i class="fa-solid fa-key text-primary me-2"></i>API 金鑰設定</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="mb-3"><label class="fw-bold small form-label">OCR.Space API Key</label><input type="text" class="form-control" id="settingOcrSpaceKey" placeholder="例如: K84357822088957"></div><div class="mb-3"><label class="fw-bold small form-label">Google Gemini API Key (選填/備用)</label><input type="text" class="form-control" id="settingGeminiKey" placeholder="例如: AIzaSy..."></div>
        <div class="mb-3"><label class="fw-bold small form-label">Anthropic Claude API Key (選填/OCR 備援)</label><input type="text" class="form-control" id="settingAnthropicKey" placeholder="例如: sk-ant-..."><div class="form-text">當 OCR.Space 辨識失敗或金額為 0 時，自動改用 Claude Vision 重新辨識一次。留空則停用此備援。</div></div>
        <div class="mb-3"><label class="fw-bold small form-label">背景處理指定 (OCR Daemon)</label>
            <select class="form-select" id="settingActiveProcessor">
                <option value="">自動 (NAS 與 PC 皆可，先到先贏)</option>
                <option value="nas">僅限 NAS (上傳時自動觸發)</option>
                <option value="pc">僅限 PC (NAS 不自動觸發，需手動在 PC 上執行)</option>
            </select>
            <div class="form-text">避免 NAS 與 PC 同時搶著處理同一張單據、重複消耗 OCR 額度。PC 端不會排程執行（避免拖慢電腦），選「僅限 PC」後，上傳完成再手動觸發即可：雙擊該台 PC 桌面上的「Process Bills Now」捷徑，或直接點上方「OCR 背景服務」→「本機 PC 立即處理」（僅對已安裝 PC 端程式的本機瀏覽器有效）。一般情況建議維持「自動」或「僅限 NAS」即可，上傳後會自動觸發、處理完畢即自動關閉，不佔用 PC 資源。</div>
        </div>
    </div><div class="modal-footer"><button type="submit" class="btn btn-primary fw-bold">儲存變更</button></div></form></div></div></div>
<div class="modal fade" id="logModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title fw-bold">OCR 背景執行日誌 (daemon.log)</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="log-box" id="logContent">載入中...</div></div><div class="modal-footer"><button type="button" class="btn btn-secondary" onclick="fetchLogs()">重新整理日誌</button></div></div></div></div>
<div class="modal fade" id="userModal" tabindex="-1"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><h5 class="modal-title fw-bold">使用者管理</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="card p-3 mb-3 bg-light"><form id="createUserForm" class="row g-2"><div class="col-md-4"><input type="text" class="form-control" id="newUsername" placeholder="帳號" required></div><div class="col-md-4"><input type="password" class="form-control" id="newPassword" placeholder="密碼" required></div><div class="col-md-4 d-flex gap-2"><select class="form-select" id="newRole"><option value="ROLE_USER">一般用戶</option><option value="ROLE_ADMIN">管理員</option></select><button type="submit" class="btn btn-primary">新增</button></div></form></div><table class="table table-bordered text-center"><tbody id="userListTable"></tbody></table></div></div></div></div>
<div class="modal fade" id="editModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><form id="editForm"><div class="modal-header"><h5 class="modal-title fw-bold">編輯帳單資料</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><input type="hidden" id="editBillId"><div class="mb-2"><label class="fw-bold small">名稱</label><input type="text" class="form-control" id="editTitle"></div><div class="row g-2 mb-2"><div class="col-6"><label class="fw-bold small">金額</label><input type="number" class="form-control" id="editAmount"></div><div class="col-6"><label class="fw-bold small">發票號碼</label><input type="text" class="form-control" id="editInvoice"></div></div><div class="mb-2"><label class="fw-bold small">繳費期限</label><input type="date" class="form-control" id="editDueDate"></div><div class="row g-2"><div class="col-6"><label class="fw-bold small">繳費狀態</label><select class="form-select" id="editPaymentStatus"><option value="0">未繳費</option><option value="1">已繳費</option></select></div><div class="col-6"><label class="fw-bold small">付款方式</label><select class="form-select" id="editPaymentMethod"><option value="">--選擇--</option><option value="Credit Card">信用卡</option><option value="Line Pay">Line Pay</option><option value="iPassMoney">iPassMoney</option><option value="Auto-Debit (自動扣繳)">自動扣繳</option><option value="Bank Transfer">銀行轉帳</option><option value="Cash">現金</option></select></div></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary">儲存變更</button></div></form></div></div></div>

<div class="modal fade" id="docsModal" tabindex="-1">
    <div class="modal-dialog modal-lg modal-dialog-centered">
        <div class="modal-content border-0 shadow-lg">
            <div class="modal-header bg-dark text-white">
                <h5 class="modal-title fw-bold"><i class="fa-solid fa-book me-2"></i>系統開發與技術文件</h5>
                <div class="ms-auto d-flex gap-2 me-2">
                    <button class="btn btn-sm btn-outline-light" onclick="loadDocContent('changelog')">Changelog</button>
                    <button class="btn btn-sm btn-outline-light" onclick="loadDocContent('gemini')">Architecture (Gemini)</button>
                </div>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body bg-light p-4">
                <pre id="docsContent" class="p-3 bg-white border rounded font-monospace small" style="white-space: pre-wrap; max-height: 450px; overflow-y: auto; color: #212529;"></pre>
            </div>
        </div>
    </div>
</div>

<div class="modal fade" id="batchUploadModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><form id="batchUploadForm"><div class="modal-header"><h5 class="modal-title fw-bold">批次上傳單據</h5><button type="button" class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><input type="file" class="form-control mb-2" id="batchFiles" accept=".jpg,.jpeg,.png,.webp,.heic" multiple required><div id="uploadProgress" class="small text-muted"></div></div><div class="modal-footer"><button type="submit" class="btn btn-primary" id="uploadSubmitBtn">開始上傳</button></div></form></div></div></div>
<div class="modal fade" id="imageModal" tabindex="-1"><div class="modal-dialog modal-lg modal-dialog-centered"><div class="modal-content"><div class="modal-body text-center p-2"><img id="modalImg" src="" class="img-fluid rounded"></div></div></div></div>
<div class="toast-container position-fixed bottom-0 end-0 p-3 no-print" id="toastContainer" style="z-index: 1080;"></div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.3/viewer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
<script>
const CSRF_TOKEN = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
let lastPendingCount = null;
let wasRunning = false;

function secureFetch(url, options = {}) {
    options.headers = options.headers || {};
    options.headers['X-CSRF-Token'] = CSRF_TOKEN;
    return fetch(url, options);
}

// --- Client-side image compression before upload -------------------------
// Resizes to max 1800px on the longest edge and re-encodes as JPEG under
// ~900KB, entirely in the browser (Canvas + toBlob - reliable on all modern
// browsers, including iOS Safari 13+ and Chrome/Android). Compression is a
// best-effort optimization, never a requirement for the upload to succeed:
// any failure at any step (can't decode - e.g. HEIC outside Safari -, low
// memory on a weak device, a stalled decode) falls back to resolving with
// the original, unmodified file, identical to upload behavior before this
// existed. A hard timeout guarantees this always resolves either way.
//
// Raised from an original 1024px/500KB after OCR accuracy on dense
// small-print bills (electric/gas bills especially - even their LABELS came
// back unreadable, not just the values) turned out to be the likely
// bottleneck: 1024px is fine for a simple large-print receipt, but heavy
// downscaling of an 8-10pt-font full-page bill can blur text past what
// OCR.Space can read, before any of the extraction code even runs. 1800px
// balances that against OCR.Space's upload limits (comfortably under 1MB).
const IMG_COMPRESS_MAX_DIMENSION = 1800;
const IMG_COMPRESS_TARGET_BYTES = 900 * 1024;
const IMG_COMPRESS_TIMEOUT_MS = 15000;
const IMG_COMPRESS_QUALITY_STEPS = [0.9, 0.8, 0.7, 0.6];

function renameToJpg(originalName) {
    const dot = originalName.lastIndexOf('.');
    const base = dot > 0 ? originalName.slice(0, dot) : originalName;
    return base + '.jpg';
}

function compressImageForUpload(file) {
    return new Promise(resolve => {
        if (!file.type || !file.type.startsWith('image/')) {
            resolve(file); // not something a canvas can decode (e.g. a PDF) - nothing to do
            return;
        }

        let settled = false;
        const timeoutId = setTimeout(() => settle(file), IMG_COMPRESS_TIMEOUT_MS);
        function settle(result) {
            if (settled) return;
            settled = true;
            clearTimeout(timeoutId);
            resolve(result);
        }

        let objectUrl;
        try {
            objectUrl = URL.createObjectURL(file);
        } catch (err) {
            settle(file);
            return;
        }

        const img = new Image();

        img.onload = () => {
            const width = img.naturalWidth, height = img.naturalHeight;
            URL.revokeObjectURL(objectUrl);
            try {
                if (!width || !height) { settle(file); return; }

                const scale = Math.min(1, IMG_COMPRESS_MAX_DIMENSION / Math.max(width, height));
                const targetW = Math.max(1, Math.round(width * scale));
                const targetH = Math.max(1, Math.round(height * scale));

                const canvas = document.createElement('canvas');
                canvas.width = targetW;
                canvas.height = targetH;
                const ctx = canvas.getContext('2d');
                if (!ctx) { settle(file); return; }
                ctx.drawImage(img, 0, 0, targetW, targetH);

                let stepIndex = 0;
                let bestBlob = null;
                const jpgName = renameToJpg(file.name);

                const finishWithBest = () => settle(bestBlob ? new File([bestBlob], jpgName, { type: 'image/jpeg' }) : file);

                const tryNextQuality = () => {
                    if (settled) return;
                    if (stepIndex >= IMG_COMPRESS_QUALITY_STEPS.length) { finishWithBest(); return; }
                    const q = IMG_COMPRESS_QUALITY_STEPS[stepIndex++];
                    try {
                        canvas.toBlob(blob => {
                            if (settled) return;
                            if (!blob) { finishWithBest(); return; }
                            if (!bestBlob || blob.size < bestBlob.size) bestBlob = blob;
                            if (blob.size <= IMG_COMPRESS_TARGET_BYTES) {
                                settle(new File([blob], jpgName, { type: 'image/jpeg' }));
                            } else {
                                tryNextQuality();
                            }
                        }, 'image/jpeg', q);
                    } catch (err) {
                        finishWithBest();
                    }
                };
                tryNextQuality();
            } catch (err) {
                settle(file);
            }
        };

        img.onerror = () => {
            URL.revokeObjectURL(objectUrl);
            settle(file); // couldn't decode - upload the original as-is
        };

        img.src = objectUrl;
    });
}

// --- Client-side QR code detection (payment lookup links) ----------------
// Some documents (notably 臺北市 street-parking notices) don't print the
// actual amount due at all - it's only available by scanning the QR code
// and checking the payment portal, since the real fee depends on duration
// calculated by their system. Decoding happens entirely in the browser
// (jsQR, a dependency-free pure-JS decoder - deliberately NOT attempted
// server-side, since this NAS has already proven it can't install even
// Tesseract, and there's no reason to assume a barcode library would fare
// better). Runs on the ORIGINAL image before compression, since JPEG
// artifacts from aggressive compression could interfere with reading the
// QR pattern. Best-effort: no QR found, or jsQR unavailable/fails to load,
// simply means no link gets attached - it never blocks the upload.
function decodeQRPayload(file) {
    return new Promise(resolve => {
        if (typeof jsQR !== 'function' || !file.type || !file.type.startsWith('image/')) {
            resolve(null);
            return;
        }
        let settled = false;
        const timeoutId = setTimeout(() => settle(null), 8000);
        function settle(result) { if (settled) return; settled = true; clearTimeout(timeoutId); resolve(result); }

        let objectUrl;
        try { objectUrl = URL.createObjectURL(file); } catch (err) { settle(null); return; }

        const img = new Image();
        img.onload = () => {
            try {
                const w = img.naturalWidth, h = img.naturalHeight;
                URL.revokeObjectURL(objectUrl);
                if (!w || !h) { settle(null); return; }
                // Cap the decode canvas so a huge phone photo doesn't stall
                // on a weak device - jsQR doesn't need full resolution.
                const scale = Math.min(1, 1600 / Math.max(w, h));
                const cw = Math.max(1, Math.round(w * scale)), ch = Math.max(1, Math.round(h * scale));
                const canvas = document.createElement('canvas');
                canvas.width = cw; canvas.height = ch;
                const ctx = canvas.getContext('2d');
                if (!ctx) { settle(null); return; }
                ctx.drawImage(img, 0, 0, cw, ch);
                const imageData = ctx.getImageData(0, 0, cw, ch);
                const code = jsQR(imageData.data, cw, ch);
                if (code && code.data && /^https?:\/\//i.test(code.data.trim())) {
                    settle(code.data.trim());
                } else {
                    settle(null); // no QR found, or it doesn't decode to a URL (e.g. a 統一發票 QR - dense encoded invoice data, not a link)
                }
            } catch (err) {
                settle(null);
            }
        };
        img.onerror = () => { URL.revokeObjectURL(objectUrl); settle(null); };
        img.src = objectUrl;
    });
}


function exportToExcel() {
    alert('正在產生包含照片的 Excel 報表，請稍候...');
    fetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'export_excel', csrf_token: CSRF_TOKEN}) }).then(r => r.json()).then(d => {
        if(d.status === 'success') window.location.href = d.url;
        else alert('匯出失敗！');
    });
}

function filterTable() {
    const q = document.getElementById('searchInput').value.toLowerCase();
    const cat = document.getElementById('categoryFilter').value;
    const type = document.getElementById('typeFilter').value;
    const month = document.getElementById('monthFilter').value;
    const deadline = document.getElementById('deadlineFilter').value;
    const anyFilterActive = (q !== '' || cat !== '' || type !== '' || month !== '' || deadline !== '' || selectedDate !== null);
    const today = new Date(); today.setHours(0, 0, 0, 0);

    document.querySelectorAll('#billsTable tbody tr').forEach(row => {
        // Group header rows (month/unpaid dividers) carry no data-category -
        // only worth showing when nothing is narrowing the list down.
        if (row.getAttribute('data-category') === null) {
            row.style.display = anyFilterActive ? 'none' : '';
            return;
        }

        const title = row.querySelector('.title-col') ? row.querySelector('.title-col').innerText.toLowerCase() : '';
        const inv = row.querySelector('.inv-col') ? row.querySelector('.inv-col').innerText.toLowerCase() : '';
        const tax = row.querySelector('.tax-col') ? row.querySelector('.tax-col').innerText.toLowerCase() : '';
        const billNo = row.querySelector('.billno-col') ? row.querySelector('.billno-col').innerText.toLowerCase() : '';
        const custNo = row.querySelector('.custno-col') ? row.querySelector('.custno-col').innerText.toLowerCase() : '';
        const rCat = row.getAttribute('data-category') || '';
        const rType = row.getAttribute('data-type') || '';
        const rMonth = row.getAttribute('data-month') || '';
        const rDate = row.getAttribute('data-date') || '';
        const rDueDate = row.getAttribute('data-due-date') || '';
        const rPaid = row.getAttribute('data-payment-status') === '1';

        const matchesQuery = (title.includes(q) || inv.includes(q) || tax.includes(q) || billNo.includes(q) || custNo.includes(q));
        const matchesCat = (cat === '' || rCat === cat);
        const matchesType = (type === '' || rType === type);
        const matchesMonth = (month === '' || rMonth === month);
        const matchesDate = (selectedDate === null || rDate === selectedDate);

        let matchesDeadline = true;
        if (deadline !== '') {
            if (rPaid || !rDueDate) {
                matchesDeadline = false; // paid, or no deadline to compare - doesn't belong under any deadline filter
            } else {
                const diffDays = Math.round((new Date(rDueDate + 'T00:00:00') - today) / 86400000);
                matchesDeadline = (deadline === 'overdue') ? diffDays < 0 : (diffDays >= 0 && diffDays <= parseInt(deadline, 10));
            }
        }

        row.style.display = (matchesQuery && matchesCat && matchesType && matchesMonth && matchesDeadline && matchesDate) ? '' : 'none';
    });
}

function resetFilters() {
    document.getElementById('searchInput').value = '';
    document.getElementById('categoryFilter').value = '';
    document.getElementById('typeFilter').value = '';
    document.getElementById('monthFilter').value = '';
    document.getElementById('deadlineFilter').value = '';
    selectedDate = null;
    renderCalendar();
    filterTable();
}

// --- Sidebar calendar (date picker) --------------------------------------
// Lets a single day be picked to filter the bills table down to whatever was
// dated that day, across every category/type at once (receipts, bills,
// parking tickets, ...) - `data-date` on each row is the same
// bill_effective_date() PHP already uses for sorting/grouping, so "today's
// bills" here always means the same "today" as the rest of the page.
const billDatesWithData = new Set(<?= json_encode(array_keys($datesWithBills)) ?>);
let selectedDate = null;
const todayStr = new Date().toISOString().slice(0, 10);
let calViewYear = parseInt(todayStr.slice(0, 4), 10);
let calViewMonth = parseInt(todayStr.slice(5, 7), 10) - 1; // 0-indexed

function calChangeMonth(delta) {
    calViewMonth += delta;
    if (calViewMonth < 0) { calViewMonth = 11; calViewYear--; }
    else if (calViewMonth > 11) { calViewMonth = 0; calViewYear++; }
    renderCalendar();
}

function pad2(n) { return String(n).padStart(2, '0'); }

function selectDate(dateStr) {
    selectedDate = (selectedDate === dateStr) ? null : dateStr;
    renderCalendar();
    filterTable();
}

function clearDateFilter() {
    selectedDate = null;
    renderCalendar();
    filterTable();
}

function renderCalendar() {
    const label = document.getElementById('calLabel');
    const grid = document.getElementById('calGrid');
    if (!label || !grid) return;

    label.textContent = `${calViewYear} 年 ${calViewMonth + 1} 月`;
    grid.innerHTML = '';

    ['日', '一', '二', '三', '四', '五', '六'].forEach(w => {
        const el = document.createElement('div');
        el.className = 'cal-weekday';
        el.textContent = w;
        grid.appendChild(el);
    });

    const firstWeekday = new Date(calViewYear, calViewMonth, 1).getDay();
    const daysInMonth = new Date(calViewYear, calViewMonth + 1, 0).getDate();

    for (let i = 0; i < firstWeekday; i++) {
        const el = document.createElement('div');
        el.className = 'cal-day cal-empty';
        grid.appendChild(el);
    }

    for (let d = 1; d <= daysInMonth; d++) {
        const dateStr = `${calViewYear}-${pad2(calViewMonth + 1)}-${pad2(d)}`;
        const el = document.createElement('button');
        el.type = 'button';
        el.className = 'cal-day';
        if (dateStr === todayStr) el.classList.add('cal-today');
        if (dateStr === selectedDate) el.classList.add('cal-selected');
        el.textContent = d;
        if (billDatesWithData.has(dateStr)) {
            const dot = document.createElement('span');
            dot.className = 'cal-dot';
            el.appendChild(dot);
        }
        el.onclick = () => selectDate(dateStr);
        grid.appendChild(el);
    }

    const clearBtn = document.getElementById('calClearBtn');
    const selLabel = document.getElementById('calSelectedLabel');
    if (clearBtn) clearBtn.style.display = selectedDate ? '' : 'none';
    if (selLabel) selLabel.textContent = selectedDate || '';
}

renderCalendar();

// Keeps the sticky calendar sidebar's offset in sync with the pinned
// header's actual rendered height (the header wraps to 2-3 lines on
// narrower/medium screens as the toolbar buttons reflow, so a fixed offset
// would either leave a gap or hide the calendar's top under the header).
function syncHeaderHeight() {
    const header = document.getElementById('pinnedHeader');
    if (!header) return;
    document.documentElement.style.setProperty('--header-height', header.offsetHeight + 'px');
}
window.addEventListener('resize', syncHeaderHeight);
window.addEventListener('load', syncHeaderHeight);
syncHeaderHeight();

// --- Expense analysis (支出分析 tab): day/month/year trend chart ----------
const dailyTotals = <?= json_encode($dailyTotals) ?>;
const monthlyTotals = <?= json_encode($monthlyTotals) ?>;
const yearlyTotals = <?= json_encode($yearlyTotals) ?>;

let analysisView = 'day';
let analysisChartInstance = null;

function setAnalysisView(view, btn) {
    analysisView = view;
    document.querySelectorAll('#analysisRangeToggle button').forEach(b => b.classList.remove('active'));
    if (btn) btn.classList.add('active');
    const picker = document.getElementById('analysisMonthPicker');
    if (picker) picker.style.display = (view === 'day') ? '' : 'none';
    renderAnalysisChart();
}

function populateAnalysisMonthPicker() {
    const sel = document.getElementById('analysisMonthPicker');
    if (!sel) return;
    sel.innerHTML = '';
    const months = Object.keys(monthlyTotals).sort().reverse();
    months.forEach(m => {
        const opt = document.createElement('option');
        opt.value = m;
        const [y, mm] = m.split('-');
        opt.textContent = `${y} 年 ${parseInt(mm, 10)} 月`;
        sel.appendChild(opt);
    });
}

function renderAnalysisChart() {
    const canvas = document.getElementById('analysisChart');
    if (!canvas || typeof Chart === 'undefined') return;

    let labels = [], data = [];
    if (analysisView === 'day') {
        const monthKey = document.getElementById('analysisMonthPicker').value;
        if (monthKey) {
            const [y, m] = monthKey.split('-').map(Number);
            const daysInMonth = new Date(y, m, 0).getDate();
            for (let d = 1; d <= daysInMonth; d++) {
                labels.push(String(d));
                data.push(dailyTotals[`${monthKey}-${pad2(d)}`] || 0);
            }
        }
    } else if (analysisView === 'month') {
        const months = Object.keys(monthlyTotals).sort();
        labels = months;
        data = months.map(m => monthlyTotals[m]);
    } else if (analysisView === 'year') {
        const years = Object.keys(yearlyTotals).sort();
        labels = years;
        data = years.map(y => yearlyTotals[y]);
    }

    if (analysisChartInstance) analysisChartInstance.destroy();
    analysisChartInstance = new Chart(canvas, {
        type: 'bar',
        data: { labels, datasets: [{ label: '支出 (NTD)', data, backgroundColor: '#0d6efd', borderRadius: 4 }] },
        options: {
            responsive: true,
            plugins: { legend: { display: false } },
            scales: { y: { beginAtZero: true } }
        }
    });
}

populateAnalysisMonthPicker();
const analysisTabBtn = document.querySelector('#mainTabs button[data-bs-target="#tab-analysis"]');
if (analysisTabBtn) analysisTabBtn.addEventListener('shown.bs.tab', renderAnalysisChart);

function viewCategoryBills(cat) {
    const billsTabBtn = document.querySelector('#mainTabs button[data-bs-target="#tab-bills"]');
    if (billsTabBtn) new bootstrap.Tab(billsTabBtn).show();
    const catFilter = document.getElementById('categoryFilter');
    if (catFilter) catFilter.value = cat; // no-op if `cat` isn't one of the fixed <option> values
    filterTable();
    document.getElementById('billsTable').scrollIntoView({ behavior: 'smooth', block: 'start' });
}

function showDetailsModal(detailsJsonStr) {
    const tbody = document.getElementById('detailsModalBody');
    if (!tbody) return;
    tbody.innerHTML = '';

    const labelMap = {
        'Raw OCR': 'OCR 原始識別內文',
        '版面重建 (依圖片排版)': '版面重建 (依圖片排版)',
        'document_type': '單據類型',
        'category': '帳單類別',
        'title': '單據名稱',
        'total_amount': '金額 (NTD)',
        'invoice_number': '統一發票號碼',
        'tax_id': '營業人統一編號',
        'due_date': '繳費期限',
        'bill_number': '單據編號/電號',
        '單據編號/電號': '單據編號/電號',
        '車牌號碼': '車輛車牌號碼'
    };

    try {
        let raw = typeof detailsJsonStr === 'object' ? JSON.stringify(detailsJsonStr) : detailsJsonStr;
        if (typeof raw === 'string') {
            raw = raw.replace(/[\x00-\x1F\x7F-\x9F]/g, ' ');
        }
        let details = typeof raw === 'string' ? JSON.parse(raw) : raw;
        if (typeof details === 'string') {
            details = JSON.parse(details.replace(/[\x00-\x1F\x7F-\x9F]/g, ' '));
        }

        if (!details || Object.keys(details).length === 0) {
            tbody.innerHTML = '<tr><td class="text-center text-muted p-4">無可用的擴充結構化資訊</td></tr>';
        } else {
            const esc = s => String(s).replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]));

            // Rendered first (before the raw OCR text and everything else) -
            // this is the presentable, identify-each-element view built by
            // extract_labeled_fields() in bill_ai_daemon.py; the raw text
            // below it remains the ground truth for anything it couldn't
            // confidently identify.
            const labeledFields = details['其他識別欄位'];
            if (labeledFields && typeof labeledFields === 'object' && Object.keys(labeledFields).length > 0) {
                let rows = Object.entries(labeledFields).map(([fk, fv]) =>
                    `<tr><th class="w-25 text-end bg-white border-end text-muted small">${esc(fk)}</th><td class="font-monospace">${esc(fv)}</td></tr>`
                ).join('');
                tbody.innerHTML += `<tr><th class="w-25 text-end bg-light border-end text-muted align-middle py-3">格式化欄位對照表</th><td class="align-middle py-2"><table class="table table-sm table-bordered mb-0 bg-light">${rows}</table></td></tr>`;
            }

            for (let [k, v] of Object.entries(details)) {
                if (k === '其他識別欄位') continue;
                if (v === null || v === '' || typeof v === 'object') continue;
                let displayKey = labelMap[k] || k;
                let safeKey = esc(displayKey);
                let safeVal = esc(v);

                if (k === 'Raw OCR' || k === 'OCR 原始識別內文' || k === '版面重建 (依圖片排版)') {
                    tbody.innerHTML += `<tr><th class="w-25 text-end bg-light border-end text-muted align-middle py-3">${safeKey}</th><td class="align-middle py-3"><div class="p-3 bg-dark text-success rounded font-monospace small" style="max-height:220px; overflow-y:auto; white-space:pre-wrap; word-break:break-all;">${safeVal}</div></td></tr>`;
                } else {
                    tbody.innerHTML += `<tr><th class="w-25 text-end bg-light border-end text-muted align-middle py-3">${safeKey}</th><td class="align-middle py-3 text-dark fw-bold font-monospace">${safeVal}</td></tr>`;
                }
            }
        }
    } catch(e) {
        tbody.innerHTML = `<tr><td class="text-danger p-4"><i class="fa-solid fa-triangle-exclamation me-2"></i>資料解析錯誤: ${e.message}</td></tr>`;
    }
    new bootstrap.Modal(document.getElementById('detailsModal')).show();
}

function showDuplicateToast(title) {
    const container = document.getElementById('toastContainer');
    if (!container) return;
    const el = document.createElement('div');
    el.className = 'toast align-items-center text-bg-warning border-0';
    el.setAttribute('role', 'alert');
    el.innerHTML = `<div class="d-flex"><div class="toast-body"><i class="fa-solid fa-triangle-exclamation me-2"></i>偵測到重複單據：<strong>${title.replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]))}</strong> 已跳過 OCR 處理</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
    container.appendChild(el);
    const toast = new bootstrap.Toast(el, { delay: 6000 });
    toast.show();
    el.addEventListener('hidden.bs.toast', () => el.remove());
}

function checkDaemonStatus() {
    secureFetch('api.php?action=check_daemon_status')
        .then(r => r.json())
        .then(data => {
            const indicator = document.getElementById('daemonStatusIndicator');
            const details = document.getElementById('daemonStatusDetails');
            if (!indicator || !details) return;

            const hasNewDuplicateToast = Array.isArray(data.new_duplicates) && data.new_duplicates.length > 0;
            if (hasNewDuplicateToast) {
                data.new_duplicates.forEach(d => showDuplicateToast(d.title || ('單據 #' + d.id)));
            }
            const reload = () => setTimeout(() => location.reload(), hasNewDuplicateToast ? 4000 : 0);

            if (data.is_running) {
                indicator.innerHTML = '<span class="badge bg-success"><i class="fa-solid fa-circle-check me-1"></i>OCR 背景程式運行中 (PID: ' + data.pid + ')</span>';
                details.innerText = '最近辨識: ' + data.last_extraction + ' | 待分析: ' + data.pending_count + ' 筆';
                wasRunning = true;
            } else {
                indicator.innerHTML = '<span class="badge bg-secondary"><i class="fa-solid fa-circle-pause me-1"></i>OCR 背景未運行</span>';
                details.innerText = '當前無排隊分析工作。上傳新單據將自動喚醒處理。';
                if (wasRunning || (lastPendingCount !== null && lastPendingCount > 0 && data.pending_count === 0)) {
                    wasRunning = false;
                    reload();
                    return;
                }
            }

            if (lastPendingCount !== null && data.pending_count < lastPendingCount) {
                reload();
                return;
            }
            lastPendingCount = data.pending_count;
        }).catch(() => {});
}

function openSettingsModal() {
    secureFetch('api.php?action=get_settings')
        .then(r => r.json())
        .then(d => {
            if (d.settings) {
                document.getElementById('settingOcrSpaceKey').value = d.settings.ocrspace_api_key || '';
                document.getElementById('settingGeminiKey').value = d.settings.gemini_api_key || '';
                document.getElementById('settingAnthropicKey').value = d.settings.anthropic_api_key || '';
                document.getElementById('settingActiveProcessor').value = d.settings.active_processor || '';
            }
            new bootstrap.Modal(document.getElementById('settingsModal')).show();
        });
}

function openLogModal() { fetchLogs(); new bootstrap.Modal(document.getElementById('logModal')).show(); }
function fetchLogs() { secureFetch('api.php?action=get_daemon_logs').then(r=>r.json()).then(d => { document.getElementById('logContent').innerText = d.logs || d.message; }); }
function startDaemon() { secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'start_daemon', csrf_token: CSRF_TOKEN}) }).then(r=>r.json()).then(d=>{ alert(d.message); setTimeout(checkDaemonStatus, 1000); }); }
// Only works on a PC that has bill_ai_daemon_pc.py installed AND has registered
// the billdaemon:// link handler (Build-ClientInstaller.ps1 sets this up) - it
// is a purely local OS handoff (browser -> registry -> program), not a network
// call, so it can only ever reach the PC this browser is actually running on.
// On any other device (a phone, a PC without the client installed) the browser
// will show a "no application found" style message and nothing happens - that
// is expected, not an error to fix; use the NAS's 啟動 button instead there.
//
// A billdaemon:// navigation has no success/failure callback at all - the
// browser may itself show its own "Open BillDaemon?" permission prompt before
// launching anything, and if that's missed/declined, NOTHING happens with no
// visible error (confirmed for real: clicking this produced zero trace - no
// process, no network connection - because that prompt was never approved).
// So "is it working" can't be answered by watching the launch itself; it's
// answered by watching the one thing that's actually ground truth regardless
// of which machine ends up processing - the pending-bill count already
// exposed by check_daemon_status (the same endpoint checkDaemonStatus()
// polls every 3s elsewhere on this page).
function pcTriggerStatusEl() {
    let el = document.getElementById('pcTriggerStatus');
    if (!el) {
        el = document.createElement('div');
        el.id = 'pcTriggerStatus';
        el.style.cssText = 'position:fixed;bottom:20px;right:20px;z-index:2000;max-width:380px;';
        document.body.appendChild(el);
    }
    return el;
}
function showPcTriggerStatus(html, cls) {
    pcTriggerStatusEl().innerHTML = `<div class="alert ${cls} shadow fw-bold mb-0">${html}</div>`;
}
function triggerLocalPcDaemon() {
    if (!confirm('將在本機 PC 執行背景處理程式 (處理完畢後自動關閉)。\n若本機尚未安裝 PC 端程式，瀏覽器可能顯示「找不到應用程式」，可忽略。\n是否繼續？')) return;

    showPcTriggerStatus('<i class="fa-solid fa-spinner fa-spin me-2"></i>正在啟動本機 PC 處理程式...', 'alert-info');

    secureFetch('api.php?action=check_daemon_status').then(r => r.json()).then(before => {
        const startPending = before.pending_count;
        window.location.href = 'billdaemon://process';

        if (startPending === 0) {
            showPcTriggerStatus('<i class="fa-solid fa-circle-info me-2"></i>目前沒有待處理的單據。', 'alert-secondary');
            setTimeout(() => pcTriggerStatusEl().remove(), 4000);
            return;
        }

        let elapsed = 0, warned = false;
        const stepMs = 3000, warnAtMs = 20000, timeoutMs = 90000;
        const poll = setInterval(() => {
            elapsed += stepMs;
            secureFetch('api.php?action=check_daemon_status').then(r => r.json()).then(d => {
                if (d.pending_count === 0) {
                    clearInterval(poll);
                    showPcTriggerStatus('<i class="fa-solid fa-circle-check me-2"></i>處理完成！正在重新整理...', 'alert-success');
                    setTimeout(() => location.reload(), 1200);
                    return;
                }
                if (d.pending_count < startPending && !warned) {
                    showPcTriggerStatus('<i class="fa-solid fa-spinner fa-spin me-2"></i>處理中... (剩餘 ' + d.pending_count + ' 筆)', 'alert-info');
                }
                if (elapsed >= warnAtMs && !warned) {
                    warned = true;
                    showPcTriggerStatus('<i class="fa-solid fa-triangle-exclamation me-2"></i>已等待 20 秒尚無進度。若瀏覽器右上角出現「開啟應用程式」提示，請點選「開啟」；否則請改用桌面上的「Process Bills Now」捷徑。(仍持續等待中...)', 'alert-warning');
                }
                if (elapsed >= timeoutMs) {
                    clearInterval(poll);
                    showPcTriggerStatus('<i class="fa-solid fa-circle-xmark me-2"></i>逾時：本機處理程式似乎未啟動。可能原因：瀏覽器封鎖了外部應用程式啟動、本機尚未安裝 PC 端程式、或未設定為背景處理來源。請改用桌面捷徑「Process Bills Now」，或改用上方「啟動 (NAS)」。', 'alert-danger');
                }
            }).catch(() => { /* transient fetch hiccup - keep polling, don't false-alarm */ });
        }, stepMs);
    }).catch(err => {
        showPcTriggerStatus('<i class="fa-solid fa-circle-xmark me-2"></i>無法取得目前處理狀態: ' + err.message, 'alert-danger');
    });
}
function stopDaemon() { secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'stop_daemon', csrf_token: CSRF_TOKEN}) }).then(r=>r.json()).then(d=>{ alert(d.message); setTimeout(checkDaemonStatus, 1000); }); }
function logout() { secureFetch('api.php?action=logout').then(() => window.location.href = 'login.php'); }

function openUserModal() { secureFetch('api.php?action=list_users').then(r=>r.json()).then(d => { const tbody = document.getElementById('userListTable'); tbody.innerHTML = ''; if(d.users) d.users.forEach(u => tbody.innerHTML += '<tr><td>'+u.id+'</td><td>'+u.username+'</td><td>'+u.role+'</td><td><button class="btn btn-sm btn-danger" onclick="deleteUser('+u.id+')">刪除</button></td></tr>'); }); new bootstrap.Modal(document.getElementById('userModal')).show(); }
function deleteUser(id) { if(confirm('確定刪除使用者？')) { const f = new FormData(); f.append('action','delete_user'); f.append('user_id', id); secureFetch('api.php',{method:'POST',body:f}).then(r=>r.json()).then(d=>{alert(d.message); openUserModal();}); } }

function fetchInternetLottery() { secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'fetch_internet_lottery', csrf_token: CSRF_TOKEN}) }).then(r=>r.json()).then(d=>{alert(d.message); location.reload();}); }
function purgeDatabase() { if(confirm('⚠️ 警告：確定清空所有資料庫並刪除全部照片嗎？此操作不可復原！')) secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'purge_database', csrf_token: CSRF_TOKEN}) }).then(r=>r.json()).then(d=>{alert(d.message); location.reload();}); }
function deleteBill(id) { if(confirm('確定刪除此單據？')) secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'delete_bill', bill_id: id, csrf_token: CSRF_TOKEN}) }).then(r=>r.json()).then(d=>location.reload()); }
function viewImage(src) { document.getElementById('modalImg').src = src; new bootstrap.Modal(document.getElementById('imageModal')).show(); }

function openEditModal(b) { 
    document.getElementById('editBillId').value = b.id; 
    document.getElementById('editTitle').value = b.title || ''; 
    document.getElementById('editAmount').value = b.total_amount || 0; 
    document.getElementById('editInvoice').value = b.invoice_number || ''; 
    var d = (b.due_date || '').split('/').join('-');
    document.getElementById('editDueDate').value = d.length === 10 ? d : ''; 
    document.getElementById('editPaymentStatus').value = b.payment_status || 0; 
    document.getElementById('editPaymentMethod').value = b.payment_method || ''; 
    new bootstrap.Modal(document.getElementById('editModal')).show(); 
}

function openDocsModal(docType = 'changelog') {
    loadDocContent(docType);
    new bootstrap.Modal(document.getElementById('docsModal')).show();
}

function loadDocContent(docType) {
    const el = document.getElementById('docsContent');
    el.innerText = '載入文件中...';
    secureFetch('api.php?action=get_docs&doc=' + docType)
        .then(r => r.json())
        .then(data => {
            el.innerText = data.content || data.message || '無法載入文件';
        }).catch(err => { el.innerText = '載入失敗: ' + err.message; });
}

document.addEventListener('DOMContentLoaded', () => {
    const settingsForm = document.getElementById('settingsForm');
    if (settingsForm) {
        settingsForm.addEventListener('submit', e => {
            e.preventDefault();
            const f = new FormData();
            f.append('action', 'save_settings');
            f.append('ocrspace_api_key', document.getElementById('settingOcrSpaceKey').value);
            f.append('gemini_api_key', document.getElementById('settingGeminiKey').value);
            f.append('anthropic_api_key', document.getElementById('settingAnthropicKey').value);
            f.append('active_processor', document.getElementById('settingActiveProcessor').value);
            secureFetch('api.php', { method: 'POST', body: f })
                .then(r => r.json())
                .then(d => {
                    alert(d.message);
                    if (d.status === 'success') {
                        bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide();
                    }
                });
        });
    }

    const createUserForm = document.getElementById('createUserForm');
    if (createUserForm) {
        createUserForm.addEventListener('submit', e => {
            e.preventDefault();
            const f = new FormData();
            f.append('action','create_user');
            f.append('username', document.getElementById('newUsername').value);
            f.append('password', document.getElementById('newPassword').value);
            f.append('role', document.getElementById('newRole').value);
            secureFetch('api.php', {method:'POST', body:f}).then(r=>r.json()).then(d => { alert(d.message); if(d.status==='success') openUserModal(); });
        });
    }

    const editForm = document.getElementById('editForm');
    if (editForm) {
        editForm.addEventListener('submit', e => {
            e.preventDefault();
            const f = new FormData();
            f.append('action', 'update_bill_details');
            f.append('csrf_token', CSRF_TOKEN);
            f.append('bill_id', document.getElementById('editBillId').value);
            f.append('title', document.getElementById('editTitle').value);
            f.append('total_amount', document.getElementById('editAmount').value);
            f.append('invoice_number', document.getElementById('editInvoice').value);
            f.append('due_date', document.getElementById('editDueDate').value);
            f.append('payment_status', document.getElementById('editPaymentStatus').value);
            f.append('payment_method', document.getElementById('editPaymentMethod').value);
            secureFetch('api.php', { method: 'POST', body: f })
                .then(r => r.json())
                .then(d => {
                    if (d.status === 'success') {
                        location.reload();
                    } else {
                        alert('儲存失敗: ' + (d.message || '未知錯誤'));
                    }
                }).catch(err => alert('網絡或系統錯誤: ' + err.message));
        });
    }

    const batchUploadForm = document.getElementById('batchUploadForm');
    if (batchUploadForm) {
        batchUploadForm.addEventListener('submit', async e => {
            e.preventDefault();
            const files = document.getElementById('batchFiles').files;
            const progressEl = document.getElementById('uploadProgress');
            document.getElementById('uploadSubmitBtn').disabled = true;
            for (let i = 0; i < files.length; i++) {
                if (progressEl) progressEl.textContent = `處理中 ${i + 1}/${files.length}：偵測 QR Code...`;
                const qrUrl = await decodeQRPayload(files[i]);
                if (progressEl) progressEl.textContent = `處理中 ${i + 1}/${files.length}：壓縮圖片...`;
                const compressed = await compressImageForUpload(files[i]);
                if (progressEl) progressEl.textContent = `處理中 ${i + 1}/${files.length}：上傳中...`;
                const fd = new FormData();
                fd.append('action', 'upload_bill');
                fd.append('bill_image', compressed);
                if (qrUrl) fd.append('qr_url', qrUrl);
                await secureFetch('api.php', { method: 'POST', body: fd });
            }
            if (progressEl) progressEl.textContent = '';
            location.reload();
        });
    }

    checkDaemonStatus();
    setInterval(checkDaemonStatus, 3000);
});

function rescanFailed() {
    if(!confirm('確定要重新掃描所有 0 元或失敗的單據嗎？\n(Rescan all $0 and Failed bills?)')) return;
    fetch('api.php?action=rescan_failed', {method: 'POST', body: new URLSearchParams({csrf_token: CSRF_TOKEN})})
    .then(r => r.json())
    .then(res => {
        if(res.status === 'success') {
            alert('已發送重新掃描指令！系統將於背景處理。');
            location.reload();
        }
    }).catch(err => alert('Error: ' + err));
}

function scanDuplicates() {
    if(!confirm('比對「所有」已辨識完成的單據，找出可能被漏掉的重複項目（依發票號碼、用戶編號+日期、標題+金額+日期等多重條件比對）。\n找到的項目會被標記為重複並排除於總額計算之外，可隨時取消標記。\n\n確定要開始掃描嗎？')) return;
    secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'scan_duplicates', csrf_token: CSRF_TOKEN}) })
    .then(r => r.json())
    .then(res => {
        alert(res.message || '掃描完成');
        if (res.status === 'success') location.reload();
    }).catch(err => alert('Error: ' + err));
}

function rescanDates() {
    if(!confirm('用「已儲存」的 OCR 文字重新解析每筆單據的發票/收據日期與繳費期限（不會重新呼叫 OCR.Space，不產生額外費用）。\n只會更新日期欄位，其他資料不受影響。\n\n確定要開始嗎？')) return;
    secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'rescan_dates', csrf_token: CSRF_TOKEN}) })
    .then(r => r.json())
    .then(res => {
        alert(res.message || '完成');
        if (res.status === 'success') location.reload();
    }).catch(err => alert('Error: ' + err));
}

function reformatFields() {
    if(!confirm('用「已儲存」的 OCR 文字，為每筆單據重新整理可辨識的欄位對照表（不會重新呼叫 OCR.Space，不產生額外費用）。\n只會更新詳情中的「其他識別欄位」，其他資料不受影響。\n\n確定要開始嗎？')) return;
    secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'reformat_fields', csrf_token: CSRF_TOKEN}) })
    .then(r => r.json())
    .then(res => {
        alert(res.message || '完成');
        if (res.status === 'success') location.reload();
    }).catch(err => alert('Error: ' + err));
}

function unmarkDuplicate(id) {
    if(!confirm('確定要取消這筆單據的「重複」標記嗎？取消後將重新計入總額。')) return;
    secureFetch('api.php', { method: 'POST', body: new URLSearchParams({action: 'unmark_duplicate', bill_id: id, csrf_token: CSRF_TOKEN}) })
    .then(r => r.json())
    .then(res => {
        alert(res.message || '完成');
        if (res.status === 'success') location.reload();
    }).catch(err => alert('Error: ' + err));
}

// Viewer.js attachment
document.addEventListener('DOMContentLoaded', () => {
    setInterval(() => {
        const tableContainer = document.querySelector('.table-responsive') || document.body;
        if (tableContainer && !tableContainer.viewerAttached) {
            new Viewer(tableContainer, {
                filter(image) {
                    return image.src && image.src.includes('uploads/');
                },
                toolbar: {
                    zoomIn: 4, zoomOut: 4, oneToOne: 4, reset: 4,
                    rotateLeft: 4, rotateRight: 4, flipHorizontal: 4, flipVertical: 4
                }
            });
            tableContainer.viewerAttached = true;
        }
    }, 1500);
});
</script>
</body>
</html>
