<?php
/* ProcessPayManual.php - Payroll Processing Wizard (General Staff - Weekly)
   3-Step Wizard: Validate → Preview → Process
   Uses ProcessPayManualAPI.php backend for all calculations */
error_reporting(E_ALL & ~E_NOTICE);
$PageSecurity = 0;
include('includes/session.inc');
$Title = _('HRMS - Process Pay (General Staff)');
include('includes/newheader.php');

// Status guard
if (isset($_GET['status'])) { $_SESSION['status'] = stripslashes($_GET['status']); }
$status = isset($_SESSION['status']) ? $_SESSION['status'] : '';

if ($status == 'locked' || $status == 'pending' || $status == 'processed') {
    echo "<script>alert('Status is not approved, or already processed/locked.');javascript:history.go(-1);</script>";
    exit();
}

if (isset($_GET['id'])) { $_SESSION['id'] = stripslashes($_GET['id']); }
$calendarid = isset($_SESSION['id']) ? intval($_SESSION['id']) : 0;

// Get period info for display
$calInfo = DB_fetch_array(DB_query("SELECT id, period, start_date, end_date FROM calendar_management WHERE id='$calendarid'"));
$periodLabel = $calInfo ? $calInfo['period'] : 'Period #'.$calendarid;
$dateRange = $calInfo ? date('d M Y', strtotime($calInfo['start_date'])).' – '.date('d M Y', strtotime($calInfo['end_date'])) : '';
?>

<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
:root {
    --teal: #2AABB3; --teal-dark: #1E8F96; --teal-deeper: #187278; --teal-light: #E6F7F8;
    --success: #52C41A; --warning: #FAAD14; --danger: #FF4D4F;
    --border: #E5E7EB; --text: #1F2937; --text-light: #6B7280; --bg: #F8FAFB;
    --shadow: 0 2px 8px rgba(0,0,0,0.08); --shadow-lg: 0 8px 24px rgba(0,0,0,0.12);
}
.ppw-wrap { font-family: 'Poppins', sans-serif; color: var(--text); max-width: 1400px; margin: 0 auto; padding: 0 20px; }
.ppw-wrap * { box-sizing: border-box; }

/* Banner */
.ppw-banner {
    background: linear-gradient(135deg, var(--teal-deeper) 0%, var(--teal) 50%, #35C4CC 100%);
    border-radius: 16px; padding: 28px 32px; margin-bottom: 24px; color: #fff; position: relative; overflow: hidden;
}
.ppw-banner::before { content: ''; position: absolute; top: -40px; right: -40px; width: 200px; height: 200px; background: rgba(255,255,255,0.06); border-radius: 50%; }
.ppw-banner h2 { margin: 0 0 4px; font-size: 22px; font-weight: 600; }
.ppw-banner .ppw-sub { opacity: 0.85; font-size: 13px; }
.ppw-banner .ppw-breadcrumb { font-size: 12px; opacity: 0.7; margin-bottom: 8px; }
.ppw-banner .ppw-breadcrumb a { color: #fff; text-decoration: none; }

/* Stepper */
.ppw-stepper { display: flex; gap: 0; margin-bottom: 28px; background: #fff; border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; }
.ppw-step { flex: 1; padding: 18px 20px; display: flex; align-items: center; gap: 12px; border-bottom: 3px solid transparent; transition: all 0.3s; cursor: default; }
.ppw-step.active { background: var(--teal-light); border-bottom-color: var(--teal); }
.ppw-step.done { background: #F0FFF4; border-bottom-color: var(--success); }
.ppw-step-num { width: 32px; height: 32px; border-radius: 50%; background: var(--border); color: var(--text-light); display: flex; align-items: center; justify-content: center; font-weight: 600; font-size: 14px; flex-shrink: 0; }
.ppw-step.active .ppw-step-num { background: var(--teal); color: #fff; }
.ppw-step.done .ppw-step-num { background: var(--success); color: #fff; }
.ppw-step-label { font-size: 14px; font-weight: 500; }
.ppw-step-desc { font-size: 11px; color: var(--text-light); }

/* Cards */
.ppw-card { background: #fff; border-radius: 12px; box-shadow: var(--shadow); margin-bottom: 20px; overflow: hidden; }
.ppw-card-header { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: space-between; }
.ppw-card-header h3 { margin: 0; font-size: 16px; font-weight: 600; }
.ppw-card-body { padding: 20px; }

/* KPI row */
.ppw-kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 24px; }
.ppw-kpi { background: #fff; border-radius: 12px; box-shadow: var(--shadow); padding: 18px 20px; border-left: 4px solid var(--teal); }
.ppw-kpi.danger { border-left-color: var(--danger); }
.ppw-kpi.warning { border-left-color: var(--warning); }
.ppw-kpi.success { border-left-color: var(--success); }
.ppw-kpi-value { font-size: 28px; font-weight: 700; color: var(--text); }
.ppw-kpi-label { font-size: 12px; color: var(--text-light); margin-top: 2px; }

/* Issue lists */
.ppw-issue-list { max-height: 300px; overflow-y: auto; }
.ppw-issue-row { display: flex; align-items: center; gap: 12px; padding: 10px 16px; border-bottom: 1px solid #F3F4F6; font-size: 13px; }
.ppw-issue-row:last-child { border-bottom: none; }
.ppw-issue-row:hover { background: #FAFBFC; }
.ppw-badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 20px; font-size: 11px; font-weight: 600; }
.ppw-badge.red { background: #FEE2E2; color: #DC2626; }
.ppw-badge.yellow { background: #FEF3C7; color: #D97706; }
.ppw-badge.green { background: #D1FAE5; color: #059669; }
.ppw-badge.blue { background: #DBEAFE; color: #2563EB; }
.ppw-issue-emp { width: 100px; font-weight: 500; flex-shrink: 0; }
.ppw-issue-name { width: 160px; flex-shrink: 0; }
.ppw-issue-dept { width: 120px; flex-shrink: 0; color: var(--text-light); }
.ppw-issue-msg { flex: 1; }

/* Buttons */
.ppw-btn { display: inline-flex; align-items: center; gap: 8px; padding: 10px 24px; border-radius: 8px; font-family: 'Poppins', sans-serif; font-size: 14px; font-weight: 500; border: none; cursor: pointer; transition: all 0.2s; }
.ppw-btn-primary { background: var(--teal); color: #fff; }
.ppw-btn-primary:hover { background: var(--teal-dark); }
.ppw-btn-danger { background: var(--danger); color: #fff; }
.ppw-btn-danger:hover { background: #E04040; }
.ppw-btn-outline { background: #fff; color: var(--text); border: 1px solid var(--border); }
.ppw-btn-outline:hover { border-color: var(--teal); color: var(--teal); }
.ppw-btn-success { background: var(--success); color: #fff; }
.ppw-btn-success:hover { background: #47A817; }
.ppw-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.ppw-actions { display: flex; gap: 12px; justify-content: flex-end; margin-top: 24px; padding: 20px 0; border-top: 1px solid var(--border); }

/* Preview table */
.ppw-table-wrap { overflow-x: auto; max-height: 600px; overflow-y: auto; }
.ppw-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.ppw-table th { background: #F8FAFB; padding: 10px 12px; text-align: left; font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-light); border-bottom: 2px solid var(--border); white-space: nowrap; position: sticky; top: 0; z-index: 2; }
.ppw-table td { padding: 10px 12px; border-bottom: 1px solid #F3F4F6; white-space: nowrap; }
.ppw-table tr:hover td { background: #FAFBFC; }
.ppw-table .text-right { text-align: right; }
.ppw-table .total-row td { font-weight: 700; background: var(--teal-light); border-top: 2px solid var(--teal); }
.ppw-table tfoot tr.total-row td { font-weight: 700; background: var(--teal-light); border-top: 2px solid var(--teal); position: sticky; bottom: 0; }
.ppw-table .warn-cell { background: #FEF3C7; }
.ppw-table .danger-cell { background: #FEE2E2; color: #DC2626; font-weight: 600; }

/* Processing modal */
.ppw-modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; }
.ppw-modal { background: #fff; border-radius: 16px; padding: 40px; text-align: center; max-width: 420px; width: 90%; box-shadow: var(--shadow-lg); }
.ppw-spinner { width: 48px; height: 48px; border: 4px solid var(--border); border-top-color: var(--teal); border-radius: 50%; animation: ppwSpin 0.8s linear infinite; margin: 0 auto 20px; }
@keyframes ppwSpin { to { transform: rotate(360deg); } }
.ppw-modal h3 { margin: 0 0 8px; font-size: 18px; }
.ppw-modal p { color: var(--text-light); font-size: 13px; margin: 0; }
.ppw-modal .ppw-timer { font-size: 24px; font-weight: 600; color: var(--teal); margin-top: 12px; }

/* Success result */
.ppw-success-icon { width: 64px; height: 64px; background: #D1FAE5; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin: 0 auto 16px; }
.ppw-success-icon i { font-size: 28px; color: var(--success); }
.ppw-result-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 20px; text-align: left; }
.ppw-result-item { padding: 12px; background: #F8FAFB; border-radius: 8px; }
.ppw-result-item .label { font-size: 11px; color: var(--text-light); }
.ppw-result-item .val { font-size: 18px; font-weight: 600; }

/* Expand toggle */
.ppw-toggle { cursor: pointer; user-select: none; color: var(--teal); font-size: 13px; font-weight: 500; }
.ppw-toggle:hover { text-decoration: underline; }

.ppw-table-controls { display: flex; justify-content: space-between; align-items: center; margin: 16px 0 8px; gap: 12px; flex-wrap: wrap; }
.ppw-search-box { position: relative; flex: 1; max-width: 360px; }
.ppw-search-box i { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #9CA3AF; }
.ppw-search-box input { width: 100%; padding: 9px 12px 9px 36px; border: 1px solid var(--border); border-radius: 8px; font-size: 13px; font-family: 'Poppins',sans-serif; outline: none; transition: border-color .2s; }
.ppw-search-box input:focus { border-color: var(--teal); box-shadow: 0 0 0 3px rgba(42,171,179,.12); }
.ppw-page-size { font-size: 13px; color: #6B7280; }
.ppw-page-size select { padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; font-size: 13px; font-family: 'Poppins',sans-serif; }
.ppw-showing { font-size: 12px; color: #9CA3AF; margin-bottom: 6px; }
.ppw-pagination { display: flex; justify-content: center; align-items: center; gap: 4px; margin-top: 12px; flex-wrap: wrap; }
.ppw-pagination button { min-width: 34px; height: 34px; border: 1px solid var(--border); background: #fff; border-radius: 6px; cursor: pointer; font-size: 13px; font-family: 'Poppins',sans-serif; color: #374151; transition: all .15s; }
.ppw-pagination button:hover:not(:disabled) { border-color: var(--teal); color: var(--teal); }
.ppw-pagination button.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.ppw-pagination button:disabled { opacity: .4; cursor: default; }
.ppw-pagination .ppw-page-info { font-size: 13px; color: #6B7280; margin: 0 8px; }
.ppw-hidden { display: none; }

/* Responsive */
@media (max-width: 768px) {
    .ppw-kpis { grid-template-columns: repeat(2, 1fr); }
    .ppw-stepper { flex-direction: column; }
    .ppw-result-grid { grid-template-columns: 1fr; }
}
</style>

<div class="ppw-wrap">

<!-- Banner -->
<div class="ppw-banner">
    <div class="ppw-breadcrumb"><a href="PayRunsManual.php">Pay Runs</a> &nbsp;/&nbsp; Process Pay</div>
    <h2><i class="fa fa-calculator"></i> &nbsp;Process Pay — General Staff (Weekly)</h2>
    <div class="ppw-sub"><?php echo htmlspecialchars($periodLabel) . ' &nbsp;|&nbsp; ' . $dateRange; ?> &nbsp;|&nbsp; Calendar ID: <?php echo $calendarid; ?></div>
</div>

<!-- Stepper -->
<div class="ppw-stepper">
    <div class="ppw-step active" id="step-ind-1">
        <div class="ppw-step-num">1</div>
        <div><div class="ppw-step-label">Validate</div><div class="ppw-step-desc">Check data quality</div></div>
    </div>
    <div class="ppw-step" id="step-ind-2">
        <div class="ppw-step-num">2</div>
        <div><div class="ppw-step-label">Preview</div><div class="ppw-step-desc">Review calculations</div></div>
    </div>
    <div class="ppw-step" id="step-ind-3">
        <div class="ppw-step-num">3</div>
        <div><div class="ppw-step-label">Process</div><div class="ppw-step-desc">Commit payroll</div></div>
    </div>
</div>

<!-- Step 1: Validation -->
<div id="step-1">
    <div class="ppw-card">
        <div class="ppw-card-header">
            <h3><i class="fa fa-search" style="color:var(--teal)"></i> &nbsp;Data Validation</h3>
            <button class="ppw-btn ppw-btn-outline" onclick="runValidation()" id="btn-revalidate"><i class="fa fa-refresh"></i> Re-check</button>
        </div>
        <div class="ppw-card-body">
            <div id="val-loading" style="text-align:center;padding:40px;">
                <div class="ppw-spinner"></div>
                <p>Validating employee data...</p>
            </div>
            <div id="val-results" class="ppw-hidden">
                <div class="ppw-kpis" id="val-kpis"></div>

                <!-- Blockers -->
                <div class="ppw-card" id="blocker-card" style="border-left:4px solid var(--danger)">
                    <div class="ppw-card-header">
                        <h3 style="color:var(--danger)"><i class="fa fa-exclamation-circle"></i> &nbsp;Blocking Issues <span id="blocker-count" class="ppw-badge red"></span></h3>
                        <button class="ppw-btn ppw-btn-outline" onclick="exportIssues('blockers')" style="font-size:12px"><i class="fa fa-download"></i> Export CSV</button>
                    </div>
                    <div class="ppw-card-body">
                        <p style="font-size:12px;color:var(--text-light);margin:0 0 8px">These employees will be <strong>skipped</strong> if you continue.</p>
                        <div class="ppw-issue-list" id="blocker-list"></div>
                    </div>
                </div>

                <!-- Notices -->
                <div class="ppw-card" id="notice-card" style="border-left:4px solid var(--warning)">
                    <div class="ppw-card-header">
                        <h3 style="color:var(--warning)"><i class="fa fa-info-circle"></i> &nbsp;Notices <span id="notice-count" class="ppw-badge yellow"></span></h3>
                        <button class="ppw-btn ppw-btn-outline" onclick="exportIssues('notices')" style="font-size:12px"><i class="fa fa-download"></i> Export CSV</button>
                    </div>
                    <div class="ppw-card-body">
                        <p style="font-size:12px;color:var(--text-light);margin:0 0 8px">Informational only — these employees will still be processed.</p>
                        <div class="ppw-issue-list" id="notice-list"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <div class="ppw-actions" id="val-actions" style="display:none">
        <a href="PayRunsManual.php" class="ppw-btn ppw-btn-outline"><i class="fa fa-arrow-left"></i> Fix Data & Return</a>
        <button class="ppw-btn ppw-btn-primary" onclick="goToPreview()" id="btn-continue"><i class="fa fa-arrow-right"></i> Continue to Preview</button>
    </div>
</div>

<!-- Step 2: Preview -->
<div id="step-2" class="ppw-hidden">
    <div class="ppw-card">
        <div class="ppw-card-header">
            <h3><i class="fa fa-eye" style="color:var(--teal)"></i> &nbsp;Pay Calculation Preview</h3>
            <div style="display:flex;gap:8px">
                <button class="ppw-btn ppw-btn-outline" onclick="exportPreviewCSV()" style="font-size:12px"><i class="fa fa-file-excel-o"></i> Export CSV</button>
                <span class="ppw-toggle" onclick="toggleDetailCols()"><i class="fa fa-columns"></i> Toggle Details</span>
            </div>
        </div>
        <div class="ppw-card-body">
            <div id="preview-loading" style="text-align:center;padding:40px;">
                <div class="ppw-spinner"></div>
                <p>Calculating pay for all employees...</p>
            </div>
            <div id="preview-results" class="ppw-hidden">
                <div class="ppw-kpis" id="preview-kpis"></div>
                <div class="ppw-table-controls">
                    <div class="ppw-search-box">
                        <i class="fa fa-search"></i>
                        <input type="text" id="prev-search" placeholder="Search by ID, name or department..." oninput="filterPreview()">
                    </div>
                    <div class="ppw-page-size">
                        Show <select id="prev-pagesize" onchange="filterPreview()">
                            <option value="20">20</option><option value="50">50</option>
                            <option value="100">100</option><option value="9999">All</option>
                        </select> per page
                    </div>
                </div>
                <div class="ppw-showing" id="prev-showing"></div>
                <div class="ppw-table-wrap">
                    <table class="ppw-table" id="preview-table">
                        <thead><tr id="preview-thead"></tr></thead>
                        <tbody id="preview-tbody"></tbody>
                        <tfoot><tr id="preview-tfoot"></tr></tfoot>
                    </table>
                </div>
                <div class="ppw-pagination" id="prev-pagination"></div>
            </div>
        </div>
    </div>
    <div class="ppw-actions">
        <button class="ppw-btn ppw-btn-outline" onclick="backToValidate()"><i class="fa fa-arrow-left"></i> Back</button>
        <button class="ppw-btn ppw-btn-success" onclick="confirmProcess()" id="btn-process"><i class="fa fa-check-circle"></i> Confirm & Process</button>
    </div>
</div>

<!-- Step 3: Complete -->
<div id="step-3" class="ppw-hidden">
    <div class="ppw-card">
        <div class="ppw-card-body" style="text-align:center;padding:40px;" id="process-result"></div>
    </div>
    <div class="ppw-actions">
        <a href="PayRunsManual.php" class="ppw-btn ppw-btn-primary"><i class="fa fa-arrow-left"></i> Return to Pay Runs</a>
    </div>
</div>

</div><!-- /ppw-wrap -->

<!-- Processing Modal -->
<div id="process-modal" class="ppw-modal-overlay ppw-hidden">
    <div class="ppw-modal">
        <div class="ppw-spinner"></div>
        <h3>Processing Payroll</h3>
        <p>Calculating tax, FNPF, deductions and writing records...</p>
        <div class="ppw-timer" id="process-timer">0s</div>
    </div>
</div>

<script>
var CALENDAR_ID = <?php echo $calendarid; ?>;
var API_URL = 'ProcessPayManualAPI.php';
var validationData = null;
var previewData = null;
var detailsExpanded = true;

// ===== STEP MANAGEMENT =====
function setStep(n) {
    for (var i = 1; i <= 3; i++) {
        var el = document.getElementById('step-' + i);
        var ind = document.getElementById('step-ind-' + i);
        if (i === n) { el.className = ''; ind.className = 'ppw-step active'; }
        else if (i < n) { el.className = 'ppw-hidden'; ind.className = 'ppw-step done'; }
        else { el.className = 'ppw-hidden'; ind.className = 'ppw-step'; }
    }
    window.scrollTo({top: 0, behavior: 'smooth'});
}

// ===== FORMATTING =====
function fmt(n) { return '$' + parseFloat(n||0).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
function fmtN(n) { return parseFloat(n||0).toFixed(2); }
function fmtInt(n) { return parseInt(n||0).toLocaleString(); }

// ===== VALIDATION =====
function runValidation() {
    document.getElementById('val-loading').className = '';
    document.getElementById('val-results').className = 'ppw-hidden';
    document.getElementById('val-actions').style.display = 'none';

    fetch(API_URL + '?action=validate&id=' + CALENDAR_ID)
    .then(function(r) { return r.json(); })
    .then(function(d) {
        validationData = d;
        document.getElementById('val-loading').className = 'ppw-hidden';
        document.getElementById('val-results').className = '';
        document.getElementById('val-actions').style.display = 'flex';
        renderValidation(d);
    })
    .catch(function(e) {
        document.getElementById('val-loading').innerHTML = '<p style="color:var(--danger)"><i class="fa fa-exclamation-triangle"></i> Validation failed: ' + e.message + '</p>';
    });
}

function renderValidation(d) {
    // KPIs
    document.getElementById('val-kpis').innerHTML =
        kpiCard(fmtInt(d.total), 'Total Employees', '') +
        kpiCard(fmtInt(d.valid_count), 'Valid (Will Process)', 'success') +
        kpiCard(fmtInt(d.blocker_count), 'Blocked (Will Skip)', 'danger') +
        kpiCard(fmtInt(d.notice_count), 'Notices', 'warning');

    // Blockers
    document.getElementById('blocker-count').textContent = d.blocker_count;
    var bHtml = '';
    if (d.blockers.length === 0) bHtml = '<p style="color:var(--success);padding:12px"><i class="fa fa-check-circle"></i> No blocking issues found!</p>';
    else d.blockers.forEach(function(b) {
        bHtml += issueRow(b.employeeid, b.name, b.department, b.issue, 'red');
    });
    document.getElementById('blocker-list').innerHTML = bHtml;
    document.getElementById('blocker-card').style.display = d.blockers.length ? '' : 'none';

    // Notices
    document.getElementById('notice-count').textContent = d.notice_count;
    var nHtml = '';
    if (d.notices.length === 0) nHtml = '<p style="color:var(--success);padding:12px"><i class="fa fa-check-circle"></i> No notices.</p>';
    else d.notices.forEach(function(n) {
        nHtml += issueRow(n.employeeid, n.name, n.department, n.issue, 'yellow');
    });
    document.getElementById('notice-list').innerHTML = nHtml;

    // Button state
    document.getElementById('btn-continue').disabled = (d.valid_count === 0);
    if (d.valid_count === 0) document.getElementById('btn-continue').textContent = 'No valid employees to process';
}

function kpiCard(val, label, cls) {
    return '<div class="ppw-kpi ' + cls + '"><div class="ppw-kpi-value">' + val + '</div><div class="ppw-kpi-label">' + label + '</div></div>';
}

function issueRow(eid, name, dept, msg, color) {
    return '<div class="ppw-issue-row">' +
        '<span class="ppw-issue-emp">' + eid + '</span>' +
        '<span class="ppw-issue-name">' + name + '</span>' +
        '<span class="ppw-issue-dept">' + dept + '</span>' +
        '<span class="ppw-issue-msg"><span class="ppw-badge ' + color + '">' + msg + '</span></span></div>';
}

function exportIssues(type) {
    window.open(API_URL + '?action=export_issues&id=' + CALENDAR_ID + '&type=' + type, '_blank');
}

// ===== PREVIEW =====
function goToPreview() {
    setStep(2);
    document.getElementById('preview-loading').className = '';
    document.getElementById('preview-results').className = 'ppw-hidden';

    fetch(API_URL + '?action=preview&id=' + CALENDAR_ID)
    .then(function(r) { return r.json(); })
    .then(function(d) {
        previewData = d;
        document.getElementById('preview-loading').className = 'ppw-hidden';
        document.getElementById('preview-results').className = '';
        renderPreview(d);
    })
    .catch(function(e) {
        document.getElementById('preview-loading').innerHTML = '<p style="color:var(--danger)"><i class="fa fa-exclamation-triangle"></i> Preview failed: ' + e.message + '</p>';
    });
}

function renderPreview(d) {
    // KPIs
    var t = d.totals;
    document.getElementById('preview-kpis').innerHTML =
        kpiCard(fmtInt(d.count), 'Employees', '') +
        kpiCard(fmt(t.gross), 'Total Gross', 'success') +
        kpiCard(fmt(t.tax + t.fnpf), 'Tax + FNPF', 'warning') +
        kpiCard(fmt(t.net), 'Total Net Pay', '');

    // Build header once
    var cols = getPreviewCols();
    var th = ''; cols.forEach(function(c) { th += '<th class="' + c.c + '">' + c.l + '</th>'; });
    document.getElementById('preview-thead').innerHTML = th;

    // Build totals footer once
    var tf = '';
    cols.forEach(function(c) {
        if (c.k === 'employeeid') { tf += '<td><strong>TOTALS</strong></td>'; return; }
        if (c.k === 'full_name' || c.k === 'dept_name') { tf += '<td></td>'; return; }
        var sum = 0; d.employees.forEach(function(e) { sum += parseFloat(e[c.k] || 0); });
        tf += '<td class="' + c.c + '">' + (c.c.indexOf('text-right') >= 0 ? fmt(sum) : fmtN(sum)) + '</td>';
    });
    document.getElementById('preview-tfoot').innerHTML = tf;
    document.getElementById('preview-tfoot').className = 'total-row';

    // Reset search
    document.getElementById('prev-search').value = '';
    prevPage = 1;
    filterPreview();

    // Errors
    if (d.errors && d.errors.length > 0) {
        var eHtml = '<div class="ppw-card" style="border-left:4px solid var(--danger);margin-top:16px"><div class="ppw-card-header"><h3 style="color:var(--danger)">Calculation Errors (' + d.errors.length + ')</h3></div><div class="ppw-card-body">';
        d.errors.forEach(function(e) { eHtml += '<div class="ppw-issue-row"><span class="ppw-issue-emp">' + e.employeeid + '</span><span class="ppw-badge red">' + e.error + '</span></div>'; });
        eHtml += '</div></div>';
        document.getElementById('preview-results').insertAdjacentHTML('beforeend', eHtml);
    }
}

function getPreviewCols() {
    return [
        {k:'employeeid',l:'ID',c:''},
        {k:'full_name',l:'Name',c:''},
        {k:'dept_name',l:'Dept',c:''},
        {k:'normal_hours',l:'Hrs',c:'text-right'},
        {k:'timehalf_hours',l:'1.5×',c:'text-right detail-col'},
        {k:'double_hours',l:'2×',c:'text-right detail-col'},
        {k:'normal_pay',l:'Normal$',c:'text-right detail-col'},
        {k:'bonus_amount',l:'Bonus$',c:'text-right detail-col'},
        {k:'total_gross',l:'Gross',c:'text-right'},
        {k:'fnpf_employee',l:'FNPF',c:'text-right'},
        {k:'tax_to_withhold',l:'Tax',c:'text-right'},
        {k:'fixed_deductions',l:'Fixed Ded',c:'text-right detail-col'},
        {k:'manual_deductions',l:'Other Ded',c:'text-right detail-col'},
        {k:'net_pay',l:'Net Pay',c:'text-right'}
    ];
}

var prevPage = 1;
var prevFiltered = [];

function filterPreview() {
    if (!previewData || !previewData.employees) return;
    var q = document.getElementById('prev-search').value.toLowerCase().trim();
    prevFiltered = previewData.employees.filter(function(e) {
        if (!q) return true;
        return (e.employeeid + '').toLowerCase().indexOf(q) >= 0 ||
               (e.full_name || '').toLowerCase().indexOf(q) >= 0 ||
               (e.dept_name || '').toLowerCase().indexOf(q) >= 0;
    });
    prevPage = 1;
    renderPreviewPage();
}

function renderPreviewPage() {
    var ps = parseInt(document.getElementById('prev-pagesize').value) || 20;
    var total = prevFiltered.length;
    var pages = Math.ceil(total / ps) || 1;
    if (prevPage > pages) prevPage = pages;
    var start = (prevPage - 1) * ps;
    var end = Math.min(start + ps, total);
    var slice = prevFiltered.slice(start, end);

    document.getElementById('prev-showing').textContent = total === 0 ? 'No matching employees' :
        'Showing ' + (start+1) + '–' + end + ' of ' + total + ' employees' +
        (total < previewData.employees.length ? ' (filtered from ' + previewData.employees.length + ')' : '');

    var cols = getPreviewCols();
    var tb = '';
    slice.forEach(function(e) {
        var rowCls = parseFloat(e.net_pay) < 0 ? ' class="danger-cell"' : '';
        tb += '<tr' + rowCls + '>';
        cols.forEach(function(c) {
            var val = e[c.k]; var cls = c.c;
            if (c.c.indexOf('text-right') >= 0 && c.k !== 'normal_hours' && c.k !== 'timehalf_hours' && c.k !== 'double_hours')
                val = fmt(val);
            else if (c.k === 'normal_hours' || c.k === 'timehalf_hours' || c.k === 'double_hours')
                val = fmtN(val);
            if (c.k === 'net_pay' && parseFloat(e.net_pay) < 0) cls += ' danger-cell';
            if (c.k === 'tax_to_withhold' && parseFloat(e.tax_to_withhold) === 0 && parseFloat(e.total_gross) > 577) cls += ' warn-cell';
            tb += '<td class="' + cls + '">' + val + '</td>';
        });
        tb += '</tr>';
    });
    document.getElementById('preview-tbody').innerHTML = tb;

    // Pagination buttons
    var pg = '';
    pg += '<button ' + (prevPage <= 1 ? 'disabled' : '') + ' onclick="goPage(1)" title="First">&laquo;</button>';
    pg += '<button ' + (prevPage <= 1 ? 'disabled' : '') + ' onclick="goPage(' + (prevPage-1) + ')">&lsaquo;</button>';
    var startP = Math.max(1, prevPage - 2), endP = Math.min(pages, prevPage + 2);
    if (startP > 1) pg += '<span class="ppw-page-info">...</span>';
    for (var i = startP; i <= endP; i++) {
        pg += '<button class="' + (i === prevPage ? 'active' : '') + '" onclick="goPage(' + i + ')">' + i + '</button>';
    }
    if (endP < pages) pg += '<span class="ppw-page-info">...</span>';
    pg += '<button ' + (prevPage >= pages ? 'disabled' : '') + ' onclick="goPage(' + (prevPage+1) + ')">&rsaquo;</button>';
    pg += '<button ' + (prevPage >= pages ? 'disabled' : '') + ' onclick="goPage(' + pages + ')" title="Last">&raquo;</button>';
    pg += '<span class="ppw-page-info">Page ' + prevPage + ' of ' + pages + '</span>';
    document.getElementById('prev-pagination').innerHTML = pg;

    // Re-apply detail column visibility
    if (!detailsExpanded) {
        var els = document.querySelectorAll('.detail-col');
        for (var i = 0; i < els.length; i++) els[i].style.display = 'none';
    }
}

function goPage(n) { prevPage = n; renderPreviewPage(); }

function toggleDetailCols() {
    detailsExpanded = !detailsExpanded;
    var els = document.querySelectorAll('.detail-col');
    for (var i = 0; i < els.length; i++) els[i].style.display = detailsExpanded ? '' : 'none';
}

function exportPreviewCSV() {
    if (!previewData || !previewData.employees) return;
    var headers = ['ID','Name','Dept','Hours','1.5x Hrs','2x Hrs','Normal Pay','Bonus','Gross','FNPF','Tax','Fixed Ded','Other Ded','Net Pay'];
    var keys = ['employeeid','full_name','dept_name','normal_hours','timehalf_hours','double_hours','normal_pay','bonus_amount','total_gross','fnpf_employee','tax_to_withhold','fixed_deductions','manual_deductions','net_pay'];
    var csv = headers.join(',') + '\n';
    previewData.employees.forEach(function(e) {
        var row = keys.map(function(k) {
            var v = (e[k] || '').toString().replace(/"/g,'""');
            return '"' + v + '"';
        });
        csv += row.join(',') + '\n';
    });
    var blob = new Blob([csv], {type:'text/csv'});
    var a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = 'payroll_preview_' + CALENDAR_ID + '.csv';
    a.click();
}

function backToValidate() { setStep(1); }

// ===== PROCESS =====
function confirmProcess() {
    var count = previewData ? previewData.count : 0;
    var net = previewData ? previewData.totals.net : 0;
    if (!confirm('Process payroll for ' + count + ' employees?\nTotal net pay: ' + fmt(net) + '\n\nThis action cannot be undone.')) return;

    document.getElementById('process-modal').className = 'ppw-modal-overlay';
    var start = Date.now();
    var timerEl = document.getElementById('process-timer');
    var timerInt = setInterval(function() {
        var s = Math.floor((Date.now() - start) / 1000);
        timerEl.textContent = s + 's';
    }, 1000);

    fetch(API_URL + '?action=process&id=' + CALENDAR_ID)
    .then(function(r) { return r.json(); })
    .then(function(d) {
        clearInterval(timerInt);
        document.getElementById('process-modal').className = 'ppw-modal-overlay ppw-hidden';
        setStep(3);
        renderResult(d, Math.floor((Date.now() - start) / 1000));
    })
    .catch(function(e) {
        clearInterval(timerInt);
        document.getElementById('process-modal').className = 'ppw-modal-overlay ppw-hidden';
        setStep(3);
        document.getElementById('process-result').innerHTML =
            '<div style="color:var(--danger)"><i class="fa fa-exclamation-triangle" style="font-size:48px"></i>' +
            '<h3>Processing Failed</h3><p>' + e.message + '</p></div>';
    });
}

function renderResult(d, secs) {
    var html = '';
    if (d.success) {
        html += '<div class="ppw-success-icon"><i class="fa fa-check"></i></div>';
        html += '<h3 style="color:var(--success)">Payroll Processed Successfully</h3>';
        html += '<p style="color:var(--text-light)">Completed in ' + secs + ' seconds</p>';
        html += '<div class="ppw-result-grid">';
        html += resultItem('Employees Processed', fmtInt(d.processed));
        html += resultItem('Employees Skipped', fmtInt(d.skipped));
        html += resultItem('Total Gross', fmt(d.total_gross));
        html += resultItem('Total Net Pay', fmt(d.total_net));
        html += '</div>';
        if (d.errors && d.errors.length > 0) {
            html += '<div style="margin-top:20px;text-align:left"><h4 style="color:var(--danger)">Errors (' + d.errors.length + ')</h4>';
            d.errors.forEach(function(e) { html += '<div class="ppw-issue-row"><span class="ppw-issue-emp">' + e.employeeid + '</span><span class="ppw-badge red">' + e.error + '</span></div>'; });
            html += '</div>';
        }
    } else {
        html += '<div style="color:var(--danger)"><i class="fa fa-times-circle" style="font-size:48px"></i>';
        html += '<h3>Processing Failed</h3><p>' + (d.error || 'Unknown error') + '</p></div>';
    }
    document.getElementById('process-result').innerHTML = html;
}

function resultItem(label, val) {
    return '<div class="ppw-result-item"><div class="label">' + label + '</div><div class="val">' + val + '</div></div>';
}

// ===== INIT =====
document.addEventListener('DOMContentLoaded', function() { runValidation(); });
</script>