<?php
/**
 * Deliverables (coding.supervise OR coding.audit) — one place to pull the chart LISTS for each
 * downstream handoff, with Excel export:
 *   ?view=billing   → charts released to the billing team (bill insurance)
 *   ?view=rfi       → RFI charts  → go to the CLIENT for clarification
 *   ?view=disregard → Disregard charts → go to the CLIENT (not codable)
 * Team-scoped: a supervisor sees their team's charts, a manager their supervisors' teams, sysadmin all.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$view = in_array($_GET['view'] ?? '', ['torelease','billing','vendor','rfi','disregard'], true) ? $_GET['view'] : 'torelease';
$isReleased = in_array($view, ['billing','vendor'], true);   // both show already-released charts (internal billing vs external vendor)
remember_filters('coding_deliv_' . $view, ['client_id','process_id','facility_id','provider_id','coder_id','from','to','patient','mrn','gender','cpt','icd','cstatus','audit']);   // per-session filter memory (per-view so filters don't bleed between tabs)

/* Final disposition: an auditor's RFI/Disregard verdict wins, else the coder's status. */
$dispo = "(CASE WHEN ci.audit_status IN ('rfi','disregard') THEN ci.audit_status ELSE ci.status END)";
/* "To release" = completed, not RFI/Disregard, NOT yet released to billing (audit optional). */
$TORELEASE = "ci.status='completed' AND COALESCE(ci.audit_status,'') NOT IN ('rfi','disregard') AND ci.released_to_billing_at IS NULL";

/* Visibility scope:
 *   - Sysadmin (team === null) → all charts.
 *   - Supervisor / manager (can coding.supervise) → their reporting team's charts (by coder), PLUS anything they
 *     personally audited or released.
 *   - Auditor (can coding.audit but NOT supervise) → every chart of the CLIENT(s) they are ASSIGNED to
 *     (coding_user_scope alignment), AUDITED OR NOT, plus anything they personally audited/released.
 *     WHY (2026-07-16): this used to be `auditor_id=me OR released_by=me` alone — "only what I personally
 *     audited". A chart that was deliberately NOT audited has auditor_id IS NULL, so it was invisible here and
 *     the auditor could never release it to billing without a supervisor stepping in. Scoping by ASSIGNMENT
 *     instead surfaces audited + not-audited together, while hiding every client NOT assigned to them.
 *     Assign clients on admin/user-alignment.php (or coding-access.php) — an auditor with NO alignment rows sees
 *     only their own past work here (and nothing at all in the audit queue, which is fail-closed the same way).
 *   - Anyone else who reaches this page (bare `page.coding-deliverables` grant, e.g. a CODER) → unchanged: only
 *     what they personally audited/released. NEVER the alignment branch — see the note on the else below. */
$team = coding_team_scope($pdo, $uid);
if ($team === null) {
    $teamSql = '';
} elseif (can('coding.supervise')) {
    $teamSql = ' AND (ci.coder_id IN (' . implode(',', $team ?: [0]) . ') OR ci.auditor_id=' . $uid . ' OR ci.released_by=' . $uid . ')';
} elseif (can('coding.audit')) {
    // CLIENT-ONLY, deliberately: coding-audit.php fences the audit QUEUES by assigned client only (it ignores
    // process). If this page fenced by client+process, an auditor with a process-level alignment could audit a
    // chart and then not see it here to release — reintroducing the exact bug this change fixes. The two pages
    // must agree on what a chart's auditor owns, so both stay client-level.
    // The OR keeps their OWN past work visible after a realignment (charts they personally audited/released),
    // so history doesn't vanish from the "Sent to billing" / vendor tabs.
    $teamSql = ' AND (EXISTS (SELECT 1 FROM coding_user_scope s WHERE s.user_id=' . $uid
             . ' AND ' . coding_vertical_scope_sql('s.vertical_id')
             . ' AND s.client_id=ci.client_id) OR ci.auditor_id=' . $uid . ' OR ci.released_by=' . $uid . ')';
} else {
    // MUST stay gated on coding.audit above — this page is grantable (medmecs_grantable_pages), so a user holding
    // only a bare `page.coding-deliverables` grant reaches it WITHOUT supervise/audit. A CODER is exactly that:
    // coding.work + real coding_user_scope alignment rows. Letting them fall into the alignment branch would show
    // every completed chart of their whole client — other coders' charts, full PHI + the Excel export. Non-auditors
    // keep the old personal fence (only what they themselves audited/released), which matches nothing for a coder.
    $teamSql = ' AND (ci.auditor_id=' . $uid . ' OR ci.released_by=' . $uid . ')';
}
$canMark = can('coding.supervise') || can('coding.audit');   // who may mark RFI/Disregard sent to client

/* POST: mark RFI/Disregard chart(s) as SENT TO CLIENT (multi-select). Only RFI/Disregard, idempotent. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'mark_sent_client') {
    csrf_check_or_die();
    $bv = in_array($_POST['view'] ?? '', ['rfi','disregard'], true) ? $_POST['view'] : 'rfi';
    if (!$canMark) { flash('error', 'You are not allowed to mark client delivery.'); redirect('/admin/coding-deliverables.php?view=' . $bv); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['ids'] ?? []))));
    if (!$ids) { flash('error', 'Select at least one chart.'); redirect('/admin/coding-deliverables.php?view=' . $bv); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    // $teamSql applied here too (2026-07-16): this handler also trusted the posted ids without a scope fence.
    $sel = $pdo->prepare("SELECT ci.id FROM coding_inventory ci
        WHERE ci.id IN ($in)
          AND (CASE WHEN ci.audit_status IN ('rfi','disregard') THEN ci.audit_status ELSE ci.status END) IN ('rfi','disregard')
          AND ci.client_sent_at IS NULL" . $teamSql);
    $sel->execute($ids);
    $elig = array_map('intval', $sel->fetchAll(PDO::FETCH_COLUMN));
    $n = 0;
    if ($elig) {
        $inE = implode(',', array_fill(0, count($elig), '?'));
        $st = $pdo->prepare("UPDATE coding_inventory SET client_sent_at=NOW(), client_sent_by=?, updated_at=NOW() WHERE id IN ($inE)");
        $st->execute(array_merge([$uid], $elig)); $n = $st->rowCount();
        foreach ($elig as $eid) coding_log_event($pdo, $eid, 'client_sent', $uid, null, null, 'sent');
        audit_log('coding.client_sent', 'coding_inventory', null, null, ['ids'=>$elig]);
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'error', $n . ' chart(s) marked sent to client.' . ($skip ? " {$skip} skipped (already sent / not RFI-Disregard)." : ''));
    redirect('/admin/coding-deliverables.php?view=' . $bv);
}

/* POST: SEND an RFI/Disregard chart BACK TO THE CODER once the client's clarification is in — the chart returns to
 * the coder's OPEN queue at TOP priority (the "sent back to me" bucket) to be reworked. Mirrors the audit RTO reset:
 * disposition + audit + client-sent state cleared, rto_at stamped. Team-scoped + idempotent. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'send_back_coder') {
    csrf_check_or_die();
    $bv = in_array($_POST['view'] ?? '', ['rfi','disregard'], true) ? $_POST['view'] : 'rfi';
    if (!$canMark) { flash('error', 'You are not allowed to send charts back to the coder.'); redirect('/admin/coding-deliverables.php?view=' . $bv); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['ids'] ?? []))));
    if (!$ids) { flash('error', 'Select at least one chart.'); redirect('/admin/coding-deliverables.php?view=' . $bv); }
    $note = trim(mb_substr((string) ($_POST['clarify_note'] ?? ''), 0, 1000));
    $in = implode(',', array_fill(0, count($ids), '?'));
    // Eligible = still RFI/Disregard (by disposition), not released to billing, and WITHIN the actor's scope ($teamSql).
    $sel = $pdo->prepare("SELECT ci.id, COALESCE(ci.coder_id, ci.assigned_to) AS coder, ci.patient_name
        FROM coding_inventory ci
        WHERE ci.id IN ($in)
          AND (CASE WHEN ci.audit_status IN ('rfi','disregard') THEN ci.audit_status ELSE ci.status END) IN ('rfi','disregard')
          AND ci.released_to_billing_at IS NULL" . $teamSql);
    $sel->execute($ids);
    $rows = $sel->fetchAll();
    $n = 0;
    if ($rows) {
        // Same reset as the audit RTO (coding-rto.php / coding-audit.php) PLUS clearing the client-sent state.
        $upd = $pdo->prepare("UPDATE coding_inventory SET status='pending', assigned_to=?, is_priority=1, priority_by=?, priority_at=NOW(),
                started_at=NULL, submitted_at=NULL, in_sample=0, sample_pct=NULL,
                audit_status=NULL, audit_result=NULL, audit_opportunities=NULL, audit_defects=NULL,
                audit_comment=NULL, audit_secondary_reason=NULL, auditor_id=NULL, audited_at=NULL,
                rebuttal_status='none', client_sent_at=NULL, client_sent_by=NULL,
                rto_at=NOW(), rto_by=?, updated_at=NOW() WHERE id=?");
        $delAud = $pdo->prepare("DELETE FROM coding_audit_codes WHERE inventory_id=?");
        foreach ($rows as $r) {
            $coder = (int) $r['coder']; $rid = (int) $r['id'];
            if (!$coder) continue;   // no coder to return it to — skip
            $pdo->beginTransaction();
            $upd->execute([$coder, $uid, $uid, $rid]);
            $delAud->execute([$rid]);
            // coding_log_event('rto') ALSO notifies the coder centrally (its $NOTIFY dispatcher → assigned_to, with this
            // note appended) — so no separate notify() here (that would double-notify), matching coding-rto.php.
            coding_log_event($pdo, $rid, 'rto', $uid, ($note !== '' ? 'Client clarification: ' . $note : 'Client clarification received — reworked'), null, 'pending');
            $pdo->commit();
            $n++;
        }
        audit_log('coding.rto.client_clarified', 'coding_inventory', null, null, ['ids' => array_column($rows, 'id'), 'note' => $note]);
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'error', $n . ' chart(s) sent back to the coder’s open queue at top priority.' . ($skip ? " {$skip} skipped (not RFI/Disregard, released, out of scope, or no coder)." : ''));
    redirect('/admin/coding-deliverables.php?view=' . $bv);
}

/* POST: SEND completed chart(s) to the billing team (multi-select) — moves them To release → Sent to billing.
 * Eligible = coder-completed, NOT RFI/Disregard, not already released (audit optional — an intentionally
 * UNAUDITED chart is releasable, which is the whole point of this queue). Idempotent.
 * SCOPE (2026-07-16): $teamSql is now applied here too — this handler previously trusted the posted ids with NO
 * scope fence, so a forged id could release a chart outside the actor's team / client assignment. The list has
 * always been fenced, so this only blocks forgery; it changes nothing a user could legitimately do from the UI. */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'release_to_billing') {
    csrf_check_or_die();
    if (!can('coding.release_billing')) { flash('error', 'You are not allowed to release charts to billing.'); redirect('/admin/coding-deliverables.php?view=torelease'); }
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['ids'] ?? []))));
    if (!$ids) { flash('error', 'Select at least one chart to send to billing.'); redirect('/admin/coding-deliverables.php?view=torelease'); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    $sel = $pdo->prepare("SELECT ci.id FROM coding_inventory ci
        WHERE ci.id IN ($in) AND ci.status='completed' AND COALESCE(ci.audit_status,'') NOT IN ('rfi','disregard')
          AND ci.released_to_billing_at IS NULL" . $teamSql);
    $sel->execute($ids);
    $elig = array_map('intval', $sel->fetchAll(PDO::FETCH_COLUMN));
    $n = 0;
    if ($elig) {
        $inE = implode(',', array_fill(0, count($elig), '?'));
        // Stamp the destination from the chart's (billing→coded→uploaded) provider's route: internal charts feed
        // the biller queue, external charts go to the External-vendor tab (excluded from internal billing).
        // Movement date: if the provider has one and the chart's DOS is BEFORE it, the provider was external at
        // that DOS → route the chart to the vendor even though the provider is now internal.
        $st = $pdo->prepare("UPDATE coding_inventory ci
            JOIN coding_providers pr ON pr.id = COALESCE(ci.billing_provider_id, ci.coded_provider_id, ci.provider_id)
            SET ci.released_to_billing_at=NOW(), ci.released_by=?, ci.billing_status='pending',
                ci.release_destination = CASE
                    WHEN pr.movement_date IS NOT NULL AND ci.dos IS NOT NULL AND ci.dos < pr.movement_date THEN 'external'
                    ELSE pr.billing_route
                END,
                ci.updated_at=NOW()
            WHERE ci.id IN ($inE)");
        $st->execute(array_merge([$uid], $elig)); $n = $st->rowCount();
        foreach ($elig as $eid) coding_log_event($pdo, $eid, 'release_billing', $uid, null, 'completed', 'billing_pending');
        audit_log('coding.release_billing', 'coding_inventory', null, null, ['ids'=>$elig]);
    }
    $skip = count($ids) - $n;
    flash($n ? 'success' : 'error', $n . ' chart(s) released (internal billing / external vendor per provider routing).' . ($skip ? " {$skip} skipped (RFI / Disregard / already sent)." : ''));
    redirect('/admin/coding-deliverables.php?view=torelease');
}

/* Filters (multi-select — arrays of ids matched with IN (…)). */
$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']   ?? []);
$fProcesses  = $intList($_GET['process_id']  ?? []);
$fFacilities = $intList($_GET['facility_id'] ?? []);
$fProviders  = $intList($_GET['provider_id'] ?? []);
$fCoders     = $intList($_GET['coder_id']    ?? []);

/* Filter-option dropdowns CASCADE from the selected client(s) → process(es) → facility(ies), so you never see
   facilities / providers that belong to another client (the "02 Other-State shows out-of-state facilities" bug).
   Built BEFORE the WHERE so we can INTERSECT each chosen id-set with what is actually available in scope — a
   provider/coder left selected under a now-unselected client is dropped instead of silently emptying the list. */
$inCsv  = static fn(array $a): string => implode(',', $a);
$cScope = $fClients   ? (' WHERE ci.client_id IN ('  . $inCsv($fClients)    . ')') : '';
$pScope = $cScope . ($fProcesses  ? (($cScope ? ' AND' : ' WHERE') . ' ci.process_id IN ('  . $inCsv($fProcesses)  . ')') : '');
$fScope = $pScope . ($fFacilities ? (($pScope ? ' AND' : ' WHERE') . ' ci.facility_id IN (' . $inCsv($fFacilities) . ')') : '');
// SECURITY: fence the filter-option lists to the SAME team scope as the chart list ($teamSql), else the dropdowns
// enumerate every client / process / facility / provider / coder across ALL teams & verticals to anyone who can open
// the page (e.g. a per-page grant). $teamSql is '' for SysAdmin and int-inlined otherwise (safe).
$scopeTeam = fn(string $scope): string => $scope . ($teamSql === '' ? '' : ($scope === '' ? ' WHERE 1=1' : '') . $teamSql);
$clients   = $pdo->query("SELECT DISTINCT c.id,c.name FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id" . $scopeTeam('') . " ORDER BY c.name")->fetchAll();
$procsOpt  = $pdo->query("SELECT DISTINCT p.id,p.name FROM coding_inventory ci JOIN processes p ON p.id=ci.process_id" . $scopeTeam($cScope) . " 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" . $scopeTeam($pScope) . " 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=COALESCE(ci.coded_provider_id,ci.provider_id)" . $scopeTeam($fScope) . " ORDER BY pr.name")->fetchAll();
$codersOpt = $pdo->query("SELECT DISTINCT u.id,u.name FROM coding_inventory ci JOIN users u ON u.id=ci.coder_id WHERE ci.coder_id IS NOT NULL" . ($fClients ? ' AND ci.client_id IN (' . $inCsv($fClients) . ')' : '') . $teamSql . " ORDER BY u.name")->fetchAll();
/* Keep only chosen ids that survive the cascade scope → the WHERE never filters by an id the user cannot see. */
$fClients    = array_values(array_intersect($fClients,    array_map('intval', array_column($clients,   'id'))));
$fProcesses  = array_values(array_intersect($fProcesses,  array_map('intval', array_column($procsOpt,  'id'))));
$fFacilities = array_values(array_intersect($fFacilities, array_map('intval', array_column($facils,    'id'))));
$fProviders  = array_values(array_intersect($fProviders,  array_map('intval', array_column($provs,     'id'))));
$fCoders     = array_values(array_intersect($fCoders,     array_map('intval', array_column($codersOpt, 'id'))));
$from = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['from'] ?? '')) ? $_GET['from'] : '';
$to   = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['to'] ?? '')) ? $_GET['to'] : '';
/* Per-header text/select filters (F3). CPT/ICD match the coder's saved codes on the chart. */
$fPatient = trim((string) ($_GET['patient'] ?? ''));
$fMrn     = trim((string) ($_GET['mrn'] ?? ''));
$fGender  = in_array($_GET['gender'] ?? '', ['male','female','other','unknown'], true) ? (string) $_GET['gender'] : '';
$fCpt     = strtoupper(trim((string) ($_GET['cpt'] ?? '')));
$fIcd     = strtoupper(trim((string) ($_GET['icd'] ?? '')));
$fAudit   = in_array($_GET['audit'] ?? '', ['audited','inprogress','not'], true) ? (string) $_GET['audit'] : '';   // audit status: audited / auditing / not audited

/* The date axis is PER VIEW — each tab's "when did this arrive here" is a different stamp. Extracted to a
 * function because the badge counts below need each OTHER view's axis, not the current one's. */
$dateColFor = function (string $v): string {
    if ($v === 'billing' || $v === 'vendor') return 'ci.released_to_billing_at';               // when it was released
    if ($v === 'torelease')                  return 'COALESCE(ci.submitted_at, ci.updated_at)'; // when coding finished
    return 'COALESCE(ci.audited_at, ci.submitted_at, ci.updated_at)';                          // when it was tagged
};
$dateCol = $dateColFor($view);

/* DEFAULT VIEW = today, on that same per-view axis — the page already had the right date column wired to
 * from/to, so this defaults the existing filter rather than bolting a second window beside it.
 * Precedence, on VALUES: dall=1 (the banner's "Show all") > an explicit range > today.
 * dall is deliberately NOT remembered, so every fresh visit lands back on today. */
/* $from / $to are NEVER mutated here — the window is applied as its OWN predicate further down, exactly as
 * coding-audit.php and coding-validate.php do it.
 * Defaulting them in place looks tidier and is a trap: 'from' and 'to' are in this page's remember_filters
 * list (line 17), and the form echoes them back into its date inputs. So a defaulted date would be submitted
 * as a LITERAL ("2026-07-17"), persisted to the session as a fixed date rather than the idea of "today", and
 * replayed on tomorrow's bare nav visit — the release queue would silently show YESTERDAY, with $dToday false
 * so neither the banner nor the Show-all escape renders to explain it. A value derived from date() must never
 * enter the persisted filter set. Keeping from/to as '' means the session only ever replays '', so the
 * default re-arms on every visit and stays idempotent across midnight. */
$dall   = ($_GET['dall'] ?? '') === '1';
$dToday = !$dall && $from === '' && $to === '';
$where = []; $params = [];
if ($view === 'billing')        { $where[] = "ci.released_to_billing_at IS NOT NULL AND COALESCE(ci.release_destination,'internal')='internal'"; }
elseif ($view === 'vendor')     { $where[] = "ci.released_to_billing_at IS NOT NULL AND COALESCE(ci.release_destination,'internal')='external'"; }
elseif ($view === 'torelease')  { $where[] = $TORELEASE; }
else { $where[] = "$dispo = ?"; $params[] = $view; }
if ($fClients)    { $where[] = 'ci.client_id IN ('   . $inCsv($fClients)    . ')'; }
if ($fProcesses)  { $where[] = 'ci.process_id IN ('  . $inCsv($fProcesses)  . ')'; }
if ($fFacilities) { $where[] = 'ci.facility_id IN (' . $inCsv($fFacilities) . ')'; }
if ($fProviders)  { $where[] = 'COALESCE(ci.coded_provider_id,ci.provider_id) IN (' . $inCsv($fProviders) . ')'; }
if ($fCoders)     { $where[] = 'ci.coder_id IN (' . $inCsv($fCoders) . ')'; }
if ($fPatient)  { $where[] = 'ci.patient_name LIKE ?'; $params[] = '%' . $fPatient . '%'; }
if ($fMrn)      { $where[] = 'ci.patient_mrn LIKE ?';  $params[] = '%' . $fMrn . '%'; }
if ($fGender)   { $where[] = 'ci.gender = ?'; $params[] = $fGender; }
if ($fCpt)      { $where[] = 'EXISTS (SELECT 1 FROM coding_chart_cpts cc WHERE cc.inventory_id=ci.id AND cc.cpt_code LIKE ?)'; $params[] = '%' . $fCpt . '%'; }
if ($fIcd)      { $where[] = 'EXISTS (SELECT 1 FROM coding_chart_cpts cc JOIN coding_chart_cpt_icds cci ON cci.chart_cpt_id=cc.id WHERE cc.inventory_id=ci.id AND cci.icd_code LIKE ?)'; $params[] = '%' . $fIcd . '%'; }
if     ($fAudit === 'audited')    { $where[] = "ci.audit_status='ready_to_deliver'"; }
elseif ($fAudit === 'inprogress') { $where[] = "ci.audit_status='in_progress'"; }
elseif ($fAudit === 'not')        { $where[] = "(ci.audit_status IS NULL OR ci.audit_status='')"; }
if ($from) { $where[] = "DATE($dateCol) >= ?"; $params[] = $from; }
if ($to)   { $where[] = "DATE($dateCol) <= ?"; $params[] = $to; }
/* The today-default, as a SEPARATE predicate rather than a defaulted from/to — see $dToday above for why
 * that distinction is load-bearing. Mirrors the same rule in $cnt(), so the badges and these rows agree. */
if ($dToday) { $where[] = "DATE($dateCol) = ?"; $params[] = date('Y-m-d'); }
/* RFI/Disregard client-delivery sub-status (default = pending = not yet sent to client). */
$isClient = in_array($view, ['rfi','disregard'], true);
$cstatus  = in_array($_GET['cstatus'] ?? '', ['pending','sent','all'], true) ? $_GET['cstatus'] : 'pending';
if ($isClient) {
    if ($cstatus === 'pending')   $where[] = 'ci.client_sent_at IS NULL';
    elseif ($cstatus === 'sent')  $where[] = 'ci.client_sent_at IS NOT NULL';
}
$wsql = 'WHERE ' . implode(' AND ', $where) . $teamSql;

$sql = "SELECT ci.*, c.name cn, p.name pn, f.name fn, pr.name prn, cp.name coded_prov,
            rp.name rendering_name, bp.name billing_name,
            u.name coder, au.name auditor, rb.name released_name, cs.name client_sent_name,
            ba.billing_status AS ba_status
        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 cp ON cp.id=ci.coded_provider_id
        LEFT JOIN coding_providers rp ON rp.id=ci.rendering_provider_id
        LEFT JOIN coding_providers bp ON bp.id=ci.billing_provider_id
        LEFT JOIN users u  ON u.id=ci.coder_id
        LEFT JOIN users au ON au.id=ci.auditor_id
        LEFT JOIN users rb ON rb.id=ci.released_by
        LEFT JOIN users cs ON cs.id=ci.client_sent_by
        LEFT JOIN billing_allocation ba ON ba.inventory_id=ci.id
        $wsql ORDER BY $dateCol DESC LIMIT 5000";
$rows = $pdo->prepare($sql); $rows->execute($params); $rows = $rows->fetchAll();

/* The ACTUAL biller disposition (billing_allocation.billing_status), not the legacy ci.billing_status='pending'.
   Defined before the export block so both the export AND the on-screen table use it. */
$bColor  = ['billed'=>'green', 'unbilled'=>'red', 'hold_deductible'=>'orange', 'pending'=>'azure'];
$bsLabel = fn($s) => ['billed'=>'Billed', 'unbilled'=>'Unbilled', 'hold_deductible'=>'Billed – On Hold Deductible'][$s] ?? 'Pending';

/* Full coded lines for every listed chart (batched) so billing/client get complete detail — and the
 * codes are the FINAL billable ones: the coder's codes with the AUDITOR's corrections applied
 * (incorrect→corrected, 'extra' dropped, 'missing' added). Charts not audited show coder codes. */
$linesByChart = [];
$invIds = array_map('intval', array_column($rows, 'id'));
if ($invIds) {
    $in = implode(',', $invIds);
    $cptRows = $pdo->query("SELECT id, inventory_id, dos, cpt_code, units FROM coding_chart_cpts WHERE inventory_id IN ($in) ORDER BY dos, sort_order, id")->fetchAll();
    $cptIds = array_map('intval', array_column($cptRows, 'id'));
    $icdBy = []; $modBy = [];
    if ($cptIds) {
        $cin = implode(',', $cptIds);
        foreach ($pdo->query("SELECT chart_cpt_id, icd_code FROM coding_chart_cpt_icds WHERE chart_cpt_id IN ($cin) ORDER BY sort_order, id")->fetchAll() as $r) $icdBy[$r['chart_cpt_id']][] = $r['icd_code'];
        foreach ($pdo->query("SELECT chart_cpt_id, modifier_code FROM coding_chart_cpt_modifiers WHERE chart_cpt_id IN ($cin) ORDER BY sort_order, id")->fetchAll() as $r) $modBy[$r['chart_cpt_id']][] = $r['modifier_code'];
    }
    $coderLines = [];
    foreach ($cptRows as $c) {
        $coderLines[(int)$c['inventory_id']][] = ['cptId'=>(int)$c['id'], 'dos'=>$c['dos'], 'cpt'=>$c['cpt_code'], 'units'=>(int)($c['units'] ?? 1), 'icds'=>$icdBy[$c['id']] ?? [], 'mods'=>$modBy[$c['id']] ?? []];
    }
    $findBy = [];
    // Skip SUPERSEDED findings (a pre-release qaEdit corrected the codes directly) — otherwise the display/export
    // would re-apply stale corrections on top of the already-corrected saved lines and diverge from what billing
    // bills (billing's coding_effective_lines_bulk applies the same superseded_at IS NULL filter).
    $supSql = app_col_exists($pdo, 'coding_audit_codes', 'superseded_at') ? ' AND superseded_at IS NULL' : '';
    foreach ($pdo->query("SELECT inventory_id, chart_cpt_id, code_type, code_value, result, correct_value FROM coding_audit_codes WHERE inventory_id IN ($in)$supSql")->fetchAll() as $f) $findBy[(int)$f['inventory_id']][] = $f;
    foreach ($invIds as $iid) $linesByChart[$iid] = coding_apply_audit_corrections($coderLines[$iid] ?? [], $findBy[$iid] ?? []);
}

/* counts per view (within team scope, ignoring the other filters).
 * $win applies the SAME today-window as the list — each view on its OWN date axis via $dateColFor. Without it
 * a windowed list of 3 would sit under a badge of 200: the count-vs-list drift this codebase keeps
 * re-introducing. Passing $win=false gives the unwindowed total, which is what $dHidden subtracts from. */
$cnt = function (string $v, bool $win = true) use ($pdo, $dispo, $teamSql, $TORELEASE, $dateColFor, $dToday) {
    if ($v === 'billing')        $w = "ci.released_to_billing_at IS NOT NULL AND COALESCE(ci.release_destination,'internal')='internal'";
    elseif ($v === 'vendor')     $w = "ci.released_to_billing_at IS NOT NULL AND COALESCE(ci.release_destination,'internal')='external'";
    elseif ($v === 'torelease')  $w = $TORELEASE;
    else                         $w = "$dispo = " . $pdo->quote($v) . " AND ci.client_sent_at IS NULL"; // rfi/disregard badge = still to send to client
    if ($win && $dToday) $w .= " AND DATE(" . $dateColFor($v) . ") = " . $pdo->quote(date('Y-m-d'));
    return (int) $pdo->query("SELECT COUNT(*) FROM coding_inventory ci WHERE $w" . $teamSql)->fetchColumn();
};
$VIEWS  = ['torelease','billing','vendor','rfi','disregard'];
$counts = []; $dHidden = [];
foreach ($VIEWS as $v) {
    $counts[$v]  = $cnt($v);
    // Subtracting the two counts (rather than counting "before today") cannot disagree with the badge, and
    // stays correct for the NULL stamps COALESCE is there to paper over.
    $dHidden[$v] = $dToday ? max(0, $cnt($v, false) - $counts[$v]) : 0;
}

// Escape hatches, built by REMOVING keys so the URL can never contradict itself. "Back to today" SETS from/to
// empty rather than omitting them — remember_filters replays a saved range into $_GET before the window is
// decided, so omitting would silently land on that range instead of today.
$delBase   = array_diff_key($_GET, array_flip(['from','to','dall','export','_f','clear']));
$dallUrl   = url('/admin/coding-deliverables.php?' . http_build_query(array_merge($delBase, ['view'=>$view, 'dall'=>'1'])));
$todayUrl  = url('/admin/coding-deliverables.php?' . http_build_query(array_merge($delBase, ['view'=>$view, 'from'=>'', 'to'=>''])));

/* Excel export — COMPLETE chart detail, ONE ROW PER CPT LINE (the billing/claim format):
 * demographics + provider + DOS + CPT + its ICDs + modifiers, plus the handoff columns.
 * A chart with no coded lines (e.g. some Disregards) still exports one row with blank code cells. */
if (($_GET['export'] ?? '') === 'xlsx') {
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $common = ['Patient','MRN','DOB','Gender','Client','Facility','Billing provider','Record sign date','DOS','CPT','Units','Modifiers','ICDs','Coder'];
    $hdr = $isReleased           ? array_merge($common, ['Coding completed','Audit completed','Released at','Released by','Billing status','Claim ref'])
        : ($view === 'torelease' ? array_merge($common, ['Audit status','Coding completed','Audit completed'])
        : array_merge($common, ['Tagged by','Reason','Comment','Coding completed','Audit completed','Client delivery']));
    $data = [];
    foreach ($rows as $r) {
        $lines = $linesByChart[(int)$r['id']] ?? [];
        if (!$lines) $lines = [['dos'=>$r['dos'], 'cpt'=>'', 'units'=>1, 'icds'=>[], 'mods'=>[]]];   // keep the chart even with no codes
        $tail = $isReleased           ? [$r['submitted_at'], $r['audited_at'], $r['released_to_billing_at'], $r['released_name'], $bsLabel($r['ba_status'] ?? ''), $r['claim_ref']]
            : ($view === 'torelease'  ? [($r['audit_status'] ?: 'not audited'), $r['submitted_at'], $r['audited_at']]
            : [ (in_array($r['audit_status'],['rfi','disregard'],true)?'auditor':'coder'),
                ($r['audit_secondary_reason'] ?: $r['secondary_reason']),
                ($r['audit_comment'] ?: $r['comment']),
                $r['submitted_at'], $r['audited_at'],
                ($r['client_sent_at'] ? ('Sent ' . $r['client_sent_at'] . ($r['client_sent_name'] ? ' by ' . $r['client_sent_name'] : '')) : 'Pending') ]);
        foreach ($lines as $ln) {
            $data[] = array_merge([
                $r['patient_name'], $r['patient_mrn'], $r['patient_dob'], $r['gender'],
                $r['cn'], $r['fn'], ($r['billing_name'] ?: ''),
                $r['record_sign_date'], $ln['dos'], (!$isClient ? coding_alf_label($pdo, $ln['cpt']) : $ln['cpt']), (int)($ln['units'] ?? 1), implode(', ', $ln['mods']), implode(', ', $ln['icds']), $r['coder'],
            ], $tail);
        }
    }
    $x->addSheet(ucfirst($view), $hdr, $data);
    $x->download('coding-deliverables-' . $view . date('-Ymd') . '.xlsx');
}

$tabs =['torelease'=>['To release','clock-pause','azure'], 'billing'=>['Sent to billing','receipt','green'], 'vendor'=>['External vendor','external-link','purple'], 'rfi'=>['RFI → client','alert-circle','orange'], 'disregard'=>['Disregard → client','ban','danger']];
// 'dall' is carried deliberately: the Export link is built from this, and without it exporting from the
// "Show all" view would silently download only today — the export must mirror the view it was launched from.
$urlFor = fn($o) => url('/admin/coding-deliverables.php?' . http_build_query(array_filter(array_merge(['view'=>$view,'client_id'=>$fClients,'process_id'=>$fProcesses,'facility_id'=>$fFacilities,'provider_id'=>$fProviders,'coder_id'=>$fCoders,'from'=>$from,'to'=>$to,'patient'=>$fPatient,'mrn'=>$fMrn,'gender'=>$fGender,'cpt'=>$fCpt,'icd'=>$fIcd,'audit'=>$fAudit,'dall'=>($dall?'1':'')], $o), fn($v)=>$v!=='' && $v!==0 && $v!==[])));

$pageTitle = 'Deliverables';
require __DIR__ . '/../includes/header.php';
?>
<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-package-export me-2 text-primary"></i>Deliverables</h2>
        <div class="text-secondary small">Pull the chart lists for each handoff — <strong>billing</strong> bills insurance; <strong>RFI / Disregard</strong> go to the client.</div></div>
    <div class="col-auto"><a href="<?= e($urlFor(['export'=>'xlsx'])) ?>" class="btn btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i>Export Excel</a></div>
</div></div></div>
<div class="page-body"><div class="container-xl">
    <?php /* Every badge is windowed to today, so every badge says so — not just the tab you are on, which
             would leave the others reading a bare 0 with work sitting behind them. */ ?>
    <ul class="nav nav-tabs mb-3">
        <?php foreach ($tabs as $k=>$t): ?>
        <li class="nav-item"><a class="nav-link <?= $view===$k?'active':'' ?>" href="<?= e(url('/admin/coding-deliverables.php?view='.$k)) ?>">
            <i class="ti ti-<?= $t[1] ?> me-1"></i><?= e($t[0]) ?> <span class="badge bg-<?= $t[2] ?>-lt ms-1"><?= $counts[$k] ?></span>
            <?php if (!empty($dHidden[$k])): ?><span class="badge bg-secondary-lt ms-1 fw-normal"
                title="<?= (int)$dHidden[$k] ?> older chart(s) are outside today's window"><?= '+' . (int)$dHidden[$k] ?></span><?php endif; ?></a></li>
        <?php endforeach; ?>
    </ul>

    <?php /* The today-default must never let a queue hide work silently — a chart completed yesterday and never
             released would drop out of the default view AND its badge would read 0. */ ?>
    <?php $hidNow = (int) ($dHidden[$view] ?? 0); ?>
    <?php if ($dToday && $hidNow > 0): ?>
    <div class="alert alert-warning d-flex flex-wrap align-items-center gap-2 py-2 px-3 mb-3">
        <i class="ti ti-clock-exclamation"></i>
        <span>Showing <strong>today</strong> only — <strong><?= $hidNow ?></strong> older chart<?= $hidNow===1?'':'s' ?>
            in this tab <?= $hidNow===1?'is':'are' ?> outside this window.</span>
        <a href="<?= e($dallUrl) ?>" class="btn btn-sm btn-warning ms-auto"><i class="ti ti-eye me-1"></i>Show all</a>
    </div>
    <?php elseif ($dall): ?>
    <div class="alert alert-secondary d-flex flex-wrap align-items-center gap-2 py-2 px-3 mb-3">
        <i class="ti ti-calendar-off"></i><span>Showing <strong>all dates</strong>.</span>
        <a href="<?= e($todayUrl) ?>" class="btn btn-sm btn-outline-secondary ms-auto"><i class="ti ti-calendar-event me-1"></i>Back to today</a>
    </div>
    <?php endif; ?>

    <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>
    <div class="card mb-3"><div class="card-body"><form method="GET" class="row g-2 align-items-end">
        <input type="hidden" name="view" value="<?= e($view) ?>">
        <input type="hidden" name="_f" value="1">
        <?php /* A method=GET form replaces the whole query string, so anything not echoed here is dropped by
                 Apply — without this, Apply from the "Show all" view silently re-armed the today-window. */ ?>
        <?php if ($dall): ?><input type="hidden" name="dall" value="1"><?php endif; ?>
        <div class="col-md-2"><label class="form-label small mb-1">Client</label><select name="client_id[]" multiple class="form-select form-select-sm del-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-md-2"><label class="form-label small mb-1">Process</label><select name="process_id[]" multiple class="form-select form-select-sm del-ms" data-placeholder="All processes"><?php foreach($procsOpt 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-md-2"><label class="form-label small mb-1">Facility</label><select name="facility_id[]" multiple class="form-select form-select-sm del-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-md-2"><label class="form-label small mb-1">Provider</label><select name="provider_id[]" multiple class="form-select form-select-sm del-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>
        <div class="col-md-2"><label class="form-label small mb-1">Coder</label><select name="coder_id[]" multiple class="form-select form-select-sm del-ms" data-placeholder="All coders"><?php foreach($codersOpt as $o):?><option value="<?=(int)$o['id']?>" <?=in_array((int)$o['id'],$fCoders,true)?'selected':''?>><?=e($o['name'])?></option><?php endforeach;?></select></div>
        <div class="col-md-2"><label class="form-label small mb-1">Patient</label><input type="search" name="patient" value="<?= e($fPatient) ?>" class="form-control form-control-sm" placeholder="name…" autocomplete="off"></div>
        <div class="col-md-2"><label class="form-label small mb-1">MRN/PRN</label><input type="search" name="mrn" value="<?= e($fMrn) ?>" class="form-control form-control-sm" placeholder="MRN…" autocomplete="off"></div>
        <div class="col-md-2"><label class="form-label small mb-1">Gender</label><select name="gender" class="form-select form-select-sm"><option value="">All</option><?php foreach(['male'=>'Male','female'=>'Female','other'=>'Other','unknown'=>'Unknown'] as $gv=>$gl):?><option value="<?=$gv?>" <?=$fGender===$gv?'selected':''?>><?=$gl?></option><?php endforeach;?></select></div>
        <div class="col-md-2"><label class="form-label small mb-1">CPT</label><input type="search" name="cpt" value="<?= e($fCpt) ?>" class="form-control form-control-sm" placeholder="CPT…" autocomplete="off"></div>
        <div class="col-md-2"><label class="form-label small mb-1">ICD</label><input type="search" name="icd" value="<?= e($fIcd) ?>" class="form-control form-control-sm" placeholder="ICD…" autocomplete="off"></div>
        <?php if (!$isClient): ?><div class="col-md-2"><label class="form-label small mb-1">Audit</label><select name="audit" class="form-select form-select-sm"><option value="">All audit</option><option value="audited" <?= $fAudit==='audited'?'selected':'' ?>>Audited</option><option value="inprogress" <?= $fAudit==='inprogress'?'selected':'' ?>>Auditing…</option><option value="not" <?= $fAudit==='not'?'selected':'' ?>>Not audited</option></select></div><?php endif; ?>
        <div class="col-md-2"><label class="form-label small mb-1"><?= $view==='billing'?'Released from':'Date from' ?></label><input type="date" name="from" value="<?= e($from) ?>" class="form-control form-control-sm"></div>
        <div class="col-md-2"><label class="form-label small mb-1">to</label><input type="date" name="to" value="<?= e($to) ?>" class="form-control form-control-sm"></div>
        <?php if ($isClient): ?><div class="col-md-2"><label class="form-label small mb-1">Client delivery</label>
            <select name="cstatus" class="form-select form-select-sm">
                <option value="pending" <?= $cstatus==='pending'?'selected':'' ?>>To send</option>
                <option value="sent" <?= $cstatus==='sent'?'selected':'' ?>>Sent</option>
                <option value="all" <?= $cstatus==='all'?'selected':'' ?>>All</option>
            </select></div><?php endif; ?>
        <div class="col-md-2"><button class="btn btn-sm btn-primary"><i class="ti ti-filter me-1"></i>Filter</button> <a href="<?= e(url('/admin/coding-deliverables.php?view='.$view.'&clear=1')) ?>" class="btn btn-sm btn-ghost-secondary">Clear</a></div>
    </form>
    <script>(function(){ if (typeof TomSelect === 'undefined') return;
        document.querySelectorAll('.del-ms').forEach(function(sel){ new TomSelect(sel, { plugins:['remove_button','clear_button'], placeholder: sel.dataset.placeholder||'', maxOptions:5000, hidePlaceholder:false, persist:false }); }); })();</script>
    </div></div>

    <?php
    /* Each chart on ONE flat row — demographics + every CPT line (DOS/CPT/Units/Mod/ICD) as columns.
     * Multi-line charts stack their code cells. Selection (col 1) drives the bulk handoff buttons. */
    $multi  = $isClient && $canMark;                                       // RFI/Disregard: multi-select "Mark sent to client"
    $relSel = ($view === 'torelease' && can('coding.release_billing'));    // To release: multi-select "Send to billing"
    $sel    = $multi || $relSel;
    $tail   = $isReleased ? 4 : ($view==='torelease' ? 2 : 5);
    $dcols  = ($sel?1:0) + 14 + $tail;                                     // 14 lead columns + tab-specific tail (+ checkbox)
    $codeCell = function(array $lines, string $field) use ($pdo, $isClient) {
        if (!$lines) return '<span class="text-secondary">—</span>';
        $out = [];
        foreach ($lines as $ln) {
            if     ($field==='dos')   $out[] = e(fmt_date($ln['dos'])) ?: '—';
            elseif ($field==='cpt')   $out[] = '<strong>'.e(!$isClient ? coding_alf_label($pdo,$ln['cpt']) : $ln['cpt']).'</strong>';
            elseif ($field==='units') $out[] = (int)($ln['units'] ?? 1);
            elseif ($field==='mods')  $out[] = e(implode(', ', $ln['mods'])) ?: '<span class="text-secondary">—</span>';
            elseif ($field==='icds')  $out[] = e(implode(', ', $ln['icds'])) ?: '<span class="text-secondary">—</span>';
        }
        return implode('<div class="border-top my-1" style="opacity:.4"></div>', $out);
    };
    ?>
    <?php if ($sel): ?>
    <form method="POST" id="dForm"><?= csrf_field() ?>
        <input type="hidden" name="view" value="<?= e($view) ?>">
        <?php if ($relSel): ?><input type="hidden" name="action" value="release_to_billing"><?php endif; ?>
    <div class="d-flex flex-wrap gap-2 align-items-center mb-2">
        <?php if ($relSel): ?>
            <button type="submit" class="btn btn-sm btn-success" onclick="if(!document.querySelector('.selchk:checked')){alert('Select at least one chart.');return false;}return confirm('Send the selected chart(s) to the billing team?');"><i class="ti ti-send me-1"></i>Send to billing</button>
            <button type="submit" class="btn btn-sm btn-outline-success" onclick="document.querySelectorAll('.selchk').forEach(function(c){c.checked=true;});return confirm('Send ALL <?= count($rows) ?> listed chart(s) to billing?');"><i class="ti ti-send me-1"></i>Send all listed (<?= count($rows) ?>)</button>
            <span class="text-secondary small"><i class="ti ti-info-circle me-1"></i>Selected charts move <strong>To release → Sent to billing</strong>. Audit is optional.</span>
        <?php else: ?>
            <button type="submit" name="action" value="mark_sent_client" class="btn btn-sm btn-primary" onclick="if(!document.querySelector('.selchk:checked')){alert('Select at least one chart.');return false;}return confirm('Mark the selected chart(s) as sent to the client?');"><i class="ti ti-mail-forward me-1"></i>Mark sent to client</button>
            <button type="submit" name="action" value="mark_sent_client" class="btn btn-sm btn-outline-primary" onclick="document.querySelectorAll('.selchk').forEach(function(c){c.checked=true;});return confirm('Mark ALL <?= count($rows) ?> listed chart(s) as sent to client?');"><i class="ti ti-mail-forward me-1"></i>Mark all listed (<?= count($rows) ?>)</button>
            <span class="vr d-none d-md-block"></span>
            <input type="text" name="clarify_note" class="form-control form-control-sm" style="max-width:240px" placeholder="Clarification note (optional)…" maxlength="1000" onkeydown="if(event.key==='Enter')event.preventDefault();"><!-- don't let Enter fire the first (Mark-sent) button -->
            <button type="submit" name="action" value="send_back_coder" class="btn btn-sm btn-warning" onclick="if(!document.querySelector('.selchk:checked')){alert('Select at least one chart.');return false;}return confirm('Send the selected chart(s) BACK to the coder to rework with the client clarification? They return to the coder\'s open queue at top priority.');"><i class="ti ti-arrow-back-up me-1"></i>Send back to coder</button>
            <span class="text-secondary small"><i class="ti ti-info-circle me-1"></i>RFI / Disregard go to the <strong>client</strong>; once the client clarifies, <strong>Send back to coder</strong> returns them to the coder to rework.</span>
        <?php endif; ?>
    </div>
    <?php endif; ?>
    <div class="card"><div class="card-header"><h3 class="card-title mb-0"><?= count($rows) ?> chart(s)</h3>
        <div class="card-actions text-secondary small"><i class="ti ti-info-circle me-1"></i>One row per chart (codes stack for multi-line) · Export = one row per CPT line<?php if ($isClient): ?> · these go to the <strong>client</strong><?php elseif ($view==='torelease'): ?> · <strong>not yet released</strong> to billing<?php endif; ?></div></div>
    <div class="table-responsive"><table class="table table-vcenter table-sm card-table" style="font-size:.8rem" data-coltweak="coding-deliv">
        <thead><tr>
            <?php if ($sel): ?><th class="w-1" data-colfixed><input type="checkbox" id="selAllChk" class="form-check-input m-0" title="Select all"></th><?php endif; ?>
            <th data-col="patient">Patient</th><th data-col="mrn">MRN/PRN</th><th data-col="dob">DOB</th><th data-col="gender">Gender</th><th data-col="rsd">Record sign</th><th data-col="client">Client</th><th data-col="facility">Facility</th>
            <th data-col="billing">Billing prov</th><th data-col="dos">DOS</th><th data-col="cpt">CPT</th><th class="text-center" data-col="units">Units</th><th data-col="mods">Modifier(s)</th><th data-col="icd">ICD(s)</th><th data-col="coder">Coder</th>
            <?php if ($isReleased): ?><th data-col="released">Released</th><th data-col="relby">By</th><th data-col="bstatus">Billing</th><th data-col="claimref">Claim ref</th>
            <?php elseif ($view==='torelease'): ?><th data-col="audit">Audit</th><th data-col="submitted">Submitted</th>
            <?php else: ?><th data-col="taggedby">Tagged by</th><th data-col="reason">Reason</th><th data-col="comment">Comment</th><th data-col="date">Date</th><th data-col="clientdlv">Client</th><?php endif; ?>
        </tr></thead>
        <tbody>
        <?php if (!$rows): ?><tr><td colspan="<?= $dcols ?>" class="text-center text-secondary py-4">Nothing in this view.<?php if ($view==='torelease'): ?> All completed charts have been released. 🎉<?php elseif ($isClient && $cstatus==='pending'): ?> Everything has been sent to the client. 🎉<?php endif; ?></td></tr>
        <?php else: foreach ($rows as $r):
            $lines = $linesByChart[(int)$r['id']] ?? [];
            $bs = (string) ($r['ba_status'] ?? '');   // the biller's actual disposition (blank = not yet processed → Pending)
            $taggedBy = in_array($r['audit_status'], ['rfi','disregard'], true) ? 'auditor' : 'coder';
            $reason = $r['audit_secondary_reason'] ?: $r['secondary_reason'];
            $cmt = $r['audit_comment'] ?: $r['comment'];
            $date = $r['audited_at'] ?: $r['submitted_at'];
            $isCorr = false; foreach ($lines as $ln) if (!empty($ln['corrected'])) { $isCorr = true; break; } ?>
            <tr>
                <?php if ($sel): ?><td><input type="checkbox" name="ids[]" value="<?= (int)$r['id'] ?>" class="form-check-input m-0 selchk"></td><?php endif; ?>
                <td><strong><?= e($r['patient_name']) ?></strong><?php if ($isCorr): ?> <span class="badge bg-purple-lt" title="Final billable codes — auditor corrections applied"><i class="ti ti-edit"></i></span><?php endif; ?></td>
                <td><?= e($r['patient_mrn'] ?: '—') ?></td>
                <td class="text-nowrap"><?= e(fmt_date($r['patient_dob'])) ?></td>
                <td><?= e(ucfirst($r['gender'] ?: '—')) ?></td>
                <td class="text-nowrap"><?= e(fmt_date($r['record_sign_date'])) ?></td>
                <td class="text-secondary"><?= e($r['cn']) ?></td>
                <td><?= e($r['fn']) ?></td>
                <td><?= e($r['billing_name'] ?: '—') ?></td>
                <td class="text-nowrap"><?= $codeCell($lines,'dos') ?></td>
                <td><?= $codeCell($lines,'cpt') ?></td>
                <td class="text-center"><?= $codeCell($lines,'units') ?></td>
                <td><?= $codeCell($lines,'mods') ?></td>
                <td><?= $codeCell($lines,'icds') ?></td>
                <td><?= e($r['coder'] ?? '—') ?></td>
                <?php if ($isReleased): ?>
                <td class="text-secondary text-nowrap"><?= e(fmt_dt($r['released_to_billing_at'])) ?></td>
                <td><?= e($r['released_name'] ?? '—') ?></td>
                <td><span class="badge bg-<?= $bColor[$bs] ?? 'azure' ?>-lt"><?= e($bsLabel($bs)) ?></span></td>
                <td><?= e($r['claim_ref'] ?? '—') ?></td>
                <?php elseif ($view==='torelease'): ?>
                <td><?php $as=$r['audit_status']; if($as==='ready_to_deliver'): ?><span class="badge bg-success-lt" title="Audited &amp; passed"><i class="ti ti-checks me-1"></i>audited</span><?php elseif($as==='in_progress'): ?><span class="badge bg-info-lt">auditing…</span><?php else: ?><span class="text-secondary">not audited</span><?php endif; ?>
                    <?php if (($r['audit_status'] ?? '')==='ready_to_deliver' && empty($r['released_to_billing_at']) && (can('coding.audit') || can('coding.supervise'))): ?>
                        <a href="<?= e(url('/admin/coding-chart.php?id='.(int)$r['id'])) ?>" class="btn btn-sm btn-outline-warning ms-1" title="Audited but not yet released — edit the codes before releasing to billing"><i class="ti ti-edit me-1"></i>Edit</a>
                    <?php endif; ?></td>
                <td class="text-secondary text-nowrap"><?= e(fmt_dt($r['submitted_at'])) ?></td>
                <?php else: ?>
                <td><span class="badge bg-<?= $taggedBy==='auditor'?'azure':'secondary' ?>-lt"><?= $taggedBy ?></span></td>
                <td><?= e($reason ?: '—') ?></td>
                <td class="text-secondary" style="max-width:220px"><?= e($cmt ?: '—') ?></td>
                <td class="text-secondary text-nowrap"><?= e($date ? fmt_dt($date) : '—') ?></td>
                <td class="text-nowrap"><?php if ($r['client_sent_at']): ?><span class="badge bg-green-lt" title="Sent <?= e(fmt_dt($r['client_sent_at'])) ?><?= $r['client_sent_name']?' by '.e($r['client_sent_name']):'' ?>"><i class="ti ti-check me-1"></i>sent</span><?php else: ?><span class="badge bg-orange-lt">to send</span><?php endif; ?>
                    <?php if ($multi): ?><button type="button" class="btn btn-sm btn-warning py-0 px-1 ms-1 sbc-one" data-id="<?= (int)$r['id'] ?>" data-name="<?= e($r['patient_name']) ?>" title="Send this chart back to the coder with the client clarification"><i class="ti ti-arrow-back-up"></i></button><?php endif; ?></td>
                <?php endif; ?>
            </tr>
        <?php endforeach; endif; ?>
        </tbody>
    </table></div>
    <?php if (count($rows) >= 5000): ?><div class="card-footer text-secondary small">Showing first 5000 — narrow with filters.</div><?php endif; ?>
    </div>
    <?php if ($sel): ?></form>
    <script>(function(){var a=document.getElementById('selAllChk');if(a)a.addEventListener('change',function(){document.querySelectorAll('.selchk').forEach(function(c){c.checked=a.checked;});});
    // Per-chart "Send back to coder": select only this row, carry a one-off clarification note, submit the shared form.
    document.querySelectorAll('.sbc-one').forEach(function(btn){
        btn.addEventListener('click', function(){
            var id=this.dataset.id, name=this.dataset.name||'this chart';
            var note=prompt('Client clarification received for '+name+'.\nOptional note for the coder — click OK to send this chart back:', '');
            if(note===null) return;                                  // cancelled
            var form=document.getElementById('dForm'); if(!form) return;
            form.querySelectorAll('.selchk').forEach(function(c){ c.checked=(c.value===id); });
            var ni=form.querySelector('input[name="clarify_note"]'); if(ni) ni.value=note;
            var h=document.createElement('input'); h.type='hidden'; h.name='action'; h.value='send_back_coder'; form.appendChild(h);
            form.submit();
        });
    });})();</script>
    <?php endif; ?>
</div></div>
<?php require __DIR__ . '/../includes/footer.php'; ?>
