<?php
/**
 * Deletion Requests — the review hub for charts a coder says must never reach Billing.
 *
 * Flow:  coder requests (mandatory reason, from coding-inventory.php)
 *          → TEAM LEAD approves (or rejects back to the coder with a comment)
 *            → MANAGER permanently deletes.
 *
 * Only a Manager may execute. That rule cannot be expressed with coding.supervise — coding_supervisor and
 * coding_manager BOTH hold it — so it lives on coding.deletion.execute, checked via coding_can_delete_charts()
 * (permission AND manager role), the same helper the re-gated coding-inventory.php delete uses.
 *
 * The chart itself is fenced out of every other queue by its status ('deletion_requested'), not by anything on
 * this page: every coding/validation/deliverables/billing queue enumerates its statuses positively, so an
 * unlisted value drops out of all of them by construction.
 *
 * The 'deleted' tab reads ONLY from coding_deletion_requests, which deliberately has NO foreign key to
 * coding_inventory: the chart row is gone and its coding_chart_events went with it (FK CASCADE), so this table
 * plus its frozen snapshot is the entire surviving record. That is the audit trail.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$canReview = can('coding.deletion.review');
$canExec   = coding_can_delete_charts($uid);
$canReq    = can('coding.deletion.request');

/* ---- WHO SEES WHICH REQUESTS ------------------------------------------------------------------------
 * Manager / SysAdmin: everything. They are the final authority, and an approved request must never strand
 *   because the lead who approved it happens to sit outside the manager's reports_to tree.
 * Team Lead: their own team only (per the agreed policy), fenced on requested_by — NOT on the chart's
 *   coder_id, which is NULL on a never-coded chart and would silently hide exactly the junk this exists for.
 * Anyone else who can request: their own requests only. */
$team     = coding_team_scope($pdo, $uid);   // null = sysadmin
$scopeSql = '';
if ($canExec) {
    $scopeSql = '';
} elseif ($canReview) {
    if ($team !== null) $scopeSql = ' AND dr.requested_by IN (' . implode(',', array_map('intval', $team ?: [0])) . ')';
} else {
    $scopeSql = ' AND dr.requested_by = ' . $uid;
}

$backUrl = '/admin/coding-deletions.php';
$STATES  = ['requested' => 'Awaiting review', 'approved' => 'Approved — awaiting Manager', 'rejected' => 'Rejected', 'deleted' => 'Deleted'];

/* ============================================================ POST: Team Lead bulk APPROVE / REJECT */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($_POST['action'] ?? '', ['approve', 'reject'], true)) {
    csrf_check_or_die();
    if (!$canReview) { flash('error', 'You are not allowed to review deletion requests.'); redirect($backUrl); }
    $act     = (string) $_POST['action'];
    $ids     = array_values(array_filter(array_map('intval', (array) ($_POST['req_ids'] ?? []))));
    $comment = trim((string) ($_POST['review_comment'] ?? ''));
    if (!$ids) { flash('error', 'Tick at least one request first.'); redirect($backUrl . '?state=requested'); }
    // A rejection sends the chart back to a coder who must know WHY; an approval carries the coder's own reason.
    if ($act === 'reject' && $comment === '') {
        flash('error', 'A comment is required when rejecting — the coder gets the chart back and needs to know why.');
        redirect($backUrl . '?state=requested');
    }
    $in = implode(',', array_fill(0, count($ids), '?'));
    // Only ever act on requests still AWAITING review, and only inside this reviewer's scope. Re-resolving the
    // ids here (rather than trusting the POST) is what stops a crafted id approving another team's request.
    $sel = $pdo->prepare("SELECT dr.* FROM coding_deletion_requests dr
        WHERE dr.id IN ($in) AND dr.state='requested'$scopeSql");
    $sel->execute($ids);
    $rows = $sel->fetchAll(PDO::FETCH_ASSOC);
    $n = 0;
    foreach ($rows as $r) {
        $rid = (int) $r['id'];
        $cid = (int) $r['inventory_id'];
        try {
            $pdo->beginTransaction();
            if ($act === 'approve') {
                $pdo->prepare("UPDATE coding_deletion_requests
                        SET state='approved', reviewed_by=?, reviewed_at=NOW(), review_comment=?, updated_at=NOW()
                        WHERE id=?")->execute([$uid, ($comment !== '' ? $comment : null), $rid]);
                // The chart STAYS in 'deletion_requested' — approval only moves it along the review chain; it must
                // remain out of every work queue until the Manager decides.
                audit_log('coding.deletion.approve', 'coding_inventory', $cid, ['state' => 'requested'], ['state' => 'approved']);
                coding_log_event($pdo, $cid, 'deletion_approved', $uid, $comment, 'requested', 'approved');
            } else {
                /* Reject → the chart goes back to the coder, and back to the EXACT queue it came from:
                 * deletion_prev_status remembers hold vs pending, which are different queues to a coder. Fall back
                 * to 'pending' only if that stamp is somehow missing, which is recoverable; guessing 'pending'
                 * unconditionally would quietly un-hold every parked chart. */
                $prev = (string) ($r['prev_status'] ?? '');
                if (!in_array($prev, ['pending', 'in_progress', 'hold'], true)) $prev = 'pending';
                $pdo->prepare("UPDATE coding_deletion_requests
                        SET state='rejected', reviewed_by=?, reviewed_at=NOW(), review_comment=?, updated_at=NOW()
                        WHERE id=?")->execute([$uid, $comment, $rid]);
                $pdo->prepare("UPDATE coding_inventory
                        SET status=?, deletion_prev_status=NULL, updated_at=NOW()
                        WHERE id=? AND status='deletion_requested'")->execute([$prev, $cid]);
                audit_log('coding.deletion.reject', 'coding_inventory', $cid, ['state' => 'requested'], ['state' => 'rejected', 'restored_to' => $prev]);
                coding_log_event($pdo, $cid, 'deletion_rejected', $uid, $comment, 'deletion_requested', $prev);
            }
            $pdo->commit();
            $n++;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
        }
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'warning', $n
        ? ($act === 'approve'
            ? "$n request(s) approved — a Manager deletes next."
            : "$n request(s) rejected — the chart(s) are back in the coder's queue.")
          . ($skip ? " {$skip} skipped (already reviewed, or outside your team)." : '')
        : 'Nothing changed — those requests are already reviewed, or outside your team.');
    redirect($backUrl . '?state=requested');
}

/* ============================================================ POST: MANAGER permanent delete */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'execute') {
    csrf_check_or_die();
    // The one irreversible action in this workflow. coding_can_delete_charts() demands the permission AND the
    // manager role, so a stray per-user grant cannot by itself hand a Team Lead the trigger.
    if (!$canExec) { flash('error', 'Only a Coding Manager can permanently delete a chart.'); redirect($backUrl . '?state=approved'); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['req_ids'] ?? []))));
    if (!$ids) { flash('error', 'Tick at least one approved request first.'); redirect($backUrl . '?state=approved'); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    // APPROVED only — never 'requested'. A Manager must not be able to skip the Team Lead's review by posting an
    // id straight from the awaiting-review tab.
    $sel = $pdo->prepare("SELECT dr.* FROM coding_deletion_requests dr WHERE dr.id IN ($in) AND dr.state='approved'");
    $sel->execute($ids);
    $rows = $sel->fetchAll(PDO::FETCH_ASSOC);
    $n = 0; $failed = 0; $stale = 0; $gone = 0;
    foreach ($rows as $r) {
        $rid = (int) $r['id'];
        $cid = (int) $r['inventory_id'];
        try {
            $pdo->beginTransaction();
            // Re-read the chart INSIDE the transaction. FOR UPDATE locks it, so a concurrent RTO or reject cannot
            // slip between this check and the cascade.
            $ch = $pdo->prepare("SELECT * FROM coding_inventory WHERE id=? FOR UPDATE");
            $ch->execute([$cid]);
            $chart = $ch->fetch(PDO::FETCH_ASSOC);

            if (!$chart) {
                /* The chart is already gone — the Manager fast path took it, or this is a double-submit. Nothing
                 * to destroy; close the request honestly rather than failing the batch. */
                $pdo->prepare("UPDATE coding_deletion_requests
                        SET state='deleted', deleted_by=?, deleted_at=NOW(), updated_at=NOW() WHERE id=?")
                    ->execute([$uid, $rid]);
                $pdo->commit();
                $gone++;
                continue;
            }
            /* STALE-APPROVAL GUARD — the approval authorises destroying a chart IN THE BUCKET, and says nothing
             * about a chart that has since left it. Without this, approving a request and then having the chart
             * rejected back to its coder (or pulled out by an RTO) still left the Manager able to permanently
             * destroy a LIVE, actively-coded chart: the handler fenced on the REQUEST's state and then deleted
             * whatever `WHERE id=?` returned. Fencing on the request row alone is not enough — the chart's own
             * status is the authority on whether it is still condemned. */
            if ((string) $chart['status'] !== 'deletion_requested') {
                $pdo->rollBack();
                $stale++;
                continue;
            }

            /* The chart AS DESTROYED, captured inside the transaction, immediately before the cascade. Separate
             * from the request-time snapshot because they answer different questions, and they can differ.
             * This is the last moment the CPT/ICD/modifier content exists anywhere. */
            $snap = coding_chart_full_snapshot($pdo, $cid);
            $json = json_encode($snap, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR);
            audit_log('coding.deletion.execute', 'coding_inventory', $cid, $snap, null);   // before-snapshot
            /* Write the snapshot BEFORE the cascade runs. Ordering is load-bearing: this is the record of what
             * is about to be destroyed, and it must already be committed to the row by the time it is. */
            $pdo->prepare("UPDATE coding_deletion_requests
                    SET state='deleted', deleted_by=?, deleted_at=NOW(), deleted_snapshot_json=?, updated_at=NOW()
                    WHERE id=?")->execute([$uid, $json, $rid]);
            /* coding_deletion_requests has no FK to coding_inventory precisely so this row survives the delete it
             * records. If it cascaded, the trail would evaporate at the exact moment it became the only evidence. */
            coding_delete_chart_cascade($pdo, $cid);
            $pdo->commit();
            $n++;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            $failed++;
        }
    }
    // Per-chart transactions, so a single failure leaves the rest deleted rather than voiding the batch.
    if ($failed) flash('error', $failed . ' chart(s) could not be deleted and were left untouched.');
    if ($stale)  flash('error', $stale . ' request(s) skipped — those charts are no longer awaiting deletion '
                     . '(sent back to a coder, or returned for rework since approval). They were NOT deleted; '
                     . 'raise a fresh request if they should still go.');
    if ($gone)   flash('warning', $gone . ' chart(s) had already been deleted — those requests are now closed.');
    if ($n) flash('success', "$n chart(s) permanently deleted. The request record and a full snapshot of what was "
                           . "destroyed are kept in the Deleted tab.");
    if (!$n && !$stale && !$gone && !$failed) flash('warning', 'Nothing deleted — those requests are not approved.');
    redirect($backUrl . '?state=approved');
}

/* ============================================================ LIST */
$state = isset($_GET['state'], $STATES[$_GET['state']]) ? $_GET['state'] : 'requested';
remember_filters('coding_deletions_' . $state, ['q']);
$q  = trim((string) ($_GET['q'] ?? ''));
$fw = ''; $fp = [];
if ($q !== '') {
    // Search the snapshot too: once the chart is deleted, the snapshot JSON is the only place its patient/MRN lives.
    $fw = ' AND (u.name LIKE ? OR dr.reason LIKE ? OR dr.chart_snapshot_json LIKE ? OR dr.inventory_id = ?)';
    $fp = ['%' . $q . '%', '%' . $q . '%', '%' . $q . '%', ctype_digit($q) ? (int) $q : 0];
}

$cntStmt = $pdo->prepare("SELECT dr.state, COUNT(*) n FROM coding_deletion_requests dr
    LEFT JOIN users u ON u.id=dr.requested_by
    WHERE 1=1 $fw$scopeSql GROUP BY dr.state");
$cntStmt->execute($fp);
$cnt = [];
foreach ($cntStmt->fetchAll(PDO::FETCH_ASSOC) as $r) $cnt[$r['state']] = (int) $r['n'];

/* LEFT JOIN every display table. The chart is INTENTIONALLY LEFT-JOINed and is NULL for a deleted request —
 * an INNER JOIN would make the Deleted tab permanently empty, destroying the audit trail this page exists to
 * show. It is also the KPI/list INNER-JOIN drift this codebase keeps re-introducing: the counts above are over
 * the base table, so any INNER JOIN here would make a badge exceed the rows it claims to describe. */
$rs = $pdo->prepare("SELECT dr.*, u.name requested_by_name, ru.name reviewed_by_name, du.name deleted_by_name,
        ci.patient_name, ci.patient_mrn, ci.dos, ci.status chart_status, ci.coder_id,
        c.name client_name, f.name facility_name,
        (SELECT COUNT(*) FROM coding_chart_cpts cc WHERE cc.inventory_id=dr.inventory_id) cpt_n
    FROM coding_deletion_requests dr
    LEFT JOIN users u  ON u.id=dr.requested_by
    LEFT JOIN users ru ON ru.id=dr.reviewed_by
    LEFT JOIN users du ON du.id=dr.deleted_by
    LEFT JOIN coding_inventory ci ON ci.id=dr.inventory_id
    LEFT JOIN clients c ON c.id=ci.client_id
    LEFT JOIN coding_facilities f ON f.id=ci.facility_id
    WHERE dr.state=? $fw$scopeSql
    ORDER BY dr.requested_at DESC, dr.id DESC LIMIT 500");
$rs->execute(array_merge([$state], $fp));
$rows = $rs->fetchAll(PDO::FETCH_ASSOC);

$tabUrl = fn($k) => url('/admin/coding-deletions.php?' . http_build_query(array_filter(['state' => $k, 'q' => $q])));

$pageTitle = 'Deletion Requests';
require __DIR__ . '/../includes/header.php';
$meta = ['requested' => ['orange', 'clock'], 'approved' => ['azure', 'checks'], 'rejected' => ['secondary', 'arrow-back-up'], 'deleted' => ['danger', 'trash']];
?>
<link rel="stylesheet" href="<?= e(url('/admin/coding-ui.css')) ?>">
<div class="page-header"><div class="container-xl"><div class="row align-items-center">
    <div class="col"><h2 class="page-title"><i class="ti ti-trash-x me-2 text-danger"></i>Deletion Requests</h2>
        <div class="text-secondary small">Charts a coder says must never reach Billing. A <strong>Team Lead</strong> approves or
            rejects; only a <strong>Manager</strong> can permanently delete. Charts waiting here are already out of every
            coding, validation, deliverables and billing queue.</div>
    </div>
</div></div></div>
<div class="page-body"><div class="container-xl">

    <?php if (!$canReview && !$canExec): ?>
    <div class="alert alert-info py-2 px-3">Showing <strong>your own</strong> deletion requests. A Team Lead reviews them next.</div>
    <?php endif; ?>

    <ul class="nav nav-tabs mb-3">
        <?php foreach ($STATES as $k => $label): ?>
        <li class="nav-item"><a class="nav-link <?= $state===$k?'active':'' ?>" href="<?= e($tabUrl($k)) ?>">
            <i class="ti ti-<?= e($meta[$k][1]) ?> me-1"></i><?= e($label) ?>
            <span class="badge bg-<?= e($meta[$k][0]) ?>-lt ms-1"><?= (int) ($cnt[$k] ?? 0) ?></span></a></li>
        <?php endforeach; ?>
    </ul>

    <div class="card mb-3"><div class="card-body py-2"><form method="GET" class="row g-2 align-items-end">
        <input type="hidden" name="state" value="<?= e($state) ?>">
        <input type="hidden" name="_f" value="1">
        <div class="col-12 col-md-6"><label class="form-label mb-1">Search</label>
            <input type="text" name="q" value="<?= e($q) ?>" class="form-control form-control-sm"
                   placeholder="Chart id, patient, MRN, requester or reason — searches the snapshot too, so deleted charts stay findable"></div>
        <div class="col-auto"><button class="btn btn-sm btn-primary">Apply</button>
            <a href="<?= e(url('/admin/coding-deletions.php?state=' . $state . '&clear=1')) ?>" class="btn btn-sm btn-link">Clear</a></div>
    </form></div></div>

    <form method="POST" action="<?= e(url('/admin/coding-deletions.php?state=' . $state)) ?>">
        <?= csrf_field() ?>
    <div class="card">
        <div class="card-header py-2 d-flex flex-wrap gap-2 align-items-center">
            <h3 class="card-title mb-0"><?= e($STATES[$state]) ?> — <?= count($rows) ?></h3>
            <?php if ($state === 'requested' && $canReview && $rows): ?>
            <div class="ms-auto d-flex flex-wrap gap-2 align-items-center">
                <input type="text" name="review_comment" maxlength="2000" class="form-control form-control-sm w-auto"
                       placeholder="Comment (required to reject)" style="min-width:20rem">
                <button type="submit" name="action" value="approve" class="btn btn-sm btn-azure"
                    onclick="if(!document.querySelector('.reqchk:checked')){alert('Tick at least one request first.');return false;} return confirm('Approve the selected request(s)? A Manager will then be able to permanently delete these charts.');"><i class="ti ti-checks me-1"></i>Approve selected</button>
                <button type="submit" name="action" value="reject" class="btn btn-sm btn-outline-secondary"
                    onclick="if(!document.querySelector('.reqchk:checked')){alert('Tick at least one request first.');return false;} if(!this.form.review_comment.value.trim()){alert('A comment is required when rejecting — the coder gets the chart back and needs to know why.');return false;} return confirm('Reject the selected request(s)? The chart(s) go back to the coder.');"><i class="ti ti-arrow-back-up me-1"></i>Reject selected</button>
            </div>
            <?php elseif ($state === 'approved' && $canExec && $rows): ?>
            <div class="ms-auto">
                <button type="submit" name="action" value="execute" class="btn btn-sm btn-danger"
                    onclick="if(!document.querySelector('.reqchk:checked')){alert('Tick at least one approved request first.');return false;} return confirm('PERMANENTLY delete the selected chart(s)?\n\nThis destroys the chart and all its coding and billing rows. It CANNOT be undone. A snapshot is kept in the Deleted tab.');"><i class="ti ti-trash me-1"></i>Permanently delete selected</button>
            </div>
            <?php elseif ($state === 'approved' && !$canExec && $rows): ?>
            <span class="ms-auto text-secondary small"><i class="ti ti-lock me-1"></i>Only a Coding Manager can delete these.</span>
            <?php endif; ?>
        </div>
        <div class="table-responsive"><table class="table table-vcenter card-table table-sm" style="font-size:.82rem">
            <thead><tr>
                <?php $tick = ($state === 'requested' && $canReview) || ($state === 'approved' && $canExec); ?>
                <?php if ($tick): ?><th class="w-1"><input type="checkbox" class="form-check-input m-0" id="reqall" title="Select all"></th><?php endif; ?>
                <th>Chart</th><th>Patient</th><th>Client / facility</th><th>DOS</th>
                <th>Reason</th><th>Requested by</th><th>Requested</th>
                <?php if ($state !== 'requested'): ?><th>Reviewed by</th><th>Comment</th><?php endif; ?>
                <?php if ($state === 'deleted'): ?><th>Deleted by</th><th>Deleted</th><?php endif; ?>
            </tr></thead>
            <tbody>
            <?php foreach ($rows as $r):
                /* For a deleted chart the live row is gone — fall back to the frozen snapshot. This is the whole
                 * reason the snapshot exists, and why nothing here INNER-JOINs coding_inventory.
                 * Prefer the DESTRUCTION snapshot over the request-time one when present: it is what actually
                 * went. Both are coding_chart_full_snapshot() shaped — {chart:{...}, cpts:[...]} — so read the
                 * nested chart, not the top level. */
                $snapRaw = json_decode((string) ($r['deleted_snapshot_json'] ?? ''), true)
                        ?: (json_decode((string) ($r['chart_snapshot_json'] ?? ''), true) ?: []);
                $snap    = is_array($snapRaw['chart'] ?? null) ? $snapRaw['chart'] : [];
                $snapCpt = is_array($snapRaw['cpts'] ?? null) ? $snapRaw['cpts'] : [];
                $gone    = ($r['chart_status'] === null);
                // ?: not ?? — a JOIN against a deleted chart yields NULL, but an absent snapshot key yields '' too,
                // and both must fall through to the next source.
                $pName = $r['patient_name'] ?: ($snap['patient_name'] ?? '');
                $pMrn  = $r['patient_mrn']  ?: ($snap['patient_mrn']  ?? '');
                $dos   = $r['dos']          ?: ($snap['dos']          ?? null);
                // Names come from the JOIN while the chart lives, and from the snapshot's inlined _client_name /
                // _facility_name once it does not — the raw row stores only ids, which are unreadable after the
                // delete leaves nothing to join to.
                $clientName   = (string) ($r['client_name']   ?: ($snap['_client_name']   ?? ''));
                $facilityName = (string) ($r['facility_name'] ?: ($snap['_facility_name'] ?? ''));
                /* "Coded charts yes, but show the Manager the history" — surface it on the row that authorises
                 * destruction, so nobody destroys coded work without being told it was coded. cpt_n is a LIVE
                 * subquery and reads 0 once the chart is deleted, so fall back to the snapshot's own line count:
                 * the Deleted tab must still show what was destroyed. */
                $cptN  = (int) ($r['cpt_n'] ?? 0) ?: count($snapCpt);
                $coded = $cptN > 0 || !empty($r['coder_id']) || !empty($snap['coder_id']) || !empty($snap['rto_at']);
            ?>
                <tr>
                    <?php if ($tick): ?><td><input type="checkbox" class="form-check-input m-0 reqchk" name="req_ids[]" value="<?= (int)$r['id'] ?>"></td><?php endif; ?>
                    <td><span class="text-secondary">#<?= (int) $r['inventory_id'] ?></span>
                        <?php if ($gone): ?><br><span class="badge bg-danger-lt">chart deleted</span><?php endif; ?></td>
                    <td><?= e($pName ?: '—') ?><?php if ($pMrn): ?><div class="text-secondary" style="font-size:.72rem"><?= e($pMrn) ?></div><?php endif; ?>
                        <?php if ($coded): ?><div><span class="badge bg-orange-lt" title="This chart has coded work — CPT lines, a coder, or a prior send-back. Deleting it destroys that work. The full codes are kept in the deletion snapshot.">
                            <i class="ti ti-alert-triangle me-1"></i>coded<?= $cptN ? ' · ' . $cptN . ' CPT' : '' ?></span></div><?php endif; ?></td>
                    <?php /* Live chart → the JOINed name; deleted chart → the name inlined in the snapshot.
                             (The previous one-liner mixed ?? with a ternary: ?? binds tighter, so it collapsed to
                             a truthiness test and rendered an em dash for every deleted row — the exact
                             precedence trap this codebase keeps re-learning. Two plain statements instead.) */ ?>
                    <td><?= e($clientName ?: '—') ?>
                        <?php if ($facilityName !== ''): ?><div class="text-secondary" style="font-size:.72rem"><?= e($facilityName) ?></div><?php endif; ?></td>
                    <td><?= e(fmt_date($dos)) ?></td>
                    <td style="max-width:22rem"><span title="<?= e((string) $r['reason']) ?>"><?= e(mb_strimwidth((string) $r['reason'], 0, 90, '…')) ?></span></td>
                    <td><?= e($r['requested_by_name'] ?? '—') ?></td>
                    <td><?= e(fmt_dt($r['requested_at'])) ?></td>
                    <?php if ($state !== 'requested'): ?>
                        <td><?= e($r['reviewed_by_name'] ?? '—') ?><?php if ($r['reviewed_at']): ?><div class="text-secondary" style="font-size:.72rem"><?= e(fmt_dt($r['reviewed_at'])) ?></div><?php endif; ?></td>
                        <td style="max-width:18rem"><?= e(mb_strimwidth((string) ($r['review_comment'] ?? ''), 0, 70, '…')) ?></td>
                    <?php endif; ?>
                    <?php if ($state === 'deleted'): ?>
                        <td><?= e($r['deleted_by_name'] ?? '—') ?></td>
                        <td><?= e(fmt_dt($r['deleted_at'])) ?></td>
                    <?php endif; ?>
                </tr>
            <?php endforeach; ?>
            <?php if (!$rows): ?>
            <?php /* Column count per tab: 7 base + 1 tick + 2 reviewed + 2 deleted. Keep in step when adding one. */ ?>
            <tr><td colspan="<?= 7 + ($tick?1:0) + ($state!=='requested'?2:0) + ($state==='deleted'?2:0) ?>" class="text-center text-secondary py-4">
                Nothing here.</td></tr>
            <?php endif; ?>
            </tbody>
        </table></div>
    </div>
    </form>
</div></div>
<script>
document.getElementById('reqall')?.addEventListener('change', function () {
    document.querySelectorAll('.reqchk').forEach(c => { c.checked = this.checked; });
});
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
