<?php
/**
 * Coding inventory queue (coding.work).
 *   - default        : the signed-in coder's allocated charts (FIFO order)
 *   - ?view=all      : every coder's charts (needs coding.supervise)
 * Filters: client, process, provider, status, (coder in the all-view).
 * "Get next" opens the oldest pending chart for coding (coding-chart.php).
 *
 * Top strip: BILLING SHEETS for the coder's client»process»facility»provider + a date (coding-bs-view.php).
 * Bulk: a coder may RE-TAG their own workable charts' client»process»facility»provider (fix a wrong upload);
 *       a supervisor/manager may DELETE a team chart (all-view) — both audit-logged.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$all = (($_GET['view'] ?? '') === 'all') && can('coding.supervise');
remember_filters('coding_inv_' . ($all ? 'all' : 'my'), ['client_id','process_id','facility_id','provider_id','coder_id','patient','mrn','dos_from','dos_to','up_from','up_to','w_from','w_to']);   // per-session filter memory
$team = coding_team_scope($pdo, $uid);                       // null = sysadmin (all); else this user's reporting team + self
$teamIn = ($team === null) ? '' : ('IN (' . implode(',', $team ?: [0]) . ')');   // SQL fragment for assigned_to scope when supervising

$intList    = static fn($v): array => array_values(array_unique(array_filter(array_map('intval', (array) $v), static fn($x) => $x > 0)));
$fClients   = $intList($_GET['client_id']   ?? []);   // multi-select filters — arrays of ids, matched with IN (…)
$fProcesses = $intList($_GET['process_id']  ?? []);
$fFacilities= $intList($_GET['facility_id'] ?? []);
$fProviders = $intList($_GET['provider_id'] ?? []);
$VALID_ST  = ['pending','in_progress','hold','completed','rfi','disregard'];
$WORKABLE  = ['pending','in_progress','hold'];
$fStatus   = in_array($_GET['status'] ?? '', $VALID_ST, true) ? $_GET['status'] : '';   // '' = the WORKABLE queue (default)
$fCoders   = $intList($_GET['coder_id']    ?? []);
$returned  = ($_GET['returned'] ?? '') === '1';   // "sent back to me" bucket — workable charts an auditor/validator/supervisor/manager returned (rto_at set)
$fPatient  = trim((string) ($_GET['patient'] ?? ''));   // per-header text filters (F4)
$fMrn      = trim((string) ($_GET['mrn'] ?? ''));
$fDosFrom  = coding_parse_date($_GET['dos_from'] ?? '') ?: null;   // DOS (date of service) range filter
$fDosTo    = coding_parse_date($_GET['dos_to'] ?? '') ?: null;
$fUpFrom   = coding_parse_date($_GET['up_from'] ?? '') ?: null;    // uploaded-date range filter
$fUpTo     = coding_parse_date($_GET['up_to'] ?? '') ?: null;
/* WORKED-date range filter = ci.submitted_at (when the coder submitted the chart — the same field the
   "Completed" column sorts by). NOTE: workable charts (open / in progress / on hold) have submitted_at NULL,
   so this filter only ever matches on the Completed / RFI / Disregard queues. Every send-back resets
   submitted_at, so it always reflects the LATEST submit, not the first. */
$fWkFrom   = coding_parse_date($_GET['w_from'] ?? '') ?: null;
$fWkTo     = coding_parse_date($_GET['w_to'] ?? '') ?: null;

/* URL back to the current view + filters (used by the bulk POST handlers). */
$selfQ = array_filter(['view'=>$all?'all':null, 'status'=>$fStatus?:null, 'returned'=>$returned?1:null,
    'client_id'=>$fClients?:null, 'process_id'=>$fProcesses?:null, 'facility_id'=>$fFacilities?:null, 'provider_id'=>$fProviders?:null, 'coder_id'=>$fCoders?:null,
    'patient'=>$fPatient?:null, 'mrn'=>$fMrn?:null, 'dos_from'=>$fDosFrom, 'dos_to'=>$fDosTo, 'up_from'=>$fUpFrom, 'up_to'=>$fUpTo, 'w_from'=>$fWkFrom, 'w_to'=>$fWkTo]);
$backUrl = '/admin/coding-inventory.php' . ($selfQ ? ('?' . http_build_query($selfQ)) : '');

/* ───────────────────────────── POST: coder RE-TAG own workable charts ───────────────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'retag') {
    csrf_check_or_die();
    if (!can('coding.work')) { flash('error', 'You are not allowed to re-tag charts.'); redirect($backUrl); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['chart_ids'] ?? []))));
    $nc = (int) ($_POST['rt_client_id'] ?? 0); $np = (int) ($_POST['rt_process_id'] ?? 0); $nf = (int) ($_POST['rt_facility_id'] ?? 0); $npr = (int) ($_POST['rt_provider_id'] ?? 0);
    if (!$ids)                          { flash('error', 'Select at least one chart to re-tag.'); redirect($backUrl); }
    if (!$nc || !$np || !$nf || !$npr)  { flash('error', 'Pick the correct client, process, facility and provider.'); redirect($backUrl); }
    // The corrected tuple must be a real hierarchy path AND within the coder's own access.
    $okHier = $pdo->prepare("SELECT 1 FROM coding_facility_providers fp JOIN coding_facilities f ON f.id=fp.facility_id
        JOIN processes p ON p.id=f.process_id WHERE fp.provider_id=? AND fp.facility_id=? AND f.process_id=? AND p.client_id=? LIMIT 1");
    $okHier->execute([$npr, $nf, $np, $nc]);
    if (!$okHier->fetchColumn())                                { flash('error', 'Invalid client / process / facility / provider combination.'); redirect($backUrl); }
    if (!coding_user_can_tuple($uid, $nc, $np, $nf, $npr))      { flash('error', 'That client / process / facility / provider is not in your access.'); redirect($backUrl); }
    // Only the coder's OWN charts, still workable, and not already in billing/client flow.
    $in = implode(',', array_fill(0, count($ids), '?'));
    $sel = $pdo->prepare("SELECT id,client_id,process_id,facility_id,provider_id FROM coding_inventory
        WHERE id IN ($in) AND assigned_to=? AND status IN ('pending','in_progress','hold')
          AND released_to_billing_at IS NULL AND client_sent_at IS NULL");
    $sel->execute(array_merge($ids, [$uid]));
    $targets = $sel->fetchAll(PDO::FETCH_ASSOC);
    $upd = $pdo->prepare("UPDATE coding_inventory SET client_id=?,process_id=?,facility_id=?,provider_id=?,updated_at=NOW() WHERE id=?");
    $n = 0;
    foreach ($targets as $b) {
        $upd->execute([$nc, $np, $nf, $npr, (int) $b['id']]);
        audit_log('coding.inventory.retag', 'coding_inventory', (int) $b['id'], $b, ['client_id'=>$nc,'process_id'=>$np,'facility_id'=>$nf,'provider_id'=>$npr]);
        coding_log_event($pdo, (int) $b['id'], 'retag', $uid, 'client/process/facility/provider corrected', null, null);
        $n++;
    }
    flash($n ? 'success' : 'warning', $n ? "$n chart(s) re-tagged." : 'Nothing re-tagged — charts must be your own and still workable (not completed / released / sent to client).');
    redirect($backUrl);
}

/* ───────────────────────────── POST: set / clear TOP priority (multi-select) ─────────────────────────────
 * "Work this next." Get next / Start coding ALREADY serves priority charts first — coding_next_chart_id()
 * ORDERs by `is_priority DESC` then the normal oldest-first aging order — so flagging here is all that's
 * needed: TOP charts are served one after another, then the queue resumes plain aging. The flag clears
 * itself when the chart is completed (see the submit UPDATE in coding-chart.php).
 * SCOPE (the security boundary — the UI is cosmetic): fenced to exactly what this user can already SEE.
 * A supervisor in the all-view may flag their TEAM's charts; anyone else only charts assigned to
 * themselves — so a posted id outside your queue is simply skipped. Workable charts only. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($_POST['action'] ?? '', ['set_priority', 'clear_priority'], true)) {
    csrf_check_or_die();
    if (!can('coding.work') && !can('coding.supervise')) { flash('error', 'You are not allowed to set priority.'); redirect($backUrl); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['chart_ids'] ?? []))));
    if (!$ids) { flash('error', 'Tick at least one chart first.'); redirect($backUrl); }
    $val       = ($_POST['action'] === 'set_priority') ? 1 : 0;
    $in        = implode(',', array_fill(0, count($ids), '?'));
    $selParams = $ids;
    $scope     = '';
    if ($all) {                       // supervisor/manager all-view → own reporting team only (null team = sysadmin)
        if ($team !== null) $scope = ' AND assigned_to IN (' . implode(',', array_map('intval', $team ?: [0])) . ')';
    } else {                          // personal view → only my own charts
        $scope = ' AND assigned_to=?'; $selParams[] = $uid;
    }
    // SELECT the eligible ids FIRST (same fence), then update them — mirrors the retag/delete handlers below.
    // Why not UPDATE + rowCount(): PDO/MySQL reports CHANGED rows, not MATCHED, so re-clicking "Set TOP" within
    // the same second (nothing actually changes) would report 0 and show the red "Nothing changed … inside your
    // own queue" warning — which reads like a permission failure when the flag is in fact already set correctly.
    // Selecting first also gives us the ids for a PER-CHART audit + timeline entry.
    $sel = $pdo->prepare("SELECT id FROM coding_inventory
        WHERE id IN ($in) AND status IN ('pending','in_progress','hold')
          AND released_to_billing_at IS NULL AND client_sent_at IS NULL$scope");
    $sel->execute($selParams);
    $elig = array_map('intval', $sel->fetchAll(PDO::FETCH_COLUMN));
    $n = 0;
    if ($elig) {
        $inE = implode(',', array_fill(0, count($elig), '?'));
        // priority_by means "who FLAGGED this" — on a clear, blank it rather than stamping whoever cleared it
        // (coding-chart.php's auto-clear-on-completion likewise only resets the flag + priority_at).
        $set      = $val ? 'is_priority=1, priority_by=?, priority_at=NOW()' : 'is_priority=0, priority_by=NULL, priority_at=NULL';
        $upParams = $val ? array_merge([$uid], $elig) : $elig;
        $pdo->prepare("UPDATE coding_inventory SET $set, updated_at=NOW() WHERE id IN ($inE)")->execute($upParams);
        $n = count($elig);
        foreach ($elig as $eid) {   // per-chart trail: a batch row with entity_id NULL never surfaces on the chart's history
            audit_log('coding.inventory.priority', 'coding_inventory', $eid, null, ['priority' => $val]);
            coding_log_event($pdo, $eid, 'priority', $uid, $val ? 'marked TOP priority' : 'priority cleared', null, null);
        }
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'warning', $n
        ? (($val ? "$n chart(s) marked TOP priority — they'll be served before everything else." : "Priority cleared on $n chart(s).")
           . ($skip ? " {$skip} skipped (not workable, or outside your queue)." : ''))
        : 'Nothing changed — charts must be workable (open / in progress / on hold) and inside your own queue.');
    redirect($backUrl);
}

/* ───────────────────────────── POST: request DELETION of workable charts ─────────────────────────────
 * A coder (or lead/manager) asks for a non-workable chart to be destroyed. This deletes NOTHING — it moves the
 * chart into the Deletion Requests bucket for a Team Lead to approve and a Manager to execute. The reason is
 * mandatory: it is the only thing the two reviewers downstream have to judge by. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'request_deletion') {
    csrf_check_or_die();
    if (!can('coding.deletion.request')) { flash('error', 'You are not allowed to request deletion.'); redirect($backUrl); }
    $ids    = array_values(array_filter(array_map('intval', (array) ($_POST['chart_ids'] ?? []))));
    $reason = trim((string) ($_POST['del_reason'] ?? ''));
    if (!$ids)          { flash('error', 'Tick at least one chart first.'); redirect($backUrl); }
    if ($reason === '') { flash('error', 'A reason is required — the Team Lead and Manager have nothing else to judge by.'); redirect($backUrl); }
    $in        = implode(',', array_fill(0, count($ids), '?'));
    $selParams = $ids;
    $scope     = '';
    if ($all) {                       // supervisor/manager all-view → own reporting team only (null team = sysadmin)
        if ($team !== null) $scope = ' AND assigned_to IN (' . implode(',', array_map('intval', $team ?: [0])) . ')';
    } else {                          // personal view → only my own charts
        $scope = ' AND assigned_to=?'; $selParams[] = $uid;
    }
    /* Eligible = the WORKABLE set, exactly as the priority and delete handlers define it. 'hold' is already
     * INSIDE that set, so "in the workable queue or on hold" is ONE predicate, not two. Select-then-write for
     * the same reason the priority handler does: we need the rows for the snapshot and a per-chart trail. */
    $sel = $pdo->prepare("SELECT * FROM coding_inventory
        WHERE id IN ($in) AND status IN ('pending','in_progress','hold')
          AND released_to_billing_at IS NULL AND client_sent_at IS NULL$scope");
    $sel->execute($selParams);
    $elig = $sel->fetchAll(PDO::FETCH_ASSOC);
    $n = 0;
    foreach ($elig as $row) {
        $cid  = (int) $row['id'];
        $prev = (string) $row['status'];
        try {
            $pdo->beginTransaction();
            /* Snapshot the chart AS REVIEWED. Freezing it at REQUEST time records what the Lead and Manager
             * actually approved destroying, rather than whatever the chart drifted into afterwards.
             * coding_chart_full_snapshot() — not the bare $row — because coding_inventory holds NO codes: the
             * CPT/ICD/modifier content lives in tables the cascade destroys, so a row-only snapshot would record
             * that a chart existed while saying nothing about what was in it. */
            $pdo->prepare("INSERT INTO coding_deletion_requests
                    (inventory_id, state, reason, requested_by, requested_at, prev_status, chart_snapshot_json, created_at, updated_at)
                    VALUES (?, 'requested', ?, ?, NOW(), ?, ?, NOW(), NOW())")
                ->execute([$cid, $reason, $uid, $prev,
                           json_encode(coding_chart_full_snapshot($pdo, $cid), JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR)]);
            /* deletion_prev_status on the CHART is what a Reject restores from (hold and pending are different
             * queues to a coder); prev_status on the REQUEST row is the permanent historical record. Two copies
             * on purpose — the chart's copy is cleared when it leaves the bucket. Priority is dropped: a chart
             * awaiting deletion must not come back at the top of anyone's queue if it is later rejected. */
            $pdo->prepare("UPDATE coding_inventory
                    SET status='deletion_requested', deletion_prev_status=?,
                        is_priority=0, priority_by=NULL, priority_at=NULL, updated_at=NOW()
                    WHERE id=?")->execute([$prev, $cid]);
            audit_log('coding.deletion.request', 'coding_inventory', $cid, ['status' => $prev],
                      ['status' => 'deletion_requested', 'reason' => $reason]);
            coding_log_event($pdo, $cid, 'deletion_requested', $uid, $reason, $prev, 'deletion_requested');
            $pdo->commit();
            $n++;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
        }
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'warning', $n
        ? "$n chart(s) moved to Deletion Requests — a Team Lead reviews them next."
          . ($skip ? " {$skip} skipped (not workable, or outside your queue)." : '')
        : 'Nothing requested — charts must be workable (open / in progress / on hold) and inside your own queue.');
    redirect($backUrl);
}

/* ───────────────────────────── POST: MANAGER-ONLY delete chart ─────────────────────────────
 * Re-gated 2026-07-16 from can('coding.supervise') to coding.deletion.execute. It previously let ANY
 * coding.supervise holder hard-delete — and coding_supervisor (the Team Lead) holds it, which flatly
 * contradicts "only the Manager can permanently delete a chart". Leaving it would have made the whole
 * Deletion Request Workflow optional: a Team Lead wanting to skip Manager approval just used this button.
 * Kept (rather than retired) as the Manager's fast path for obvious junk, with its never-coded guard
 * intact — the request workflow is the route for charts that HAVE been coded.
 * Belt-and-braces role re-check mirrors admin/data-cleanup.php:31, which re-asserts is_sysadmin() inside
 * the POST even though the route gate already said so: for irreversible destruction of PHI, one gate is
 * not enough, and a stray per-user grant of the permission must not by itself hand out the trigger. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'delete') {
    csrf_check_or_die();
    if (!coding_can_delete_charts($uid)) {
        flash('error', 'Only a Coding Manager can permanently delete a chart. Submit a deletion request instead.');
        redirect($backUrl);
    }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['chart_ids'] ?? []))));
    if (!$ids) { flash('error', 'Select at least one chart to delete.'); redirect($backUrl); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    // Team-scoped; ONLY charts that have NEVER been coded may be removed. A chart is "touched/coded" — and therefore
    // PROTECTED — if it was ever returned for rework (rto_at set), was ever submitted by a coder (coder_id set), or
    // already has saved CPT lines. IMPORTANT: every send-back path (coding-rto / coding-audit / coding-validate)
    // resets status='pending' + submitted_at=NULL, so submitted_at is NOT a reliable "never coded" signal; rto_at and
    // coder_id survive that reset. Guarding on them means deleting can never orphan a coder's work or downstream
    // audit / billing / client history.
    $teamClause = ($team === null) ? '' : (' AND ci.assigned_to IN (' . implode(',', array_map('intval', $team ?: [0])) . ')');
    $sel = $pdo->prepare("SELECT ci.* FROM coding_inventory ci
        WHERE ci.id IN ($in) AND ci.status IN ('pending','in_progress','hold')
          AND ci.rto_at IS NULL AND ci.coder_id IS NULL
          AND ci.released_to_billing_at IS NULL AND ci.client_sent_at IS NULL
          AND NOT EXISTS (SELECT 1 FROM coding_chart_cpts cc WHERE cc.inventory_id=ci.id)$teamClause");
    $sel->execute($ids);
    $targets = $sel->fetchAll(PDO::FETCH_ASSOC);
    /* Per-chart transaction, NOT one around the batch: a single bad chart must not silently void the deletes
     * that already succeeded (includes/billing.php:798 wraps a whole batch and discards the count on throw).
     * The audit_log() before-snapshot is written BEFORE the cascade because afterwards there is nothing left
     * to snapshot — it is the only forensic record the chart ever existed. */
    $n = 0; $failed = 0;
    foreach ($targets as $row) {
        $cid = (int) $row['id'];
        try {
            $pdo->beginTransaction();
            audit_log('coding.inventory.delete', 'coding_inventory', $cid, $row, null);
            // Was a bare DELETE trusting FK ON DELETE CASCADE. That cascade cleans nothing in billing_*
            // (all FK-LESS) and may not exist at all on databases older than the CREATE that declares it.
            coding_delete_chart_cascade($pdo, $cid);
            $pdo->commit();
            $n++;
        } catch (Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            $failed++;
        }
    }
    if ($failed) flash('error', $failed . ' chart(s) could not be deleted and were left untouched.');
    flash($n ? 'success' : 'warning', $n ? "$n chart(s) deleted." : 'Nothing deleted — only never-coded charts from your own team can be deleted (a chart that has been coded, even if later sent back, is protected).');
    redirect($backUrl);
}

/* ───────────────────────────── POST: supervisor/manager REASSIGN charts to another team coder ─────────────────────────────
 * Move workable (open / in-progress / on-hold) team charts to a different coder; the chart returns to that coder's open
 * queue (status reset to pending). Team-scoped exactly like coding-reassign.php — a supervisor can only touch their team. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'reassign') {
    csrf_check_or_die();
    if (!can('coding.supervise')) { flash('error', 'You are not allowed to reassign charts.'); redirect($backUrl); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['chart_ids'] ?? []))));
    $to  = (int) ($_POST['to_coder'] ?? 0);
    if (!$ids) { flash('error', 'Select at least one chart to reassign.'); redirect($backUrl); }
    if (!$to)  { flash('error', 'Choose a coder to reassign to.'); redirect($backUrl); }
    if ($team !== null && !in_array($to, $team, true)) { flash('error', 'You can only reassign to a member of your team.'); redirect($backUrl); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    $teamClause = ($team === null) ? '' : (' AND assigned_to IN (' . implode(',', array_map('intval', $team ?: [0])) . ')');
    $st = $pdo->prepare("UPDATE coding_inventory SET assigned_to=?, status='pending', started_at=NULL, updated_at=NOW()
                         WHERE id IN ($in) AND status IN ('pending','in_progress','hold')$teamClause");
    $st->execute(array_merge([$to], $ids));
    audit_log('coding.inventory.reassign', 'coding_inventory', null, null, ['to'=>$to, 'ids'=>$ids]);
    if ($st->rowCount() && $to !== $uid)   // tell the new coder (one summary, not per-chart)
        notify($to, 'coding', 'Charts assigned to you', $st->rowCount() . ' chart(s) were assigned to your coding queue by your supervisor.', '/admin/coding-inventory.php');
    flash($st->rowCount() ? 'success' : 'warning', $st->rowCount() ? $st->rowCount() . ' chart(s) reassigned.' : 'Nothing reassigned — only workable (open / in-progress / on-hold) team charts can be reassigned.');
    redirect($backUrl);
}

$where = []; $params = [];
if (!$all) { $where[] = 'ci.assigned_to = ?'; $params[] = $uid; }
elseif ($teamIn !== '') { $where[] = "ci.assigned_to $teamIn"; }   // supervisor/manager: only their team
if ($fClients)    { $where[] = 'ci.client_id IN ('   . implode(',', $fClients)    . ')'; }
if ($fProcesses)  { $where[] = 'ci.process_id IN ('  . implode(',', $fProcesses)  . ')'; }
if ($fFacilities) { $where[] = 'ci.facility_id IN (' . implode(',', $fFacilities) . ')'; }
if ($fProviders)  { $where[] = 'ci.provider_id IN (' . implode(',', $fProviders)  . ')'; }
// Status queue: the "sent back" bucket (workable charts returned to the coder) takes precedence;
// otherwise a specific status when chosen, otherwise the WORKABLE set (yet-to-code / in-progress / hold).
if ($returned)          { $where[] = "ci.status IN ('pending','in_progress','hold') AND ci.rto_at IS NOT NULL"; }
elseif ($fStatus !== '') { $where[] = 'ci.status = ?'; $params[] = $fStatus; }
else                     { $where[] = "ci.status IN ('pending','in_progress','hold')"; }
if ($all && $fCoders) { $where[] = 'ci.assigned_to IN (' . implode(',', $fCoders) . ')'; }
if ($fPatient) { $where[] = 'ci.patient_name LIKE ?'; $params[] = '%' . $fPatient . '%'; }
if ($fMrn)     { $where[] = 'ci.patient_mrn LIKE ?';  $params[] = '%' . $fMrn . '%'; }
if ($fDosFrom) { $where[] = 'ci.dos >= ?'; $params[] = $fDosFrom; }
if ($fDosTo)   { $where[] = 'ci.dos <= ?'; $params[] = $fDosTo; }
if ($fUpFrom)  { $where[] = 'ci.uploaded_at >= ?'; $params[] = $fUpFrom . ' 00:00:00'; }
if ($fUpTo)    { $where[] = 'ci.uploaded_at < ?';  $params[] = date('Y-m-d', strtotime($fUpTo . ' +1 day')) . ' 00:00:00'; }
if ($fWkFrom)  { $where[] = 'ci.submitted_at >= ?'; $params[] = $fWkFrom . ' 00:00:00'; }
if ($fWkTo)    { $where[] = 'ci.submitted_at < ?';  $params[] = date('Y-m-d', strtotime($fWkTo . ' +1 day')) . ' 00:00:00'; }
$wsql = $where ? ('WHERE ' . implode(' AND ', $where)) : '';

/* Status counts — respect the ACTIVE filters (client/process/facility/provider/coder/patient/mrn/dos/uploaded) so the
   queue cards change with the filter bar. Status itself is NOT applied (the cards ARE the per-status breakdown); the
   "sent back" bucket is counted separately below. Scope (own charts / team) always applies. Keep $filt in sync with
   the list WHERE built above. */
$cntScope = []; $cntParams = [];
if (!$all) { $cntScope[] = 'ci.assigned_to = ?'; $cntParams[] = $uid; }
elseif ($teamIn !== '') { $cntScope[] = "ci.assigned_to $teamIn"; }
$filt = []; $filtP = [];
if ($fClients)        $filt[] = 'ci.client_id IN ('   . implode(',', $fClients)    . ')';
if ($fProcesses)      $filt[] = 'ci.process_id IN ('  . implode(',', $fProcesses)  . ')';
if ($fFacilities)     $filt[] = 'ci.facility_id IN (' . implode(',', $fFacilities) . ')';
if ($fProviders)      $filt[] = 'ci.provider_id IN (' . implode(',', $fProviders)  . ')';
if ($all && $fCoders) $filt[] = 'ci.assigned_to IN ('. implode(',', $fCoders)     . ')';
if ($fPatient) { $filt[] = 'ci.patient_name LIKE ?'; $filtP[] = '%' . $fPatient . '%'; }
if ($fMrn)     { $filt[] = 'ci.patient_mrn LIKE ?';  $filtP[] = '%' . $fMrn . '%'; }
if ($fDosFrom) { $filt[] = 'ci.dos >= ?'; $filtP[] = $fDosFrom; }
if ($fDosTo)   { $filt[] = 'ci.dos <= ?'; $filtP[] = $fDosTo; }
if ($fUpFrom)  { $filt[] = 'ci.uploaded_at >= ?'; $filtP[] = $fUpFrom . ' 00:00:00'; }
if ($fUpTo)    { $filt[] = 'ci.uploaded_at < ?';  $filtP[] = date('Y-m-d', strtotime($fUpTo . ' +1 day')) . ' 00:00:00'; }
if ($fWkFrom)  { $filt[] = 'ci.submitted_at >= ?'; $filtP[] = $fWkFrom . ' 00:00:00'; }
if ($fWkTo)    { $filt[] = 'ci.submitted_at < ?';  $filtP[] = date('Y-m-d', strtotime($fWkTo . ' +1 day')) . ' 00:00:00'; }
$cntAll  = array_merge($cntScope, $filt);
$cntAllP = array_merge($cntParams, $filtP);
$cntWsql = $cntAll ? ('WHERE ' . implode(' AND ', $cntAll)) : '';
$counts = ['pending'=>0,'in_progress'=>0,'hold'=>0,'completed'=>0,'rfi'=>0,'disregard'=>0];
$cs = $pdo->prepare("SELECT ci.status, COUNT(*) n FROM coding_inventory ci $cntWsql GROUP BY ci.status");
$cs->execute($cntAllP);
foreach ($cs->fetchAll() as $r) $counts[$r['status']] = (int) $r['n'];

/* "Sent back to me" = workable charts an auditor/validator/supervisor/manager returned (rto_at set) — same filters. */
$rWhere = array_merge($cntScope, $filt); $rWhere[] = "ci.status IN ('pending','in_progress','hold')"; $rWhere[] = "ci.rto_at IS NOT NULL";
$rcs = $pdo->prepare("SELECT COUNT(*) FROM coding_inventory ci WHERE " . implode(' AND ', $rWhere));
$rcs->execute($cntAllP);
$returnedCount = (int) $rcs->fetchColumn();

/* Column sort (whitelist → SQL). Default keeps priority-first, oldest-upload ordering. */
$SORTS = ['patient'=>'ci.patient_name','mrn'=>'ci.patient_mrn','dob'=>'ci.patient_dob','dos'=>'ci.dos','rsd'=>'ci.record_sign_date',
          'client'=>'c.name','facility'=>'f.name','provider'=>'pr.name','coder'=>'u.name','uploaded'=>'ci.uploaded_at','completed'=>'ci.submitted_at','status'=>'ci.status'];
$sort = (string) ($_GET['sort'] ?? '');
$dir  = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';
$orderBy = isset($SORTS[$sort]) ? ($SORTS[$sort] . ' ' . $dir . ', ci.id DESC') : 'ci.is_priority DESC, ci.uploaded_at ASC, ci.id ASC';
/* Sortable <th>: clicking toggles asc/desc; preserves the active filters. */
$sortTh = function (string $key, string $label, string $cls = '') use ($sort, $dir, $selfQ) {
    $isCur = ($sort === $key);
    $nextDir = ($isCur && $dir === 'ASC') ? 'desc' : 'asc';
    $ico = $isCur ? ($dir === 'ASC' ? 'ti-caret-up-filled' : 'ti-caret-down-filled') : 'ti-arrows-sort';
    $u = '/admin/coding-inventory.php?' . http_build_query(array_merge($selfQ, ['sort' => $key, 'dir' => $nextDir]));
    return '<th data-col="' . e($key) . '" data-collabel="' . e($label) . '"' . ($cls ? ' class="' . e($cls) . '"' : '') . '><a href="' . e(url($u)) . '" class="text-reset text-decoration-none text-nowrap">' . e($label) . ' <i class="ti ' . $ico . ' ' . ($isCur ? 'text-primary' : 'text-secondary') . '"' . ($isCur ? '' : ' style="opacity:.4"') . '></i></a></th>';
};
$rows = $pdo->prepare("SELECT ci.*, c.name cn, p.name pn, f.name fn, pr.name prn, u.name coder, rb.name rtoname,
    (SELECT COUNT(*) FROM coding_chart_cpts cc WHERE cc.inventory_id=ci.id) AS cpt_count
    FROM coding_inventory ci
    LEFT JOIN clients c ON c.id=ci.client_id
    LEFT JOIN processes p ON p.id=ci.process_id
    LEFT JOIN coding_facilities f ON f.id=ci.facility_id
    LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
    LEFT JOIN users u ON u.id=ci.assigned_to
    LEFT JOIN users rb ON rb.id=ci.rto_by
    $wsql
    ORDER BY $orderBy
    LIMIT 1000");
$rows->execute($params);
$rows = $rows->fetchAll();

/* Coded views (Completed / RFI / Disregard) also show the CPT + ICD(s) per chart (stacked, audit-corrected effective
   lines). Skipped on the workable queue where charts aren't coded yet. */
$codesView  = in_array($fStatus, ['completed','rfi','disregard'], true);
$linesByInv = $codesView ? coding_effective_lines_bulk($pdo, array_column($rows, 'id')) : [];

/* filter option sources (within the visible scope) */
$optWhere = !$all ? ('WHERE ci.assigned_to = ' . $uid) : ($teamIn !== '' ? "WHERE ci.assigned_to $teamIn" : '');
$clients = $pdo->query("SELECT DISTINCT c.id, c.name FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id $optWhere ORDER BY c.name")->fetchAll();
$procs   = $pdo->query("SELECT DISTINCT p.id, p.name FROM coding_inventory ci JOIN processes p ON p.id=ci.process_id $optWhere ORDER BY p.name")->fetchAll();
$facils  = $pdo->query("SELECT DISTINCT f.id, f.name FROM coding_inventory ci JOIN coding_facilities f ON f.id=ci.facility_id $optWhere ORDER BY f.name")->fetchAll();
$provs   = $pdo->query("SELECT DISTINCT pr.id, pr.name FROM coding_inventory ci JOIN coding_providers pr ON pr.id=ci.provider_id $optWhere ORDER BY pr.name")->fetchAll();
$coders  = $all ? $pdo->query("SELECT DISTINCT u.id, u.name FROM coding_inventory ci JOIN users u ON u.id=ci.assigned_to " . ($teamIn !== '' ? "WHERE ci.assigned_to $teamIn" : '') . " ORDER BY u.name")->fetchAll() : [];
/* Reassign target list (supervisor/manager all-view): every active coder/supervisor/manager in scope — includes
   team members who currently hold no charts, so an empty queue can still receive reassigned work. */
$teamCoders = ($all && can('coding.supervise')) ? $pdo->query("SELECT DISTINCT u.id, u.name FROM users u
    JOIN user_roles ur ON ur.user_id=u.id JOIN roles r ON r.id=ur.role_id
    WHERE u.status='active' AND r.code IN ('coder','coding_supervisor','coding_manager')" . ($teamIn !== '' ? " AND u.id $teamIn" : '') . " ORDER BY u.name")->fetchAll() : [];

// 'deletion_requested' is here so the badge renders if a chart is ever shown while in the bucket (e.g. a direct
// ?id= open). It is deliberately NOT in $VALID_ST (the status filter) or $CAT_MAP (the export categories):
// those are the coding queue's own vocabulary, and a chart awaiting deletion must not be listed or exported by it.
$statusBadge = ['pending'=>'secondary','in_progress'=>'info','hold'=>'warning','completed'=>'success','rfi'=>'orange','disregard'=>'danger','deletion_requested'=>'danger'];
$workableCount = $counts['pending'] + $counts['in_progress'] + $counts['hold'];

/* URL for a queue card/tab, preserving the current scope + filters (status = '' is the workable queue) */
$qbase = array_filter(['view'=>$all?'all':null, 'client_id'=>$fClients?:null, 'process_id'=>$fProcesses?:null, 'facility_id'=>$fFacilities?:null, 'provider_id'=>$fProviders?:null, 'coder_id'=>$fCoders?:null, 'dos_from'=>$fDosFrom, 'dos_to'=>$fDosTo, 'up_from'=>$fUpFrom, 'up_to'=>$fUpTo, 'w_from'=>$fWkFrom, 'w_to'=>$fWkTo]);
$qurl  = fn($status) => url('/admin/coding-inventory.php' . (($q = array_merge($qbase, $status===''?[]:['status'=>$status])) ? '?'.http_build_query($q) : ''));
$rurl  = url('/admin/coding-inventory.php?' . http_build_query(array_merge($qbase, ['returned'=>1])));   // the "sent back" bucket

/* ── Billing sheets the signed-in user may view (scoped to coding_user_scope; unrestricted = all) ── */
$restricted = !coding_scope_unrestricted($uid);
$sheetSql = "SELECT bs.id, bs.client_id cid, bs.process_id pid, bs.facility_id fid, bs.provider_id prid,
        DATE_FORMAT(bs.sheet_date,'%Y-%m-%d') sdate, bs.original_name oname,
        c.name cn, p.name pn, f.name fn, pr.name prn
    FROM coding_billing_sheets bs
    JOIN clients c ON c.id=bs.client_id JOIN processes p ON p.id=bs.process_id
    JOIN coding_facilities f ON f.id=bs.facility_id JOIN coding_providers pr ON pr.id=bs.provider_id";
$sheetParams = [];
if ($restricted) {
    $sheetSql .= " JOIN coding_user_scope s ON s.user_id=? AND " . coding_vertical_scope_sql('s.vertical_id') . " AND s.client_id=bs.client_id
        AND (s.process_id IS NULL OR s.process_id=bs.process_id)
        AND (s.facility_id IS NULL OR s.facility_id=bs.facility_id)
        AND (s.provider_id IS NULL OR s.provider_id=bs.provider_id)";
    $sheetParams[] = $uid;   // coding billing sheets use the user's coding alignment only (vertical fenced by code in the JOIN)
}
$sheetSql .= " ORDER BY bs.sheet_date DESC, c.name, p.name, f.name, pr.name LIMIT 3000";
$ss = $pdo->prepare($sheetSql); $ss->execute($sheetParams); $sheets = $ss->fetchAll(PDO::FETCH_ASSOC);

/* Prefill the billing-sheet strip from the coder's most recently uploaded chart. */
$recentTuple = null;
if (!$all) {
    $rt = $pdo->prepare("SELECT client_id cid, process_id pid, facility_id fid, provider_id prid FROM coding_inventory WHERE assigned_to=? ORDER BY uploaded_at DESC, id DESC LIMIT 1");
    $rt->execute([$uid]); $recentTuple = $rt->fetch(PDO::FETCH_ASSOC) ?: null;
}

/* The user's scoped C»P»F»Provider tree — powers BOTH the coder re-tag picker AND the billing-sheets strip, so
   EVERY aligned facility/provider shows there (even before any sheet is uploaded for it). Built once, coder view
   only (a supervisor's team view doesn't need it, and an unrestricted tree would be huge). */
$accessTree = $all ? [] : coding_accessible_tree($uid);
$newChartTree = coding_accessible_tree($uid, true);   // Add New Chart picker: your allocated c»p»f»provider (SysAdmin: all Coding clients) — built in both views
$retagTree  = (!$all && can('coding.work')) ? $accessTree : [];
$bsTree     = $accessTree;

/* ─────────── Excel export (F4): the current inventory list with the active filters + scope ─────────── */
if (($_GET['export'] ?? '') === 'xlsx') {
    // Re-run the same list query (same $wsql + $params, same scope/filters) without the on-screen LIMIT cap.
    $ex = $pdo->prepare("SELECT ci.id, ci.is_priority, ci.patient_name, ci.patient_mrn, ci.patient_dob, ci.dos, ci.record_sign_date,
            c.name cn, p.name pn, f.name fn, pr.name prn, u.name coder, ci.uploaded_at, ci.status, rb.name rtoname, ci.rto_at
        FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        LEFT JOIN users u ON u.id=ci.assigned_to
        LEFT JOIN users rb ON rb.id=ci.rto_by
        $wsql ORDER BY ci.is_priority DESC, ci.uploaded_at ASC, ci.id ASC LIMIT 10000");
    $ex->execute($params);
    $hdr = ['Chart #', 'Priority', 'Patient', 'MRN/PRN', 'DOB', 'DOS', 'Record sign', 'Client', 'Process', 'Facility', 'Provider'];
    if ($all) $hdr[] = 'Coder';
    $hdr[] = 'Uploaded'; $hdr[] = 'Status'; if ($returned) $hdr[] = 'Sent back by';
    $data = [];
    foreach ($ex->fetchAll() as $r) {
        $row = [(int) $r['id'], ((int) $r['is_priority'] ? 'TOP' : ''), $r['patient_name'], $r['patient_mrn'],
            $r['patient_dob'] ? date('Y-m-d', strtotime($r['patient_dob'])) : '',
            $r['dos'] ? date('Y-m-d', strtotime($r['dos'])) : '',
            $r['record_sign_date'] ? date('Y-m-d', strtotime($r['record_sign_date'])) : '',
            $r['cn'], $r['pn'], $r['fn'], $r['prn']];
        if ($all) $row[] = $r['coder'] ?: '';
        $row[] = disp_dt($r['uploaded_at']);
        $row[] = str_replace('_', ' ', (string) $r['status']);
        if ($returned) $row[] = $r['rtoname'] ?: '';
        $data[] = $row;
    }
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $x->addSheet('Coding inventory', $hdr, $data);
    audit_log('coding.inventory.export', 'coding_inventory', null, null, ['rows'=>count($data), 'view'=>($all?'all':'my'), 'status'=>$fStatus, 'returned'=>$returned]);
    $x->download('coding-inventory-' . ($all ? 'all' : 'my') . ($fStatus ? '-' . $fStatus : '') . date('-Ymd') . '.xlsx');
    exit;
}

/* ─────────── Detailed export (with codes): the coder's charts, ONE row per coded CPT line (CPT/units/mods/ICDs) ─────────── */
if (($_GET['export'] ?? '') === 'detail') {
    $ex = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.patient_dob, c.name cn, p.name pn, f.name fn, pr.name prn, u.name coder, ci.status, ci.uploaded_at
        FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        LEFT JOIN users u ON u.id=ci.assigned_to
        $wsql ORDER BY ci.uploaded_at ASC, ci.id ASC LIMIT 10000");
    $ex->execute($params);
    $charts = $ex->fetchAll();
    $linesByInv = coding_effective_lines_bulk($pdo, array_column($charts, 'id'));
    $hdr = ['Chart #','Patient','MRN/PRN','DOB','Client','Process','Facility','Provider'];
    if ($all) $hdr[] = 'Coder';
    $hdr = array_merge($hdr, ['Status','Uploaded','Line DOS','CPT','Units','Modifier(s)','ICD(s)']);
    $data = [];
    foreach ($charts as $r) {
        $base = [(int) $r['id'], $r['patient_name'], $r['patient_mrn'], $r['patient_dob'] ? date('Y-m-d', strtotime($r['patient_dob'])) : '',
            $r['cn'], $r['pn'], $r['fn'], $r['prn']];
        if ($all) $base[] = $r['coder'] ?: '';
        $base[] = str_replace('_', ' ', (string) $r['status']);
        $base[] = disp_dt($r['uploaded_at']);
        $lines = $linesByInv[(int) $r['id']] ?? [];
        if (!$lines) { $data[] = array_merge($base, ['', '', '', '', '']); continue; }
        foreach ($lines as $ln) $data[] = array_merge($base, [
            !empty($ln['dos']) ? date('Y-m-d', strtotime($ln['dos'])) : '', (string) ($ln['cpt'] ?? ''), (int) ($ln['units'] ?? 1),
            implode(', ', $ln['mods'] ?? []), implode(', ', $ln['icds'] ?? [])]);
    }
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $x->addSheet('Coded charts', $hdr, $data);
    audit_log('coding.inventory.export_detail', 'coding_inventory', null, null, ['rows' => count($data), 'view' => ($all ? 'all' : 'my')]);
    $x->download('coding-' . ($all ? 'all' : 'my') . '-coded' . date('-Ymd') . '.xlsx');
    exit;
}

/* Category → status sets for the full export + productivity (Workable / Completed / RFI / Disregard). */
$CAT_MAP = ['workable'=>['pending','in_progress','hold'], 'completed'=>['completed'], 'rfi'=>['rfi'], 'disregard'=>['disregard']];

/* ─────────── FULL export (F-new): fixed 20-column layout, one row per coded CPT line, with category
   checkboxes (Workable/Completed/RFI/Disregard — persisted as the user's default), an Uploaded-date range
   (default Today) + the active Client/Coder scope. ─────────── */
if (($_GET['export'] ?? '') === 'full') {
    $cats = array_values(array_intersect(array_keys($CAT_MAP), (array) ($_GET['cat'] ?? [])));
    $_SESSION['ci_exp_cat'] = $cats ?: array_keys($CAT_MAP);   // sticky default: persist the selection (empty = all)
    $useCats = $cats ?: array_keys($CAT_MAP);
    $stat = [];
    foreach ($useCats as $c) $stat = array_merge($stat, $CAT_MAP[$c]);
    $stat = array_values(array_unique($stat));
    $statusIn = 'ci.status IN (' . implode(',', array_map(fn($s) => $pdo->quote($s), $stat)) . ')';
    $exFrom = coding_parse_date($_GET['ex_from'] ?? '') ?: date('Y-m-d');   // default Today
    $exTo   = coding_parse_date($_GET['ex_to'] ?? '') ?: date('Y-m-d');
    $_SESSION['ci_exp_from'] = $exFrom; $_SESSION['ci_exp_to'] = $exTo;
    $ew = []; $ep = [];
    if (!$all)               { $ew[] = 'ci.assigned_to = ?'; $ep[] = $uid; }
    elseif ($teamIn !== '')  { $ew[] = "ci.assigned_to $teamIn"; }
    $ew[] = $statusIn;
    if ($fClients)           $ew[] = 'ci.client_id IN ('   . implode(',', $fClients)    . ')';
    if ($fProcesses)         $ew[] = 'ci.process_id IN ('  . implode(',', $fProcesses)  . ')';
    if ($fFacilities)        $ew[] = 'ci.facility_id IN (' . implode(',', $fFacilities) . ')';
    if ($fProviders)         $ew[] = 'ci.provider_id IN (' . implode(',', $fProviders)  . ')';
    if ($all && $fCoders)    $ew[] = 'ci.assigned_to IN (' . implode(',', $fCoders)     . ')';
    $ew[] = 'ci.uploaded_at >= ?'; $ep[] = $exFrom . ' 00:00:00';
    $ew[] = 'ci.uploaded_at < ?';  $ep[] = date('Y-m-d', strtotime($exTo . ' +1 day')) . ' 00:00:00';
    $ex = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.patient_dob, ci.uploaded_at, ci.submitted_at,
            ci.status, ci.audit_status, ci.secondary_reason, ci.comment, ci.scribe_flag,
            c.name cn, p.name pn, f.name fn, pr.name prn, u.name coder, rp.name scribe_name, rp.npi scribe_npi
        FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        LEFT JOIN coding_providers rp ON rp.id=ci.rendering_provider_id
        LEFT JOIN users u ON u.id=ci.assigned_to
        WHERE " . implode(' AND ', $ew) . " ORDER BY c.name, u.name, ci.uploaded_at, ci.id LIMIT 20000");
    $ex->execute($ep);
    $charts = $ex->fetchAll();
    $linesByInv = coding_effective_lines_bulk($pdo, array_column($charts, 'id'));
    require_once __DIR__ . '/../includes/xlsx.php';
    $hdr = ['Client','Coder','Uploaded Date','Completed Date','Process','Facility','Provider','Scribe No.','Scribe Name',
        'MRN/PRN','DOB','Patient','DOS','CPT','Units','Modifier(s)','ICD(s)','Status','Discrepancy Status','Reason','Comment'];
    $data = [];
    foreach ($charts as $r) {
        $scribeNo   = ((int) $r['scribe_flag'] && $r['scribe_npi']) ? substr(preg_replace('/\s+/', '', (string) $r['scribe_npi']), -4) : '';
        $scribeName = (int) $r['scribe_flag'] ? ($r['scribe_name'] ?: '') : '';
        $pre = [$r['cn'] ?: '', $r['coder'] ?: '',
            disp_dt($r['uploaded_at']),
            disp_dt($r['submitted_at']),
            $r['pn'] ?: '', $r['fn'] ?: '', $r['prn'] ?: '', $scribeNo, $scribeName,
            $r['patient_mrn'] ?: '', $r['patient_dob'] ? date('Y-m-d', strtotime($r['patient_dob'])) : '', $r['patient_name'] ?: ''];
        $post = [str_replace('_', ' ', (string) $r['status']),
            $r['audit_status'] ? str_replace('_', ' ', (string) $r['audit_status']) : '',
            $r['secondary_reason'] ?: '', $r['comment'] ?: ''];
        $lines = $linesByInv[(int) $r['id']] ?? [];
        if (!$lines) { $data[] = array_merge($pre, ['', '', '', '', ''], $post); continue; }
        foreach ($lines as $ln) $data[] = array_merge($pre,
            [!empty($ln['dos']) ? date('Y-m-d', strtotime($ln['dos'])) : '', (string) ($ln['cpt'] ?? ''), (int) ($ln['units'] ?? 1), implode(', ', $ln['mods'] ?? []), implode(', ', $ln['icds'] ?? [])],
            $post);
    }
    $x = new SimpleXlsx();
    $x->addSheet('Inventory', $hdr, $data);
    audit_log('coding.inventory.export_full', 'coding_inventory', null, null, ['rows'=>count($data), 'cats'=>$useCats, 'from'=>$exFrom, 'to'=>$exTo, 'view'=>($all?'all':'my')]);
    $x->download('coding-inventory-full-' . $exFrom . '_' . $exTo . '.xlsx');
    exit;
}

/* ─────────── DAILY PRODUCTIVITY export: for one day (default Today), a per-coder SUMMARY (Completed + RFI
   counts) plus a DETAIL sheet of the records each coder processed — both in one workbook. Attribution =
   coder_id (who coded), day = submitted_at. Honors the active Client/Coder scope. ─────────── */
if (($_GET['export'] ?? '') === 'productivity') {
    $pDate = coding_parse_date($_GET['prod_date'] ?? '') ?: date('Y-m-d');
    $pw = ['ci.submitted_at >= ?', 'ci.submitted_at < ?', "ci.status IN ('completed','rfi')"];
    $pp = [$pDate . ' 00:00:00', date('Y-m-d', strtotime($pDate . ' +1 day')) . ' 00:00:00'];
    if (!$all)              { $pw[] = 'ci.coder_id = ?'; $pp[] = $uid; }
    elseif ($teamIn !== '') { $pw[] = "ci.coder_id $teamIn"; }
    if ($fClients)          $pw[] = 'ci.client_id IN ('   . implode(',', $fClients)    . ')';
    if ($fProcesses)        $pw[] = 'ci.process_id IN ('  . implode(',', $fProcesses)  . ')';
    if ($fFacilities)       $pw[] = 'ci.facility_id IN (' . implode(',', $fFacilities) . ')';
    if ($fProviders)        $pw[] = 'ci.provider_id IN (' . implode(',', $fProviders)  . ')';
    if ($all && $fCoders)   $pw[] = 'ci.coder_id IN ('    . implode(',', $fCoders)     . ')';
    $pwSql = 'WHERE ' . implode(' AND ', $pw);
    $det = $pdo->prepare("SELECT cu.name coder, c.name cn, p.name pn, f.name fn, pr.name prn,
            ci.patient_name, ci.patient_mrn, ci.dos, ci.status, ci.submitted_at
        FROM coding_inventory ci
        LEFT JOIN users cu ON cu.id=ci.coder_id
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        $pwSql ORDER BY cu.name, ci.submitted_at LIMIT 20000");
    $det->execute($pp); $detRows = $det->fetchAll();
    $sum = $pdo->prepare("SELECT cu.name coder, SUM(ci.status='completed') completed, SUM(ci.status='rfi') rfi
        FROM coding_inventory ci LEFT JOIN users cu ON cu.id=ci.coder_id
        $pwSql GROUP BY ci.coder_id, cu.name ORDER BY cu.name");
    $sum->execute($pp); $sumRows = $sum->fetchAll();
    require_once __DIR__ . '/../includes/xlsx.php';
    $sData = []; $tc = 0; $tr = 0;
    foreach ($sumRows as $s) { $sData[] = [$s['coder'] ?: '—', (int) $s['completed'], (int) $s['rfi']]; $tc += (int) $s['completed']; $tr += (int) $s['rfi']; }
    $sData[] = ['TOTAL', $tc, $tr];
    $dData = [];
    foreach ($detRows as $r) $dData[] = [$r['coder'] ?: '—', $r['cn'] ?: '', $r['pn'] ?: '', $r['fn'] ?: '', $r['prn'] ?: '',
        $r['patient_name'], $r['patient_mrn'] ?: '', $r['dos'] ? date('Y-m-d', strtotime($r['dos'])) : '',
        str_replace('_', ' ', (string) $r['status']), disp_dt($r['submitted_at'])];
    $x = new SimpleXlsx();
    $x->addSheet('Summary', ['Coder','Completed','RFI'], $sData);
    $x->addSheet('Detail', ['Coder','Client','Process','Facility','Provider','Patient','MRN/PRN','DOS','Status','Completed at'], $dData);
    audit_log('coding.inventory.export_productivity', 'coding_inventory', null, null, ['date'=>$pDate, 'rows'=>count($dData), 'view'=>($all?'all':'my')]);
    $x->download('coding-productivity-' . $pDate . '.xlsx');
    exit;
}

/* Show the checkbox column + a bulk toolbar: coder → re-tag own workable charts; supervisor → delete. */
$canRetag = !$all && can('coding.work') && $retagTree;
// MUST match the POST handler's gate (coding_can_delete_charts) — gating the button on coding.supervise while
// the handler demands the manager role would show every Team Lead a Delete button that always refuses.
$canDelete   = $all && coding_can_delete_charts($uid);
$canReassign = $all && can('coding.supervise');   // was folded into $canDelete; split when delete went manager-only
$canReqDel   = can('coding.deletion.request');    // coders raise these from their OWN queue, so NOT gated on $all
$canPrio  = can('coding.work') || can('coding.supervise');   // anyone on this page may flag TOP priority (server-fenced to their own queue / team)
// Every bulk action must be listed: $bulk is what renders the row checkboxes at all, so an action missing from
// here is an action whose button appears with nothing tickable to aim it at.
$bulk = $canRetag || $canDelete || $canPrio || $canReassign || $canReqDel;

$pageTitle = $all ? 'All inventory' : 'My inventory';
require __DIR__ . '/../includes/header.php';
?>
<div class="page-header"><div class="container-xl"><div class="row align-items-center">
    <?php $qLabels = ['pending'=>'Open','in_progress'=>'In progress','hold'=>'On hold','completed'=>'Completed','rfi'=>'RFI','disregard'=>'Disregard']; ?>
    <div class="col"><h2 class="page-title"><i class="ti ti-list-check me-2 text-primary"></i><?= $all ? 'All inventory' : 'My inventory' ?>
        <span class="badge bg-<?= $returned?'danger':'blue' ?>-lt ms-2"><?= $returned ? 'Sent back to you' : ($fStatus===''?'Workable queue':e($qLabels[$fStatus])) ?></span></h2>
        <div class="text-secondary small"><?php if ($returned): ?>Charts a <strong>validator / auditor / supervisor / manager</strong> sent back for rework — use <strong>Get next sent-back</strong> to take them one by one.<?php else: ?>Your <strong>workable</strong> queue = yet-to-code · in-progress · on-hold. Charts tagged Completed / RFI / Disregard have moved on — click a card to view them. Use <strong>Get next</strong> for the oldest open chart (FIFO).<?php endif; ?></div>
    </div>
    <?php if (can('coding.supervise')): ?>
    <div class="col-auto">
        <?php if ($all): ?>
            <a href="<?= e(url('/admin/coding-inventory.php')) ?>" class="btn btn-outline-secondary"><i class="ti ti-user me-1"></i>My inventory</a>
        <?php else: ?>
            <a href="<?= e(url('/admin/coding-inventory.php?view=all')) ?>" class="btn btn-outline-primary" title="See every coder's charts across your team — and delete unnecessary un-coded charts"><i class="ti ti-stack me-1"></i>All team inventory</a>
        <?php endif; ?>
    </div>
    <?php endif; ?>
    <?php if (!$all && can('coding.work')): ?>
    <div class="col-auto">
        <?php if ($returnedCount > 0): ?><a href="<?= e(url('/admin/coding-chart.php?next=returned')) ?>" class="btn btn-danger me-1"><i class="ti ti-arrow-back-up me-1"></i>Get next sent-back (<?= number_format($returnedCount) ?>)</a><?php endif; ?>
        <button type="button" class="btn btn-success me-1" data-bs-toggle="modal" data-bs-target="#newChartModal" title="Create a chart by hand — for clients whose inventory isn't uploaded"><i class="ti ti-file-plus me-1"></i>Add New Chart</button>
        <a href="<?= e(url('/admin/coding-chart.php?next=1')) ?>" class="btn btn-primary"><i class="ti ti-player-play me-1"></i>Get next chart</a>
    </div>
    <?php endif; ?>
</div></div></div>
<div class="page-body"><div class="container-xl">

    <!-- Clickable queue cards. "Workable" (default) = yet-to-code + in-progress + on-hold; the others
         are charts that have moved on (audit / billing / client) and open read-only on click. -->
    <div class="row row-cards mb-3">
        <div class="col"><a href="<?= e($qurl('')) ?>" class="text-decoration-none">
            <div class="card card-sm <?= ($fStatus===''&&!$returned)?'border-primary':'' ?>"><div class="card-body py-2 text-center">
                <div class="stat-label"><i class="ti ti-briefcase me-1"></i>Workable</div><div class="h3 mb-0 text-primary"><?= number_format($workableCount) ?></div>
            </div></div></a></div>
        <div class="col"><a href="<?= e($rurl) ?>" class="text-decoration-none" title="Charts a validator / auditor / supervisor / manager sent back to you for rework">
            <div class="card card-sm <?= $returned?'border-danger':'' ?>"><div class="card-body py-2 text-center">
                <div class="stat-label"><i class="ti ti-arrow-back-up me-1"></i>Sent back</div><div class="h3 mb-0 text-danger"><?= number_format($returnedCount) ?></div>
            </div></div></a></div>
        <?php foreach (['pending'=>'Open','in_progress'=>'In progress','hold'=>'On hold','completed'=>'Completed','rfi'=>'RFI','disregard'=>'Disregard'] as $st=>$lbl): ?>
        <div class="col"><a href="<?= e($qurl($st)) ?>" class="text-decoration-none">
            <div class="card card-sm <?= $fStatus===$st?'border-'.$statusBadge[$st]:'' ?>"><div class="card-body py-2 text-center">
                <div class="stat-label"><?= $lbl ?></div><div class="h3 mb-0 text-<?= $statusBadge[$st] ?>"><?= number_format($counts[$st]) ?></div>
            </div></div></a></div>
        <?php endforeach; ?>
    </div>

    <div class="card">
        <div class="card-header">
            <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.bootstrap5.min.css">
            <script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
            <style>.inv-filters>.col{min-width:150px}.inv-filters .ts-wrapper{min-width:0}</style>
            <form method="GET" class="row g-2 w-100 inv-filters">
                <input type="hidden" name="_f" value="1">
                <?php if ($all): ?><input type="hidden" name="view" value="all"><?php endif; ?>
                <?php if ($fStatus !== ''): ?><input type="hidden" name="status" value="<?= e($fStatus) ?>"><?php endif; ?>
                <?php if ($returned): ?><input type="hidden" name="returned" value="1"><?php endif; ?>
                <div class="col"><select name="client_id[]" multiple class="form-select form-select-sm inv-ms" data-placeholder="All clients">
                    <?php foreach ($clients as $o): ?><option value="<?= (int)$o['id'] ?>" <?= in_array((int)$o['id'],$fClients,true)?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?></select></div>
                <div class="col"><select name="process_id[]" multiple class="form-select form-select-sm inv-ms" data-placeholder="All processes">
                    <?php foreach ($procs as $o): ?><option value="<?= (int)$o['id'] ?>" <?= in_array((int)$o['id'],$fProcesses,true)?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?></select></div>
                <div class="col"><select name="facility_id[]" multiple class="form-select form-select-sm inv-ms" data-placeholder="All facilities">
                    <?php foreach ($facils as $o): ?><option value="<?= (int)$o['id'] ?>" <?= in_array((int)$o['id'],$fFacilities,true)?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?></select></div>
                <div class="col"><select name="provider_id[]" multiple class="form-select form-select-sm inv-ms" data-placeholder="All providers">
                    <?php foreach ($provs as $o): ?><option value="<?= (int)$o['id'] ?>" <?= in_array((int)$o['id'],$fProviders,true)?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?></select></div>
                <?php if ($all): ?><div class="col"><select name="coder_id[]" multiple class="form-select form-select-sm inv-ms" data-placeholder="All coders">
                    <?php foreach ($coders as $o): ?><option value="<?= (int)$o['id'] ?>" <?= in_array((int)$o['id'],$fCoders,true)?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?></select></div><?php endif; ?>
                <div class="col"><input type="search" name="patient" value="<?= e($fPatient) ?>" class="form-control form-control-sm" placeholder="Patient…" autocomplete="off"></div>
                <div class="col"><input type="search" name="mrn" value="<?= e($fMrn) ?>" class="form-control form-control-sm" placeholder="MRN/PRN…" autocomplete="off"></div>
                <div class="col-auto"><input type="date" name="dos_from" value="<?= e($fDosFrom) ?>" class="form-control form-control-sm" title="DOS from (date of service)"></div>
                <div class="col-auto"><input type="date" name="dos_to" value="<?= e($fDosTo) ?>" class="form-control form-control-sm" title="DOS to (date of service)"></div>
                <div class="col-auto"><input type="date" name="up_from" value="<?= e($fUpFrom) ?>" class="form-control form-control-sm" title="Uploaded from"></div>
                <div class="col-auto"><input type="date" name="up_to" value="<?= e($fUpTo) ?>" class="form-control form-control-sm" title="Uploaded to"></div>
                <?php /* Worked date = when the coder submitted the chart. Only matches on the coded queues
                         (Completed / RFI / Disregard) — a workable chart has no submitted_at yet. */ ?>
                <div class="col-auto"><input type="date" name="w_from" value="<?= e($fWkFrom) ?>" class="form-control form-control-sm" title="Worked (completed) from — coded queues only"></div>
                <div class="col-auto"><input type="date" name="w_to" value="<?= e($fWkTo) ?>" class="form-control form-control-sm" title="Worked (completed) to — coded queues only"></div>
                <div class="col-auto"><button class="btn btn-sm btn-primary"><i class="ti ti-filter"></i></button>
                    <?php $clearQ = array_filter(['view'=>$all?'all':null, 'status'=>$fStatus?:null, 'returned'=>$returned?1:null]) + ['clear'=>1]; ?>
                    <a href="<?= e(url('/admin/coding-inventory.php' . ($clearQ?('?'.http_build_query($clearQ)):''))) ?>" class="btn btn-sm btn-ghost-secondary" title="Clear client / process / facility / provider / patient filters (keep this queue)">Clear</a>
                    <?php if ($rows): ?><a href="<?= e(url('/admin/coding-inventory.php?' . http_build_query(array_merge($selfQ, ['export'=>'xlsx'])))) ?>" class="btn btn-sm btn-outline-success" title="Download the filtered inventory list as Excel"><i class="ti ti-file-spreadsheet me-1"></i>Export</a>
                    <a href="<?= e(url('/admin/coding-inventory.php?' . http_build_query(array_merge($selfQ, ['export'=>'detail'])))) ?>" class="btn btn-sm btn-outline-success" title="Download your charts WITH the coded lines (CPT / units / modifiers / ICDs)"><i class="ti ti-list-numbers me-1"></i>Export (with codes)</a>
                    <a class="btn btn-sm btn-outline-primary" data-bs-toggle="collapse" href="#invExport" title="Choose categories + date range and download the full 20-column report or a daily productivity workbook"><i class="ti ti-report-analytics me-1"></i>Report / productivity…</a><?php endif; ?></div>
            </form>
            <?php /* Full 20-column export (category checkboxes persist as your default) + daily productivity — a separate GET form so the selection is submitted; carries the current scope filters. */
                $expCats = $_SESSION['ci_exp_cat'] ?? array_keys($CAT_MAP); $expFrom = $_SESSION['ci_exp_from'] ?? date('Y-m-d'); $expTo = $_SESSION['ci_exp_to'] ?? date('Y-m-d'); ?>
            <div class="collapse mt-2" id="invExport">
                <form method="GET" action="<?= e(url('/admin/coding-inventory.php')) ?>" class="card card-body py-2 bg-primary-lt">
                    <?php if ($all): ?><input type="hidden" name="view" value="all"><?php endif; ?>
                    <?php foreach ($fClients as $cid): ?><input type="hidden" name="client_id[]" value="<?= (int)$cid ?>"><?php endforeach; ?>
                    <?php foreach ($fProcesses as $cid): ?><input type="hidden" name="process_id[]" value="<?= (int)$cid ?>"><?php endforeach; ?>
                    <?php foreach ($fFacilities as $cid): ?><input type="hidden" name="facility_id[]" value="<?= (int)$cid ?>"><?php endforeach; ?>
                    <?php foreach ($fProviders as $cid): ?><input type="hidden" name="provider_id[]" value="<?= (int)$cid ?>"><?php endforeach; ?>
                    <?php foreach ($fCoders as $cid): ?><input type="hidden" name="coder_id[]" value="<?= (int)$cid ?>"><?php endforeach; ?>
                    <div class="row g-2 align-items-end">
                        <div class="col-auto">
                            <div class="form-label small mb-1">Include categories <span class="text-secondary">(remembered)</span></div>
                            <?php foreach (['workable'=>'Workable','completed'=>'Completed','rfi'=>'RFI','disregard'=>'Disregard'] as $ck=>$lbl): ?>
                            <label class="form-check form-check-inline"><input class="form-check-input" type="checkbox" name="cat[]" value="<?= $ck ?>" <?= in_array($ck,$expCats,true)?'checked':'' ?>><span class="form-check-label small"><?= $lbl ?></span></label>
                            <?php endforeach; ?>
                        </div>
                        <div class="col-auto"><label class="form-label small mb-1">Uploaded from</label><input type="date" name="ex_from" value="<?= e($expFrom) ?>" class="form-control form-control-sm"></div>
                        <div class="col-auto"><label class="form-label small mb-1">to</label><input type="date" name="ex_to" value="<?= e($expTo) ?>" class="form-control form-control-sm"></div>
                        <div class="col-auto"><button name="export" value="full" class="btn btn-sm btn-success"><i class="ti ti-file-spreadsheet me-1"></i>Download full report</button></div>
                    </div>
                    <div class="row g-2 align-items-end mt-1 pt-2 border-top">
                        <div class="col-auto"><label class="form-label small mb-1">Daily productivity — date</label><input type="date" name="prod_date" value="<?= e(date('Y-m-d')) ?>" class="form-control form-control-sm"></div>
                        <div class="col-auto"><button name="export" value="productivity" class="btn btn-sm btn-outline-success"><i class="ti ti-report me-1"></i>Download daily productivity</button></div>
                        <div class="col text-secondary small align-self-center">Summary (Completed + RFI per coder) + detailed records in one workbook. Both exports honor the Client / Process / Facility / Provider<?= $all?' / Coder':'' ?> filters selected above.</div>
                    </div>
                </form>
            </div>
            <script>(function(){ if (typeof TomSelect === 'undefined') return;
                document.querySelectorAll('.inv-ms').forEach(function(sel){ new TomSelect(sel, { plugins:['remove_button','clear_button'], placeholder: sel.dataset.placeholder||'', maxOptions:5000, hidePlaceholder:false, persist:false }); }); })();</script>
        </div>

        <form method="POST" id="invbulk" action="<?= e(url('/admin/coding-inventory.php' . ($selfQ ? ('?' . http_build_query($selfQ)) : ''))) ?>">
            <?= csrf_field() ?>

            <?php if ($canPrio && $rows): /* TOP-priority bar — shown in BOTH the personal and all views */ ?>
            <div class="card-body py-2 border-bottom d-flex flex-wrap gap-2 align-items-center">
                <span class="badge bg-red-lt"><i class="ti ti-arrow-up me-1"></i>Priority</span>
                <span class="text-secondary small">Tick <strong>workable</strong> chart(s) → <strong>Set TOP</strong>. <em>Get next</em> always serves TOP charts first, one after another; once none are left it resumes the normal oldest-first (aging) order. The flag clears itself when a chart is completed.</span>
                <button type="submit" name="action" value="set_priority" class="btn btn-sm btn-red ms-auto"
                    onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.');return false;} return confirm('Mark the selected chart(s) TOP priority? They will be coded before all other charts.');"><i class="ti ti-arrow-up me-1"></i>Set TOP priority</button>
                <button type="submit" name="action" value="clear_priority" class="btn btn-sm btn-outline-secondary"
                    onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.');return false;} return confirm('Clear the TOP priority flag on the selected chart(s)?');"><i class="ti ti-arrow-down me-1"></i>Clear priority</button>
            </div>
            <?php endif; ?>

            <?php if ($canRetag && $rows): /* re-tag bar — only when there are charts to act on */ ?>
            <div class="card-body py-2 border-bottom border-top">
                <div class="row g-2 align-items-end">
                    <div class="col-12 col-xl-3">
                        <span class="badge bg-azure-lt"><i class="ti ti-edit me-1"></i>Re-tag selected</span>
                        <div class="text-secondary mt-1" style="font-size:.72rem">Uploaded against the wrong client&nbsp;»&nbsp;process&nbsp;»&nbsp;facility&nbsp;»&nbsp;provider? Tick the chart(s) below, choose the correct one, then <strong>Re-tag</strong>.</div>
                    </div>
                    <div class="col-6 col-md-3 col-xl"><label class="form-label mb-1 small">Client</label><select name="rt_client_id" id="rt_c" class="form-select form-select-sm"><option value="">— client —</option></select></div>
                    <div class="col-6 col-md-3 col-xl"><label class="form-label mb-1 small">Process</label><select name="rt_process_id" id="rt_p" class="form-select form-select-sm"><option value="">— process —</option></select></div>
                    <div class="col-6 col-md-3 col-xl"><label class="form-label mb-1 small">Facility</label><select name="rt_facility_id" id="rt_f" class="form-select form-select-sm"><option value="">— facility —</option></select></div>
                    <div class="col-6 col-md-3 col-xl"><label class="form-label mb-1 small">Provider</label><select name="rt_provider_id" id="rt_pr" class="form-select form-select-sm"><option value="">— provider —</option></select></div>
                    <div class="col-12 col-md-auto"><button type="submit" name="action" value="retag" class="btn btn-sm btn-azure w-100"
                        onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.');return false;} if(!document.getElementById('rt_pr').value){alert('Pick client, process, facility and provider.');return false;} return confirm('Re-tag the selected chart(s) to the chosen client / process / facility / provider?');"><i class="ti ti-edit me-1"></i>Re-tag</button></div>
                </div>
            </div>
            <?php /* Gated on $canReassign, NOT $canDelete: this block holds BOTH Reassign and Delete, and
                     $canDelete narrowed to Manager-only when the delete was re-gated — leaving it here would
                     have silently stripped Reassign from every supervisor. The Delete button inside carries
                     its own $canDelete check. */ ?>
            <?php elseif ($canReassign && $rows): /* all-view (supervisor/manager): reassign + (manager) delete */ ?>
            <div class="card-body py-2 border-bottom border-top d-flex flex-wrap gap-2 align-items-center">
                <span class="badge bg-azure-lt"><i class="ti ti-arrows-exchange me-1"></i>Reassign</span>
                <span class="text-secondary small">Tick <strong>workable</strong> chart(s), pick a coder, then Reassign — the chart moves to that coder's open queue.</span>
                <select name="to_coder" class="form-select form-select-sm w-auto"><option value="">reassign to…</option>
                    <?php foreach ($teamCoders as $o): ?><option value="<?= (int)$o['id'] ?>"><?= e($o['name']) ?></option><?php endforeach; ?></select>
                <button type="submit" name="action" value="reassign" class="btn btn-sm btn-azure"
                    onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.');return false;} if(!this.form.to_coder.value){alert('Pick a coder to reassign to.');return false;} return confirm('Reassign the selected chart(s) to the chosen coder?');"><i class="ti ti-user-share me-1"></i>Reassign</button>
                <?php if ($canDelete): ?>
                <button type="submit" name="action" value="delete" class="btn btn-sm btn-outline-danger ms-auto"
                    onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.');return false;} return confirm('Permanently delete the selected chart(s)? Only never-coded charts are removed — coded charts are protected. This cannot be undone (a delete log is kept).');"><i class="ti ti-trash me-1"></i>Delete selected</button>
                <?php endif; ?>
            </div>
            <?php endif; ?>

            <?php /* Deletion request — available to anyone who can request, in BOTH the personal and all views,
                     so a coder can raise one from their own queue (which is where they notice junk). */ ?>
            <?php if ($canReqDel && $rows): ?>
            <?php /* Only WORKABLE rows get a checkbox ($pick, below), so a view filtered to rfi / disregard /
                     completed offers nothing to tick. Say so here rather than let the user hunt for a checkbox
                     that was never rendered and then hit a bare "Tick at least one chart first". */ ?>
            <?php $anyPick = false; foreach ($rows as $_r) { if (in_array($_r['status'], $WORKABLE, true)) { $anyPick = true; break; } } ?>
            <div class="card-body py-2 border-bottom d-flex flex-wrap gap-2 align-items-center">
                <span class="badge bg-red-lt"><i class="ti ti-trash-x me-1"></i>Request deletion</span>
                <?php if ($anyPick): ?>
                <span class="text-secondary small">Chart that should never go to Billing? Tick it, give a reason, then request —
                    a Team Lead reviews and a Manager deletes. Nothing is deleted by this button.</span>
                <input type="text" name="del_reason" maxlength="2000" class="form-control form-control-sm w-auto flex-grow-1"
                       placeholder="Reason (required) — e.g. duplicate of chart #1234, test record, wrong patient">
                <button type="submit" name="action" value="request_deletion" class="btn btn-sm btn-outline-danger"
                    onclick="if(!document.querySelector('.invchk:checked')){alert('Tick at least one chart first.\n\nOnly workable charts (open / in progress / on hold) can be ticked — RFI, Disregard and Completed charts are not eligible for a deletion request.');return false;} if(!this.form.del_reason.value.trim()){alert('A reason is required — the Team Lead and Manager have nothing else to judge by.');return false;} return confirm('Request deletion of the selected chart(s)? They leave your queue and wait for review.');"><i class="ti ti-send me-1"></i>Request deletion</button>
                <?php else: ?>
                <span class="text-secondary small">Nothing here can be requested — only <strong>workable</strong> charts
                    (open / in progress / on hold) are eligible. This view is filtered to charts that are already
                    completed, RFI or disregarded. Clear the <strong>status</strong> filter to find workable ones.</span>
                <?php endif; ?>
            </div>
            <?php endif; ?>

            <div class="table-responsive"><table class="table table-vcenter card-table" data-coltweak="coding-inv">
                <thead><tr>
                    <?php if ($bulk): ?><th class="w-1" data-colfixed><input type="checkbox" id="invchkall" class="form-check-input m-0" title="Select all"></th><?php endif; ?>
                    <th data-col="priority" data-collabel="Priority"></th><?= $sortTh('patient','Patient') ?><?= $sortTh('mrn','MRN/PRN') ?><?= $sortTh('dob','DOB') ?><?= $sortTh('dos','DOS') ?><?= $sortTh('rsd','Record sign') ?>
                    <?= $sortTh('client','Client') ?><?= $sortTh('facility','Facility') ?><?= $sortTh('provider','Provider') ?>
                    <?php if ($all): ?><?= $sortTh('coder','Coder') ?><?php endif; ?>
                    <?php if ($codesView): ?><th data-col="cpt">CPT</th><th data-col="icd">ICD(s)</th><?php endif; ?>
                    <?= $sortTh('uploaded','Uploaded') ?><?= $sortTh('completed','Completed') ?><?php if ($returned): ?><th data-col="sentback">Sent back</th><?php endif; ?><?= $sortTh('status','Status') ?><th class="w-1" data-colfixed></th>
                </tr></thead>
                <tbody>
                <?php if (!$rows): ?>
                    <tr><td colspan="<?= ($all?14:13) + ($returned?1:0) + ($bulk?1:0) + ($codesView?2:0) ?>" class="text-center text-secondary py-4"><?= $returned ? 'Nothing sent back — your rework queue is clear. 🎉' : 'No charts in this queue.' ?></td></tr>
                <?php else: foreach ($rows as $r):
                    // A chart is tickable when WORKABLE (open / in-progress / on-hold): the coder can re-tag it and a
                    // supervisor can reassign it. DELETE is additionally guarded server-side to NEVER-coded charts only
                    // (never returned, never submitted, no saved CPT lines), so the shared checkbox is safe — a coded
                    // chart ticked for delete is simply skipped server-side.
                    $pick = in_array($r['status'], $WORKABLE, true); ?>
                    <tr>
                        <?php if ($bulk): ?><td><?php if ($pick): ?><input type="checkbox" name="chart_ids[]" value="<?= (int)$r['id'] ?>" class="form-check-input m-0 invchk" form="invbulk"><?php endif; ?></td><?php endif; ?>
                        <td><?= (int)$r['is_priority'] ? '<span class="badge bg-red" title="Top priority"><i class="ti ti-arrow-up"></i> TOP</span>' : '' ?></td>
                        <td><strong><?= e($r['patient_name']) ?></strong></td>
                        <td class="small"><?= e($r['patient_mrn'] ?? '—') ?></td>
                        <td class="small"><?= e(fmt_date($r['patient_dob'])) ?></td>
                        <td class="small text-nowrap"><?= $r['dos'] ? e(fmt_date($r['dos'])) : '<span class="text-secondary">—</span>' ?></td>
                        <td class="small"><?= e(fmt_date($r['record_sign_date'])) ?></td>
                        <td class="small text-secondary"><?= e($r['cn']) ?></td>
                        <td class="small"><?= e($r['fn']) ?></td>
                        <td class="small"><?= e($r['prn']) ?></td>
                        <?php if ($all): ?><td class="small"><?= e($r['coder'] ?? '—') ?></td><?php endif; ?>
                        <?php if ($codesView): $lns = $linesByInv[(int)$r['id']] ?? []; ?>
                        <td class="small"><?= $lns ? implode('<br>', array_map(fn($l) => e($l['cpt'] ?? ''), $lns)) : '<span class="text-secondary">—</span>' ?></td>
                        <td class="small"><?= $lns ? implode('<br>', array_map(fn($l) => e(implode(', ', $l['icds'] ?? [])) ?: '—', $lns)) : '<span class="text-secondary">—</span>' ?></td>
                        <?php endif; ?>
                        <td class="small text-secondary text-nowrap"><?= e(fmt_dt($r['uploaded_at'])) ?></td>
                        <td class="small text-secondary text-nowrap"><?= !empty($r['submitted_at']) ? e(fmt_dt($r['submitted_at'])) : '<span class="text-secondary">—</span>' ?></td>
                        <?php if ($returned): ?><td class="small"><?= e($r['rtoname'] ?? '—') ?><?php if (!empty($r['rto_at'])): ?><div class="text-secondary text-nowrap" style="font-size:.7rem"><?= e(fmt_dt($r['rto_at'])) ?></div><?php endif; ?></td><?php endif; ?>
                        <td><span class="badge bg-<?= $statusBadge[$r['status']] ?>-lt"><?= e(str_replace('_',' ',$r['status'])) ?></span></td>
                        <td class="text-nowrap">
                            <a href="<?= e(url('/admin/coding-chart-view.php?id='.(int)$r['id'].'&back='.rawurlencode($backUrl))) ?>" class="btn btn-sm btn-outline-secondary" title="View chart details (read-only)"><i class="ti ti-eye me-1"></i>View</a>
                            <?php if (!$all && in_array($r['status'], ['in_progress','hold'], true)): ?><a href="<?= e(url('/admin/coding-chart.php?id='.(int)$r['id'])) ?>" class="btn btn-sm btn-primary"><i class="ti ti-player-play me-1"></i>Resume</a><?php endif; ?>
                        </td>
                    </tr>
                <?php endforeach; endif; ?>
                </tbody>
            </table></div>
        </form>
        <?php if (count($rows) >= 1000): ?><div class="card-footer text-secondary small">Showing first 1000 — use filters to narrow.</div><?php endif; ?>
    </div>
</div></div>
<?php if ($bulk): ?>
<script>
(function(){
    var a=document.getElementById('invchkall');
    if(a) a.addEventListener('change',function(){ document.querySelectorAll('.invchk').forEach(function(c){ c.checked=a.checked; }); });
    <?php if ($canRetag && $rows): ?>
    var TREE = <?= json_encode($retagTree, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_HEX_APOS) ?>;
    var elC=document.getElementById('rt_c'), elP=document.getElementById('rt_p'), elF=document.getElementById('rt_f'), elPr=document.getElementById('rt_pr');
    function opt(v,t){ var o=document.createElement('option'); o.value=v; o.textContent=t; return o; }
    function fill(el, map, ph){ el.innerHTML=''; el.appendChild(opt('', ph));
        Object.keys(map||{}).sort(function(x,y){ return (map[x].name||map[x]).localeCompare(map[y].name||map[y]); })
            .forEach(function(k){ el.appendChild(opt(k, map[k].name||map[k])); }); }
    Object.keys(TREE).sort(function(x,y){ return TREE[x].name.localeCompare(TREE[y].name); }).forEach(function(k){ elC.appendChild(opt(k, TREE[k].name)); });
    function reset(lvl){ if(lvl<=1){ elP.innerHTML='<option value="">— process —</option>'; } if(lvl<=2){ elF.innerHTML='<option value="">— facility —</option>'; } if(lvl<=3){ elPr.innerHTML='<option value="">— provider —</option>'; } }
    elC.onchange=function(){ reset(1); var c=TREE[elC.value]; if(c){ fill(elP, c.processes, '— process —'); } };
    elP.onchange=function(){ reset(2); var c=TREE[elC.value]; if(c && c.processes[elP.value]){ fill(elF, c.processes[elP.value].facilities, '— facility —'); } };
    elF.onchange=function(){ reset(3); var c=TREE[elC.value]; if(c && c.processes[elP.value] && c.processes[elP.value].facilities[elF.value]){ fill(elPr, c.processes[elP.value].facilities[elF.value].providers, '— provider —'); } };
    <?php endif; ?>
})();
</script>
<?php endif; ?>

<?php if (!$all && can('coding.work')): /* ── Add-new-chart modal: pick c»p»f»billing-provider → create a manual chart and start coding ── */ ?>
<div class="modal fade" id="newChartModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-dialog-centered"><div class="modal-content">
    <form method="POST" action="<?= e(url('/admin/coding-chart.php')) ?>">
        <?= csrf_field() ?><input type="hidden" name="action" value="new_manual_chart">
        <div class="modal-header"><h5 class="modal-title"><i class="ti ti-file-plus me-2 text-success"></i>Add new chart</h5><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button></div>
        <div class="modal-body">
            <p class="text-secondary small mb-3">For clients whose inventory isn't uploaded — pick the client » process » facility » billing provider, then start coding. After each chart you can <strong>add another</strong> with the same selection until you Submit &amp; exit the queue.</p>
            <div class="mb-2"><label class="form-label required">Client</label><select name="client_id" id="nc_c" class="form-select" required><option value="">— client —</option></select></div>
            <div class="mb-2"><label class="form-label required">Process</label><select name="process_id" id="nc_p" class="form-select" required><option value="">— process —</option></select></div>
            <div class="mb-2"><label class="form-label required">Facility</label><select name="facility_id" id="nc_f" class="form-select" required><option value="">— facility —</option></select></div>
            <div class="mb-2"><label class="form-label required">Billing provider</label><select name="provider_id" id="nc_pr" class="form-select" required><option value="">— provider —</option></select></div>
        </div>
        <div class="modal-footer"><button type="button" class="btn btn-link link-secondary" data-bs-dismiss="modal">Cancel</button>
            <button type="submit" class="btn btn-success"><i class="ti ti-player-play me-1"></i>Start coding</button></div>
    </form>
</div></div></div>
<script>
(function(){
    var TREE = <?= json_encode($newChartTree, JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_HEX_APOS) ?>;
    var elC=document.getElementById('nc_c'), elP=document.getElementById('nc_p'), elF=document.getElementById('nc_f'), elPr=document.getElementById('nc_pr');
    if(!elC) return;
    function opt(v,t){ var o=document.createElement('option'); o.value=v; o.textContent=t; return o; }
    function fill(el, map, ph){ el.innerHTML=''; el.appendChild(opt('', ph));
        Object.keys(map||{}).sort(function(x,y){ return (map[x].name||map[x]).localeCompare(map[y].name||map[y]); })
            .forEach(function(k){ el.appendChild(opt(k, map[k].name||map[k])); }); }
    Object.keys(TREE).sort(function(x,y){ return TREE[x].name.localeCompare(TREE[y].name); }).forEach(function(k){ elC.appendChild(opt(k, TREE[k].name)); });
    function reset(lvl){ if(lvl<=1){ elP.innerHTML='<option value="">— process —</option>'; } if(lvl<=2){ elF.innerHTML='<option value="">— facility —</option>'; } if(lvl<=3){ elPr.innerHTML='<option value="">— provider —</option>'; } }
    elC.onchange=function(){ reset(1); var c=TREE[elC.value]; if(c){ fill(elP, c.processes, '— process —'); } };
    elP.onchange=function(){ reset(2); var c=TREE[elC.value]; if(c && c.processes[elP.value]){ fill(elF, c.processes[elP.value].facilities, '— facility —'); } };
    elF.onchange=function(){ reset(3); var c=TREE[elC.value]; if(c && c.processes[elP.value] && c.processes[elP.value].facilities[elF.value]){ fill(elPr, c.processes[elP.value].facilities[elF.value].providers, '— provider —'); } };
    // Open the modal explicitly (don't rely on Bootstrap's data-api delegation, which isn't always active here).
    var openBtn=document.querySelector('[data-bs-target="#newChartModal"]');
    if(openBtn && window.bootstrap && window.bootstrap.Modal){ openBtn.addEventListener('click', function(){ window.bootstrap.Modal.getOrCreateInstance(document.getElementById('newChartModal')).show(); }); }
})();
</script>
<?php endif; ?>
<?php require __DIR__ . '/../includes/footer.php'; ?>
