<?php
/**
 * Audit (coding.audit).
 *   list (default)        : ONE unified "Audit" queue = every coder-completed chart awaiting billing
 *                           (Audited tab = finalized audit results; Rebuttals tab = coder disputes)
 *   ?id=X                 : audit a chart (read coded lines, set outcome + quality verdict)
 *   POST action=audit     : record the audit
 *   (Release to billing moved to coding-deliverables.php — Audit only QAs charts now.)
 *   POST action=rebuttal_resolve   : accept/reject a coder rebuttal
 *
 * Process flow: coder marks a chart "Completed" → it lands in the Audit queue. Bucket sampling flags
 * ~X% of them (in_sample=1, per the coder's 3-week-quality tier) as the audit sample — those are
 * badged "Sample" and SHOULD be audited. The auditor audits the sample, then bulk-selects any/all
 * completed charts and sends them to billing (audit is OPTIONAL — advisory sampling). RFI / Disregard
 * never appear here; they are the Validator's (coding-validate.php) and return to the client via RTO.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

/* Per-session filter memory (keyed per queue). Placed at the TOP so it also applies to the export + single-chart
   (?id=) paths, which return before the list block. Names = the audit filters (queue itself is navigation). */
$rfQueue = in_array($_GET['queue'] ?? '', ['audited','rebuttals'], true) ? (string) $_GET['queue'] : 'audit';
remember_filters('coding_audit_' . $rfQueue, ['client_id','process_id','facility_id','provider_id','coder_id','patient','from','to','sample','astate','priority']);

/* Client-assignment fence (2026-07-02): an auditor / supervisor sees ONLY the clients they are ASSIGNED to
   (coding_user_scope alignment). SysAdmin is unrestricted. Applied to every queue (To-audit / Audited /
   Rebuttals) + the counts + the per-chart audit view + the POST handlers (so a forged id can't escape scope).
   Assign clients to auditors/supervisors on admin/user-alignment.php (or coding-access.php). */
$isSysAud = is_sysadmin();
$scopeClientIds = null;                                   // null = unrestricted (sysadmin)
if (!$isSysAud) {
    $sc = $pdo->prepare("SELECT DISTINCT client_id FROM coding_user_scope WHERE user_id=? AND client_id IS NOT NULL AND " . coding_vertical_scope_sql('vertical_id'));
    $sc->execute([$uid]);
    $scopeClientIds = array_map('intval', $sc->fetchAll(PDO::FETCH_COLUMN));
}
/** " AND <col> IN (ids)" fence for a query, or '' for sysadmin. Empty scope → IN (0) (matches nothing). */
$clientFenceSql = fn(string $col) => $scopeClientIds === null ? '' : (' AND ' . $col . ' IN (' . ($scopeClientIds ? implode(',', array_map('intval', $scopeClientIds)) : '0') . ')');
/** True when a chart's client is in the current user's assigned clients (sysadmin: always). */
$chartInScope = fn(int $clientId) => $scopeClientIds === null || in_array($clientId, $scopeClientIds, true);

/* ── Queue filter carry-through (persistence + auto-advance) ──────────────────────────────────────
   The auditor's queue filters (queue tab + client/facility/provider/coder/date/sample/astate/priority)
   must survive the open-chart → audit → back round-trip, and after finishing a chart we auto-open the
   NEXT chart within the SAME filter set. audit_filter_set() whitelists + compacts the active filters
   from a request array (GET on the list/view, POST on submit). */
function audit_filter_set(array $src): array {
    $out = [];
    /* 'dall' is in this whitelist deliberately. This set drives $listCarry — every sort header, every row's
     * Audit link, the detail Back button and every POST redirect — so a key missing here is a key silently
     * dropped by all of them, which made the "Show all" escape one-shot: one sort click and the today-window
     * snapped back on.
     * Safe for audit_next_chart(), which reads only the specific keys it names below and never 'dall' — the
     * dispatcher stays unwindowed either way, which is what keeps the backlog reachable. */
    foreach (['queue','client_id','process_id','facility_id','provider_id','coder_id','patient','from','to','sample','astate','priority','dall'] as $k) {
        $v = trim((string) ($src[$k] ?? ''));
        if ($v !== '' && $v !== '0') $out[$k] = $v;   // drop empty + 0-int (no filter); keep '1' flags, dates, strings
    }
    return $out;
}
/** Base WHERE body for a queue tab ('audit' = still to audit, 'audited' = finalized). */
function audit_queue_cond(string $queue): string {
    $base = "ci.status='completed' AND COALESCE(ci.audit_status,'') NOT IN ('rfi','disregard') AND ci.released_to_billing_at IS NULL";
    return $queue === 'audited' ? "$base AND ci.audit_status='ready_to_deliver'" : "$base AND COALESCE(ci.audit_status,'') <> 'ready_to_deliver'";
}
/** Next chart id in the active filter set (same WHERE the list uses) honoring the client fence; 0 if none. */
function audit_next_chart(PDO $pdo, array $carry, string $fenceSql, int $excludeId): int {
    $queue = (($carry['queue'] ?? 'audit') === 'audited') ? 'audited' : 'audit';
    $w = [audit_queue_cond($queue)]; $pa = [];
    if (!empty($carry['client_id']))   { $w[] = 'ci.client_id=?';   $pa[] = (int) $carry['client_id']; }
    if (!empty($carry['process_id']))  { $w[] = 'ci.process_id=?';  $pa[] = (int) $carry['process_id']; }
    if (!empty($carry['facility_id'])) { $w[] = 'ci.facility_id=?'; $pa[] = (int) $carry['facility_id']; }
    if (!empty($carry['provider_id'])) { $w[] = 'COALESCE(ci.coded_provider_id,ci.provider_id)=?'; $pa[] = (int) $carry['provider_id']; }
    if (!empty($carry['coder_id']))    { $w[] = 'ci.coder_id=?'; $pa[] = (int) $carry['coder_id']; }
    // sample / audit-state filters apply only on the 'audit' (to-do) queue — mirror the list's $isAuditQ gating.
    if ($queue === 'audit') {
        if (($carry['astate'] ?? '') === 'todo') $w[] = 'ci.audit_status IS NULL';
        if (($carry['astate'] ?? '') === 'prog') $w[] = "ci.audit_status='in_progress'";
        if (!empty($carry['sample'])) $w[] = 'ci.in_sample=1';
    }
    if (!empty($carry['priority'])) $w[] = 'ci.is_priority=1';
    if (!empty($carry['from'])) { $w[] = 'ci.submitted_at>=?'; $pa[] = $carry['from'] . ' 00:00:00'; }
    if (!empty($carry['to']))   { $w[] = 'ci.submitted_at<?';  $pa[] = date('Y-m-d', strtotime($carry['to'] . ' +1 day')) . ' 00:00:00'; }
    $w[] = 'ci.id<>?'; $pa[] = $excludeId;
    $st = $pdo->prepare("SELECT ci.id FROM coding_inventory ci WHERE " . implode(' AND ', $w) . $fenceSql . " ORDER BY ci.is_priority DESC, ci.submitted_at ASC LIMIT 1");
    $st->execute($pa);
    return (int) $st->fetchColumn();
}
/** Build '/admin/coding-audit.php?...': to the next filtered chart if any, else back to the filtered list. */
function audit_after_url(PDO $pdo, array $carry, string $fenceSql, int $doneId): string {
    $next = audit_next_chart($pdo, $carry, $fenceSql, $doneId);
    if ($next) return '/admin/coding-audit.php?' . http_build_query(array_merge($carry, ['id' => $next]));
    return '/admin/coding-audit.php' . ($carry ? ('?' . http_build_query($carry)) : '');
}

/* ----------------------------------------------------------- Excel export
 * Exports the ACTIVE queue + the active filters + the client fence — i.e. exactly what is on screen — one row per coded
 * (audit-corrected) CPT line, carrying the audit verdict (blank for a not-yet-audited chart). Previously this exported
 * only the viewer's own finalized audits, so on the To-audit queue a supervisor/sysadmin got an EMPTY file. */
if (($_GET['export'] ?? '') === 'xlsx') {
    $eq = in_array($_GET['queue'] ?? '', ['audited','rebuttals'], true) ? (string) $_GET['queue'] : 'audit';
    if ($eq === 'rebuttals') { $wsql = "ci.rebuttal_status='raised'" . (can('coding.supervise') ? '' : ' AND ci.auditor_id=' . (int) $uid); }
    else                     { $wsql = audit_queue_cond($eq); }
    $wsql .= $clientFenceSql('ci.client_id');
    $ep = [];
    foreach ([['client_id','ci.client_id'], ['process_id','ci.process_id'], ['facility_id','ci.facility_id'], ['coder_id','ci.coder_id']] as [$g, $col]) {
        $v = (int) ($_GET[$g] ?? 0); if ($v) { $wsql .= " AND $col=?"; $ep[] = $v; }
    }
    $pv = (int) ($_GET['provider_id'] ?? 0); if ($pv) { $wsql .= " AND COALESCE(ci.coded_provider_id,ci.provider_id)=?"; $ep[] = $pv; }
    // Mirror the on-screen list's sample / audit-state / priority filters (sample+astate are To-audit-queue only; see $isAuditQ gating).
    if ($eq === 'audit') {
        if (($_GET['astate'] ?? '') === 'todo')      { $wsql .= " AND ci.audit_status IS NULL"; }
        elseif (($_GET['astate'] ?? '') === 'prog')  { $wsql .= " AND ci.audit_status='in_progress'"; }
        if (!empty($_GET['sample']))                 { $wsql .= " AND ci.in_sample=1"; }
    }
    if ($eq !== 'rebuttals' && !empty($_GET['priority'])) { $wsql .= " AND ci.is_priority=1"; }
    $pt = trim((string) ($_GET['patient'] ?? '')); if ($pt !== '') { $wsql .= " AND ci.patient_name LIKE ?"; $ep[] = '%' . $pt . '%'; }
    $xf = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) ($_GET['from'] ?? '')) ? $_GET['from'] : '';
    $xt = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string) ($_GET['to'] ?? '')) ? $_GET['to'] : '';
    if ($xf) { $wsql .= " AND ci.submitted_at>=?"; $ep[] = $xf . ' 00:00:00'; }
    if ($xt) { $wsql .= " AND ci.submitted_at<?";  $ep[] = date('Y-m-d', strtotime($xt . ' +1 day')) . ' 00:00:00'; }
    /* The same today's-activity default the list applies. This block runs BEFORE the page's hoisted filters
     * (it early-exits), so the rule is repeated rather than shared — keep the two in step. Without it the
     * download would quietly contain the whole queue while the screen showed today, breaking the standing
     * rule that an export mirrors the view it was launched from. */
    if (!(($_GET['dall'] ?? '') === '1') && $xf === '' && $xt === '') {
        $wsql .= " AND ci.updated_at>=? AND ci.updated_at<?";
        $ep[] = date('Y-m-d') . ' 00:00:00';
        $ep[] = date('Y-m-d', strtotime('+1 day')) . ' 00:00:00';
    }
    $ex = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.patient_dob, ci.dos, c.name cn, f.name fn,
            COALESCE(bp.name, cp.name, pr.name) provider, u.name coder,
            ci.submitted_at,
            ci.audit_result, ci.audit_opportunities, ci.audit_defects, ci.audit_secondary_reason, ci.audit_comment, ci.audited_at, ci.rebuttal_status
        FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_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 bp ON bp.id=ci.billing_provider_id
        LEFT JOIN coding_providers cp ON cp.id=ci.coded_provider_id
        LEFT JOIN users u ON u.id=ci.coder_id
        WHERE $wsql
        ORDER BY ci.is_priority DESC, ci.submitted_at DESC, ci.id DESC LIMIT 20000");
    $ex->execute($ep);
    $charts = $ex->fetchAll();
    $linesByInv = coding_effective_lines_bulk($pdo, array_column($charts, 'id'));
    $acc = fn($opp, $def) => $opp > 0 ? round(100 * ($opp - $def) / $opp, 1) : ($def > 0 ? 0 : 100);
    $data = [];
    foreach ($charts as $r) {
        $base = [(int) $r['id'], $r['patient_name'], $r['patient_mrn'] ?: '', !empty($r['patient_dob']) ? date('Y-m-d', strtotime($r['patient_dob'])) : '',
            $r['cn'], $r['fn'], $r['provider'] ?: '', $r['coder'] ?: '',
            disp_dt($r['submitted_at']),   // Chart completed = when the coder submitted it (matches the on-screen column)
            !empty($r['dos']) ? date('Y-m-d', strtotime($r['dos'])) : ''];
        $aud  = [ucfirst((string) ($r['audit_result'] ?? '')), (int) $r['audit_opportunities'], (int) $r['audit_defects'],
            $acc((int) $r['audit_opportunities'], (int) $r['audit_defects']), $r['audit_secondary_reason'] ?: '', $r['audit_comment'] ?: '',
            disp_dt($r['audited_at']), $r['rebuttal_status'] ?: 'none'];
        $lines = $linesByInv[(int) $r['id']] ?? [];
        if (!$lines) { $data[] = array_merge($base, ['', '', '', '', ''], $aud); 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'] ?? [])],
            $aud);
    }
    require_once __DIR__ . '/../includes/xlsx.php';
    $hdr = ['Chart #','Patient','MRN','DOB','Client','Facility','Provider','Coder','Chart completed','Chart DOS','Line DOS','CPT','Units','Modifier(s)','ICD(s)','Audit result','Opportunities','Defects','Accuracy %','Secondary reason','Audit comment','Audited at','Rebuttal'];
    $x = new SimpleXlsx();
    $x->addSheet('My audits', $hdr, $data);
    audit_log('coding.audit.export', 'coding_inventory', null, null, ['rows' => count($data), 'queue' => $eq]);
    $x->download('coding-audit-' . $eq . date('-Ymd') . '.xlsx');
    exit;
}

/* ----------------------------------------------------------- POST handlers */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf_check_or_die();
    $act = $_POST['action'] ?? '';

    /* Release to billing was REMOVED from Audit (2026-06-19). Releasing audited charts to the billing
     * team now lives ONLY on Deliverables (admin/coding-deliverables.php). Audit only QAs charts here. */

    if ($act === 'audit') {
        $id = (int) input('id');
        $ch = $pdo->prepare("SELECT * FROM coding_inventory WHERE id=?"); $ch->execute([$id]); $ch = $ch->fetch();
        if (!$ch) { flash('error', 'Chart not found.'); redirect('/admin/coding-audit.php'); }
        if (!$chartInScope((int) $ch['client_id'])) { flash('error', 'That chart is not in your assigned clients.'); redirect('/admin/coding-audit.php'); }
        // LOCK (no role override): a chart is read-only in audit once it is AUDITED (ready_to_deliver, no open
        // rebuttal) — it re-opens for re-audit ONLY when the coder raises a rebuttal — OR once it is RELEASED to
        // billing, after which NOBODY can change the audit (the billed codes are frozen). Applies to RTO + audit.
        if (!empty($ch['released_to_billing_at'])
            || ($ch['audit_status'] === 'ready_to_deliver' && ($ch['rebuttal_status'] ?? 'none') !== 'raised')) {
            flash('error', !empty($ch['released_to_billing_at'])
                ? 'This chart has been released to billing — its audit can no longer be changed.'
                : 'This chart has been audited and is locked. It re-opens for re-audit only when the coder raises a rebuttal.');
            redirect('/admin/coding-audit.php?id=' . $id);
        }
        // Only charts the coder has finished (completed / rfi / disregard) are auditable — never one
        // that's still pending / in-progress / on hold in the coder's queue.
        if (!in_array($ch['status'], ['completed','rfi','disregard'], true)) {
            flash('error', 'That chart is still with the coder (' . str_replace('_',' ',$ch['status']) . ') and cannot be audited yet.');
            redirect('/admin/coding-audit.php');
        }

        // RTO outcome — send the chart back to a coder's OPEN queue at TOP priority (rework after client clarification).
        if (input('audit_status') === 'rto') {
            // Once a chart has gone to billing it is out of the auditor's hands — RTO here would reset its
            // status but leave released_to_billing_at set, orphaning it (can't re-bill, can't re-audit).
            if (!empty($ch['released_to_billing_at'])) {
                flash('error', 'This chart has already been sent to billing — it can no longer be returned to the coder from Audit.');
                redirect('/admin/coding-audit.php?id=' . $id);
            }
            $to = (int) input('rto_coder') ?: (int) ($ch['coder_id'] ?: $ch['assigned_to']);
            if (!$to) { flash('error', 'Choose a coder to send it back to.'); redirect('/admin/coding-audit.php?id=' . $id); }
            $note = trim((string) input('audit_comment'));
            $pdo->beginTransaction();
            $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', rto_at=NOW(), rto_by=?, updated_at=NOW() WHERE id=?")
                ->execute([$to, $uid, $uid, $id]);
            $pdo->prepare("DELETE FROM coding_audit_codes WHERE inventory_id=?")->execute([$id]);
            coding_log_event($pdo, $id, 'rto', $uid, ($note ?: 'sent back to coder #' . $to), ($ch['audit_status'] ?: $ch['status']), 'pending');
            $pdo->commit();
            audit_log('coding.rto', 'coding_inventory', $id, null, ['to' => $to]);
            flash('success', 'Chart returned to the coder’s open queue at TOP priority.');
            // Auto-advance to the next chart in the auditor's active filter set (keeps their filters applied).
            redirect(audit_after_url($pdo, audit_filter_set($_POST), $clientFenceSql('ci.client_id'), $id));
        }

        $astatus = in_array(input('audit_status'), CODING_AUDIT_STATUSES, true) ? (string) input('audit_status') : 'in_progress';
        // RFI / Disregard are NOT an audit outcome — they're set by the Validator (or supervisor/manager). Auditor does quality only.
        if (in_array($astatus, ['rfi','disregard'], true)) {
            flash('error', 'RFI / Disregard is decided by the Validator (or a supervisor / manager), not in audit.');
            redirect('/admin/coding-audit.php?id=' . $id);
        }
        $comment = trim((string) input('audit_comment'));
        $secondary = trim((string) input('audit_secondary_reason'));
        $scored  = in_array($astatus, ['ready_to_deliver','in_progress'], true);   // code-level DPO applies

        // Collect per-code findings (each entered code is an opportunity).
        $findings = [];
        foreach ((array) ($_POST['aud'] ?? []) as $f) {
            if (!is_array($f)) continue;
            $val = strtoupper(trim((string) ($f['code_value'] ?? '')));
            if ($val === '(MISSING)') $val = '';   // the placeholder of a value-less missing row — normalize back to empty, not a literal code
            $res = in_array($f['result'] ?? '', ['correct','incorrect','missing','extra'], true) ? $f['result'] : 'correct';
            if ($val === '' && $res !== 'missing') continue;
            $findings[] = [
                'chart_cpt_id'   => (int) ($f['chart_cpt_id'] ?? 0) ?: null,
                'code_type'      => $f['code_type'] ?? 'cpt',
                'code_value'     => $val,
                'result'         => $res,
                'error_category' => $f['error_category'] ?? '',
                'correct_value'  => $f['correct_value'] ?? '',
                'note'           => $f['note'] ?? '',
            ];
        }

        $errs = [];
        if (in_array($astatus, ['rfi','disregard'], true)) {
            if ($comment === '')   $errs[] = 'A comment is required for RFI / Disregard.';
            if ($secondary === '') $errs[] = 'Select a secondary reason.';
        }
        if ($scored) foreach ($findings as $f) if ($f['result'] !== 'correct' && trim((string) $f['error_category']) === '')
            $errs[] = "Pick an error category for {$f['code_type']} " . ($f['code_value'] ?: '(missing)') . '.';
        // B: an ADDED (missing) ICD / modifier must hang off a coded CPT, else it silently never reaches billing.
        foreach ($findings as $f)
            if (($f['result'] ?? '') === 'missing' && in_array($f['code_type'] ?? '', ['icd','modifier'], true) && empty($f['chart_cpt_id']))
                $errs[] = 'Pick the CPT that the added ' . strtoupper((string) $f['code_type']) . ' ' . ($f['code_value'] ?: '') . ' belongs to.';
        if ($errs) { foreach (array_unique($errs) as $e) flash('error', $e); redirect('/admin/coding-audit.php?id=' . $id); }

        $opp = $def = null; $result = null;
        $done = $astatus !== 'in_progress';
        $pdo->beginTransaction();
        if ($scored) {
            [$opp, $def] = coding_save_audit_codes($pdo, $id, $findings, $uid);
            $result = $opp > 0 ? ($def > 0 ? 'error' : 'pass') : null;
        } else {
            $pdo->prepare("DELETE FROM coding_audit_codes WHERE inventory_id=?")->execute([$id]); // RFI/Disregard → not code-scored
        }
        // Auditor may have reordered a CPT's ICDs / modifiers — persist the corrected sequence.
        $reseq = json_decode((string) ($_POST['reseq'] ?? ''), true);
        if (is_array($reseq)) coding_resequence_chart_codes($pdo, $id, $reseq);
        $pdo->prepare("UPDATE coding_inventory SET audit_status=?, audit_result=?, audit_opportunities=?, audit_defects=?, auditor_id=?,
                        audited_at=" . ($done ? 'NOW()' : 'audited_at') . ", audit_comment=?, audit_secondary_reason=?, updated_at=NOW()
                       WHERE id=?")->execute([$astatus, $result, $opp, $def, $uid, $comment ?: null, $secondary ?: null, $id]);
        coding_log_event($pdo, $id, 'audit', $uid, ($opp !== null ? "DPO {$def}/{$opp}" : null), $ch['audit_status'], $astatus);
        $pdo->commit();
        audit_log('coding.audit', 'coding_inventory', $id, null, ['audit_status'=>$astatus,'opportunities'=>$opp,'defects'=>$def]);
        $acc = ($opp > 0) ? ' — ' . round(100.0 * ($opp - $def) / $opp, 1) . "% accuracy ($def/$opp defects)" : '';
        flash('success', 'Audit saved: ' . str_replace('_', ' ', $astatus) . $acc);
        $carry = audit_filter_set($_POST);
        // Ready to Deliver = the chart is done → auto-advance to the next chart in the active filter set.
        // SAVE (in_progress) = still working → return to the filtered list (filters kept, no advance).
        if ($astatus === 'ready_to_deliver') redirect(audit_after_url($pdo, $carry, $clientFenceSql('ci.client_id'), $id));
        redirect('/admin/coding-audit.php' . ($carry ? ('?' . http_build_query($carry)) : ''));
    }

    /* Auditor RE-TAG: correct a mis-tagged chart's client » process » facility » provider during audit. Only a
       completed, not-released chart; both the current AND the new client must be in the auditor's assigned scope;
       the tuple must be a real hierarchy (coding_facility_providers). Does not touch codes or coder quality. */
    if ($act === 'retag') {
        $id = (int) input('id');
        $combo = (string) input('retag_combo');   // "clientId:processId:facilityId:providerId"
        $back  = '/admin/coding-audit.php?' . http_build_query(array_merge(audit_filter_set($_POST), ['id' => $id]));
        $ch = $pdo->prepare("SELECT * FROM coding_inventory WHERE id=?"); $ch->execute([$id]); $ch = $ch->fetch();
        if (!$ch) { flash('error', 'Chart not found.'); redirect('/admin/coding-audit.php'); }
        if (!$chartInScope((int) $ch['client_id'])) { flash('error', 'That chart is not in your assigned clients.'); redirect('/admin/coding-audit.php'); }
        if ($ch['status'] !== 'completed' || !empty($ch['released_to_billing_at'])) { flash('error', 'Only a completed, not-yet-released chart can be re-tagged.'); redirect($back); }
        $p = array_map('intval', explode(':', $combo) + [0, 0, 0, 0]);
        [$nc, $np, $nf, $npr] = [$p[0], $p[1], $p[2], $p[3]];
        if (!$nc || !$np || !$nf || !$npr) { flash('error', 'Pick a client » process » facility » provider to re-tag to.'); redirect($back); }
        // Fence the TARGET at tuple granularity (honors facility/provider-level alignment; sysadmin/supervise = unrestricted).
        if (!coding_user_can_tuple($uid, $nc, $np, $nf, $npr)) { flash('error', 'You are not aligned to the target client » process » facility » provider.'); redirect($back); }
        $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', 'That client » process » facility » provider combination is not valid.'); redirect($back); }
        $pdo->prepare("UPDATE coding_inventory SET client_id=?, process_id=?, facility_id=?, provider_id=?, updated_at=NOW() WHERE id=?")
            ->execute([$nc, $np, $nf, $npr, $id]);
        coding_log_event($pdo, $id, 'audit_retag', $uid, 'Auditor re-tagged client/process/facility/provider', null, null);
        audit_log('coding.audit.retag', 'coding_inventory', $id, ['client_id'=>$ch['client_id'],'process_id'=>$ch['process_id'],'facility_id'=>$ch['facility_id'],'provider_id'=>$ch['provider_id']], ['client_id'=>$nc,'process_id'=>$np,'facility_id'=>$nf,'provider_id'=>$npr]);
        flash('success', 'Chart re-tagged.');
        redirect($back);
    }

    if ($act === 'rebuttal_resolve') {
        $id = (int) input('id');
        $note = trim((string) input('resolve_note'));
        // PER-CODE decision: $_POST['rb'][auditCodeId] = 'accept' (coder right → flip to correct, defect cleared)
        // or 'reject' (the flag stands). So the auditor can uphold SOME disputed codes and keep others as errors,
        // and the DPO accuracy recomputes from whatever remains an error.
        $decisions = (array) ($_POST['rb'] ?? []);
        $ch = $pdo->prepare("SELECT * FROM coding_inventory WHERE id=?"); $ch->execute([$id]); $ch = $ch->fetch();
        if ($ch && !$chartInScope((int) $ch['client_id'])) { flash('error', 'That chart is not in your assigned clients.'); redirect('/admin/coding-audit.php?queue=rebuttals'); }
        if ($ch && $ch['rebuttal_status'] === 'raised') {
            $pdo->beginTransaction();
            $acc = $pdo->prepare("UPDATE coding_audit_codes SET result='correct', error_category=NULL, correct_value=NULL, rebuttal_result='accepted' WHERE id=? AND inventory_id=? AND disputed=1");
            $rej = $pdo->prepare("UPDATE coding_audit_codes SET rebuttal_result='rejected' WHERE id=? AND inventory_id=? AND disputed=1");
            foreach ($decisions as $codeId => $d) {
                $codeId = (int) $codeId;
                if ((string) $d === 'accept') $acc->execute([$codeId, $id]); else $rej->execute([$codeId, $id]);
            }
            // Any disputed code the form omitted defaults to KEPT (rejected) — never leave a 'pending' dangling.
            $pdo->prepare("UPDATE coding_audit_codes SET rebuttal_result='rejected' WHERE inventory_id=? AND disputed=1 AND COALESCE(rebuttal_result,'pending')='pending'")->execute([$id]);
            coding_recompute_defects($pdo, $id);  // recompute audit_opportunities/defects/result from the surviving errors
            $cn = $pdo->prepare("SELECT COALESCE(SUM(rebuttal_result='accepted'),0) a, COALESCE(SUM(rebuttal_result='rejected'),0) r FROM coding_audit_codes WHERE inventory_id=? AND disputed=1");
            $cn->execute([$id]); $cn = $cn->fetch();
            $nAcc = (int) $cn['a']; $nRej = (int) $cn['r'];
            // If ANY error still stands after the recompute, the chart RE-OPENS to the coder's To-acknowledge queue
            // (rebuttal_status='none') so they can dispute the remaining error(s) AGAIN or agree; only a fully-clean
            // chart (no defects left) closes the rebuttal as 'accepted'.
            $arq = $pdo->prepare("SELECT audit_result FROM coding_inventory WHERE id=?"); $arq->execute([$id]);
            $stillError = ($arq->fetchColumn() === 'error');
            $decision = $stillError ? 'none' : 'accepted';
            // Refresh audited_at to the RESOLUTION date: the coder's quality (coding_quality_pct) only SUMs charts with
            // audited_at within the rolling 21-day window, so without this a rebuttal resolved after the original audit
            // aged out of the window would update audit_defects in the DB but never move the coder's displayed %.
            $pdo->prepare("UPDATE coding_inventory SET rebuttal_status=?, rebuttal_resolved_at=NOW(), audited_at=NOW(),
                            audit_comment=CONCAT(COALESCE(audit_comment,''), ?), updated_at=NOW() WHERE id=?")
                ->execute([$decision, "\n[rebuttal resolved · {$nAcc} accepted / {$nRej} kept" . ($stillError ? ' · re-opened — coder may dispute the remaining error(s)' : '') . "] " . $note, $id]);
            coding_log_event($pdo, $id, 'rebuttal_resolve', $uid, $note, 'raised', $decision);
            $pdo->commit();
            audit_log('coding.rebuttal.resolve', 'coding_inventory', $id, null, ['accepted'=>$nAcc,'rejected'=>$nRej]);
            flash('success', "Rebuttal resolved — {$nAcc} accepted, {$nRej} kept as error. Accuracy recomputed." . ($stillError ? ' The coder can dispute the remaining error(s) again.' : ''));
            // Auto-advance to the NEXT raised rebuttal in scope (auditor flows chart-to-chart), else back to the list.
            $canSup = can('coding.supervise');
            $nx = $pdo->prepare("SELECT id FROM coding_inventory WHERE rebuttal_status='raised' AND id<>?" . ($canSup ? '' : ' AND auditor_id=?') . $clientFenceSql('client_id') . " ORDER BY rebuttal_at ASC LIMIT 1");
            $nx->execute($canSup ? [$id] : [$id, $uid]);
            $nextId = (int) $nx->fetchColumn();
            redirect($nextId ? ('/admin/coding-audit.php?id=' . $nextId) : '/admin/coding-audit.php?queue=rebuttals');
        }
        redirect('/admin/coding-audit.php?queue=rebuttals');
    }
}

/* ============================ AUDIT VIEW (?id=X) ============================ */
$id = (int) ($_GET['id'] ?? 0);
$viewCarry = audit_filter_set($_GET);                       // active queue filters to preserve across this chart
$viewCarryQs = $viewCarry ? ('?' . http_build_query($viewCarry)) : '';
if ($id) {
    $chart = $pdo->prepare("SELECT ci.*, c.name cn, p.name pn, f.name fn, pr.name uprov, cp.name coded_prov, u.name coder,
            bp.name billing_prov
        FROM coding_inventory ci
        JOIN clients c ON c.id=ci.client_id JOIN processes p ON p.id=ci.process_id
        JOIN coding_facilities f ON f.id=ci.facility_id 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 bp ON bp.id=ci.billing_provider_id
        LEFT JOIN users u ON u.id=ci.coder_id
        WHERE ci.id=?");
    $chart->execute([$id]); $chart = $chart->fetch();
    if (!$chart) { flash('error', 'Chart not found.'); redirect('/admin/coding-audit.php'); }
    if (!$chartInScope((int) $chart['client_id'])) { flash('error', 'That chart is not in your assigned clients.'); redirect('/admin/coding-audit.php'); }
    if (!in_array($chart['status'], ['completed','rfi','disregard'], true)) {
        flash('error', 'That chart is still with the coder (' . str_replace('_',' ',$chart['status']) . ') and cannot be audited yet.');
        redirect('/admin/coding-audit.php');
    }
    // READ-ONLY when the chart is RELEASED to billing (audit frozen forever) OR audited (ready_to_deliver) with no
    // open rebuttal. A raised rebuttal re-opens an UN-released chart so the auditor can respond. No role override.
    $auditLocked = !empty($chart['released_to_billing_at'])
        || ($chart['audit_status'] === 'ready_to_deliver' && ($chart['rebuttal_status'] ?? 'none') !== 'raised');
    $lines = coding_chart_lines($pdo, $id);

    // Past coded DOS for THIS patient (same facility + MRN/name), excluding this chart — shown at the bottom so the
    // auditor sees the patient's coding history in context. Reuses the coder-editor helper.
    $patHistory = coding_patient_history($pdo, (int) $chart['facility_id'], $chart['patient_mrn'] ?: null, $chart['patient_name'] ?: null, $id, 50);

    // Prior findings (for resume / re-audit) keyed by type|cptId|value.
    $prior = [];
    foreach (coding_audit_codes($pdo, $id) as $f) $prior[$f['code_type'] . '|' . ((int) $f['chart_cpt_id']) . '|' . $f['code_value']] = $f;

    // Flatten coded lines into reviewable opportunities (cpt + its modifiers + its icds),
    // grouped by the coder's date of service so the auditor reviews one DOS at a time.
    $dosBuckets = [];
    foreach ($lines as $l) {
        $cid = (int) $l['id'];
        $dk  = (string) ($l['dos'] ?? '');
        if (!isset($dosBuckets[$dk])) $dosBuckets[$dk] = ['dos' => $l['dos'] ?? null, 'opps' => []];
        $dosBuckets[$dk]['opps'][] = ['type' => 'cpt', 'value' => $l['cpt_code'], 'cpt_id' => $cid, 'units' => (int) ($l['units'] ?? 1)];
        foreach ($l['mods'] as $md) $dosBuckets[$dk]['opps'][] = ['type' => 'modifier', 'value' => $md, 'cpt_id' => $cid];
        foreach ($l['icds'] as $ic) $dosBuckets[$dk]['opps'][] = ['type' => 'icd', 'value' => $ic, 'cpt_id' => $cid];
    }
    $hasOpps = false; foreach ($dosBuckets as $b) if ($b['opps']) { $hasOpps = true; break; }
    $ageAt = function (?string $dos) use ($chart): ?int {
        if (empty($chart['patient_dob']) || !$dos) return null;
        $a = date_create($chart['patient_dob']); $b = date_create($dos);
        return ($a && $b) ? $b->diff($a)->y : null;
    };

    // Coders for the RTO (send-back) picker.
    $rtoCoders = $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') ORDER BY u.name")->fetchAll();

    // Re-tag options (G2): the auditor's accessible client»process»facility»provider combos (scoped by their
    // alignment; sysadmin/supervise = all). One flat picker → no cascade JS. Only for a not-locked chart.
    $retagCanDo = !$auditLocked && (can('coding.audit') || can('coding.supervise'));
    $retagCombos = [];
    if ($retagCanDo) {
        foreach (coding_accessible_tree($uid) as $cid => $cnode) {
            foreach (($cnode['processes'] ?? []) as $pid => $pnode) {
                foreach (($pnode['facilities'] ?? []) as $fid => $fnode) {
                    foreach (($fnode['providers'] ?? []) as $prid => $prname) {
                        $retagCombos[] = ['v' => "$cid:$pid:$fid:$prid",
                            'label' => $cnode['name'] . ' » ' . $pnode['name'] . ' » ' . $fnode['name'] . ' » ' . $prname];
                    }
                }
            }
        }
    }
    $curCombo = (int) $chart['client_id'] . ':' . (int) $chart['process_id'] . ':' . (int) $chart['facility_id'] . ':' . (int) $chart['provider_id'];

    $pageTitle = 'Audit chart';
    require __DIR__ . '/../includes/header.php';
    ?>
    <link rel="stylesheet" href="<?= e(url('/admin/coding-ui.css')) ?>">
    <div class="cw">
    <div class="container-xl">
        <div class="cw-banner" style="--cw-accent:#0ea5e9">
            <div class="avatar-pt" style="background:linear-gradient(135deg,#0ea5e9,#4f46e5)"><?= e(strtoupper(substr($chart['patient_name'] ?: 'C', 0, 2))) ?></div>
            <div class="cw-bn-main">
                <div class="cw-bn-top">
                    <span class="pt-name"><?= e($chart['patient_name']) ?></span>
                    <span class="cw-bn-pills">
                        <?php if ($chart['in_sample']): ?><span class="cw-pill bg-purple-lt">sampled <?= (int)$chart['sample_pct'] ?>%</span><?php endif; ?>
                        <span class="cw-pill bg-secondary-lt">coder: <?= e(str_replace('_',' ',$chart['status'])) ?></span>
                        <a href="<?= e(url('/admin/coding-audit.php' . $viewCarryQs)) ?>" class="btn btn-sm btn-outline-secondary"><i class="ti ti-arrow-left me-1"></i>Queue</a>
                        <?php if (!$auditLocked && (can('coding.audit') || can('coding.supervise'))): $auditBack = '/admin/coding-audit.php?' . http_build_query(array_merge($viewCarry, ['id' => $id])); ?>
                        <a href="<?= e(url('/admin/coding-chart.php?' . http_build_query(['id' => $id, 'audit_edit' => 1, 'back' => $auditBack]))) ?>" class="btn btn-sm btn-warning" title="Correct the coder's demographics, providers or codes in the full chart editor"><i class="ti ti-edit me-1"></i>Edit chart</a>
                        <?php endif; ?>
                    </span>
                </div>
                <div class="pt-sub"><?= e($chart['cn']) ?> · <?= e($chart['fn']) ?> · coded by <strong><?= e($chart['coder'] ?? '—') ?></strong> · <?= e(fmt_dt($chart['submitted_at'])) ?></div>
                <div class="pt-fields">
                    <div class="cw-fld"><span class="l">MRN / PRN</span><span class="v"><?= e($chart['patient_mrn'] ?: '—') ?></span></div>
                    <div class="cw-fld"><span class="l">DOB</span><span class="v"><?= e(fmt_date($chart['patient_dob'])) ?></span></div>
                    <div class="cw-fld"><span class="l">Gender</span><span class="v"><?= e($chart['gender'] ?: '—') ?></span></div>
                    <div class="cw-fld"><span class="l">Billing provider</span><span class="v"><?= e($chart['billing_prov'] ?: $chart['coded_prov'] ?: $chart['uprov']) ?></span></div>
                    <div class="cw-fld"><span class="l">PCP</span><span class="v"><?= $chart['pcp_flag']?'Yes':'No' ?></span></div>
                </div>
            </div>
        </div>
    </div>
    <?php if ($retagCanDo && $retagCombos): ?>
    <div class="container-xl mb-2"><div class="card"><div class="card-body py-2 d-flex flex-wrap align-items-center gap-2">
        <span class="text-secondary small"><i class="ti ti-map-pin me-1"></i>Tagged as: <strong><?= e($chart['cn'] . ' » ' . $chart['pn'] . ' » ' . $chart['fn'] . ' » ' . ($chart['uprov'] ?? '')) ?></strong></span>
        <form method="POST" action="<?= e(url('/admin/coding-audit.php')) ?>" class="d-flex align-items-center gap-2 ms-auto flex-wrap">
            <?= csrf_field() ?><input type="hidden" name="action" value="retag"><input type="hidden" name="id" value="<?= $id ?>">
            <?php foreach ($viewCarry as $ck => $cv): ?><input type="hidden" name="<?= e($ck) ?>" value="<?= e($cv) ?>"><?php endforeach; ?>
            <span class="text-secondary small text-nowrap">Re-tag to:</span>
            <select name="retag_combo" class="form-select form-select-sm" style="min-width:340px">
                <?php foreach ($retagCombos as $rc): ?><option value="<?= e($rc['v']) ?>" <?= $rc['v'] === $curCombo ? 'selected' : '' ?>><?= e($rc['label']) ?></option><?php endforeach; ?>
            </select>
            <button class="btn btn-sm btn-outline-warning text-nowrap" onclick="return confirm('Re-tag this chart to the selected client » process » facility » provider?');"><i class="ti ti-arrows-exchange me-1"></i>Re-tag</button>
        </form>
    </div></div></div>
    <?php endif; ?>
    <?php
    $catOpts = function ($sel = '') { $h = '<option value="">— error category —</option>';
        foreach (CODING_AUDIT_ERROR_CATEGORIES as $c) $h .= '<option' . ($sel === $c ? ' selected' : '') . '>' . e($c) . '</option>'; return $h; };
    $typeColor = ['cpt' => 'blue', 'icd' => 'cyan', 'modifier' => 'secondary'];
    ?>
    <form method="POST" action="<?= e(url('/admin/coding-audit.php')) ?>" id="auditForm">
    <?= csrf_field() ?><input type="hidden" name="action" value="audit"><input type="hidden" name="id" value="<?= $id ?>"><input type="hidden" name="reseq" id="reseq">
    <?php foreach ($viewCarry as $ck => $cv): ?><input type="hidden" name="<?= e($ck) ?>" value="<?= e($cv) ?>"><?php endforeach; ?><!-- carry queue filters so auto-advance stays in the same set -->
    <div class="page-body"><div class="container-xl"><div class="row">
        <div class="col-lg-8">
            <div class="card" id="codeAuditCard">
                <div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-list-check me-1"></i>Code-level audit (DPO)</h3>
                    <div class="card-actions d-flex align-items-center gap-2">
                        <span id="dpoChip" class="cw-dpo-chip dpo-good" title="DPO accuracy — updates live as you mark codes">
                            <i class="ti ti-target"></i> <strong id="dpo_acc">100%</strong>
                            <span class="cw-dpo-od"><span id="dpo_def" class="fw-bold">0</span>/<span id="dpo_opp">0</span> def</span></span>
                        <button type="button" class="btn btn-sm btn-outline-primary" id="addMissing"><i class="ti ti-plus me-1"></i>Add missing code</button>
                    </div>
                </div>
                <div class="card-body">
                    <div id="audRows">
                    <?php $k = 0; foreach ($dosBuckets as $b): if (!$b['opps']) continue; $ag = $ageAt($b['dos']); ?>
                    <div class="aud-dos-group">
                        <div class="aud-dos-head"><i class="ti ti-calendar-event me-1"></i>DOS <strong><?= $b['dos'] ? e(fmt_date($b['dos'])) : '—' ?></strong>
                            <span class="aud-dos-age">Age <?= $ag!==null?(int)$ag:'—' ?></span></div>
                        <?php foreach ($b['opps'] as $o):
                            $key = $o['type'] . '|' . $o['cpt_id'] . '|' . $o['value']; $p = $prior[$key] ?? null;
                            $res = $p['result'] ?? 'correct'; $bad = $res !== 'correct'; ?>
                            <div class="aud-row" data-k="<?= $k ?>">
                                <input type="hidden" name="aud[<?= $k ?>][code_type]" value="<?= e($o['type']) ?>">
                                <input type="hidden" name="aud[<?= $k ?>][code_value]" value="<?= e($o['value']) ?>">
                                <input type="hidden" name="aud[<?= $k ?>][chart_cpt_id]" value="<?= (int)$o['cpt_id'] ?>">
                                <span class="code-tag <?= $o['type']==='modifier'?'mod':e($o['type']) ?>"><?= $o['type']==='modifier'?'MOD':e(strtoupper($o['type'])) ?></span>
                                <code class="aud-code"><?= e($o['value']) ?></code><?php if ($o['type']==='cpt' && isset($o['units'])): ?><span class="badge bg-secondary-lt ms-1" title="Units coded">×<?= (int)$o['units'] ?></span><?php endif; ?>
                                <div class="btn-group btn-group-sm seg">
                                    <input type="radio" class="btn-check res" name="aud[<?= $k ?>][result]" id="r<?= $k ?>c" value="correct" <?= $bad?'':'checked' ?>>
                                    <label class="btn btn-outline-success" for="r<?= $k ?>c" title="Correct"><i class="ti ti-check"></i></label>
                                    <input type="radio" class="btn-check res" name="aud[<?= $k ?>][result]" id="r<?= $k ?>e" value="incorrect" <?= $bad?'checked':'' ?>>
                                    <label class="btn btn-outline-danger" for="r<?= $k ?>e" title="Error"><i class="ti ti-x"></i></label>
                                </div>
                                <div class="aud-detail" style="<?= $bad?'':'display:none' ?>">
                                    <select name="aud[<?= $k ?>][error_category]" class="form-select form-select-sm aud-cat"><?= $catOpts($p['error_category'] ?? '') ?></select>
                                    <?php if ($o['type']==='modifier'): ?>
                                    <select name="aud[<?= $k ?>][correct_value]" class="form-select form-select-sm cc-sel"><option value="">→ correct</option>
                                        <?php foreach (CODING_MODIFIERS as $m): ?><option <?= ($p['correct_value']??'')===$m?'selected':'' ?>><?= e($m) ?></option><?php endforeach; ?></select>
                                    <?php else: ?>
                                    <span class="ac-box cc-box"><input name="aud[<?= $k ?>][correct_value]" data-ct="<?= e($o['type']) ?>" class="form-control form-control-sm cc-in code-input" placeholder="→ correct <?= e(strtoupper($o['type'])) ?>" value="<?= e($p['correct_value'] ?? '') ?>" autocomplete="off"></span>
                                    <?php endif; ?>
                                    <input name="aud[<?= $k ?>][note]" class="form-control form-control-sm aud-note" placeholder="note" value="<?= e($p['note'] ?? '') ?>">
                                </div>
                                <?php if ($o['type']!=='cpt'): ?>
                                <span class="aud-move ms-auto" title="Reorder within this CPT">
                                    <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-up"><i class="ti ti-chevron-up"></i></button>
                                    <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-dn"><i class="ti ti-chevron-down"></i></button>
                                </span>
                                <?php endif; ?>
                                <?php if (!empty($p['disputed'])): /* D: the coder's per-error rebuttal reason, visible to the auditor on re-audit */ ?>
                                <div class="aud-dispute"><i class="ti ti-message-report me-1 text-warning"></i><strong>Coder disputes:</strong> <?= e($p['dispute_reason'] ?: '(no reason given)') ?><?php if (!empty($p['rebuttal_result']) && $p['rebuttal_result'] !== 'pending'): ?> <span class="badge bg-<?= $p['rebuttal_result']==='accepted'?'success':'danger' ?>-lt"><?= e($p['rebuttal_result']) ?></span><?php endif; ?></div>
                                <?php endif; ?>
                            </div>
                        <?php $k++; endforeach; ?>
                    </div>
                    <?php endforeach; ?>
                        <div id="audMissing">
                        <?php /* D: re-render the auditor's own previously-ADDED missing codes so they are visible & editable on re-audit (they live in coding_audit_codes with result='missing', not in the coder's lines). */
                        foreach ($prior as $pf): if (($pf['result'] ?? '') !== 'missing') continue; ?>
                            <div class="aud-row bad" data-k="<?= $k ?>">
                                <input type="hidden" name="aud[<?= $k ?>][code_type]" value="<?= e($pf['code_type']) ?>" class="m-type">
                                <input type="hidden" name="aud[<?= $k ?>][code_value]" value="<?= e($pf['code_value']) ?>" class="m-val">
                                <input type="hidden" name="aud[<?= $k ?>][result]" value="missing">
                                <input type="hidden" name="aud[<?= $k ?>][chart_cpt_id]" value="<?= (int)$pf['chart_cpt_id'] ?>" class="m-cpt">
                                <span class="code-tag" style="color:#f59e0b;border-color:rgba(245,158,11,.4)" title="Code the auditor added as missing">MISSING</span>
                                <select class="form-select form-select-sm m-typesel" style="width:auto">
                                    <option value="cpt" <?= $pf['code_type']==='cpt'?'selected':'' ?>>CPT</option>
                                    <option value="icd" <?= $pf['code_type']==='icd'?'selected':'' ?>>ICD</option>
                                    <option value="modifier" <?= $pf['code_type']==='modifier'?'selected':'' ?>>Mod</option>
                                </select>
                                <select class="form-select form-select-sm m-parent" style="width:auto;display:none" title="Which coded CPT this added diagnosis / modifier belongs to"><option value="0">— under CPT —</option></select>
                                <span class="ac-box"><input class="form-control form-control-sm text-uppercase m-codein cc-in" data-ct="<?= e($pf['code_type']) ?>" style="width:120px" value="<?= e($pf['code_value']==='(missing)'?'':$pf['code_value']) ?>" placeholder="missed code"></span>
                                <div class="aud-detail" style="display:flex">
                                    <select name="aud[<?= $k ?>][error_category]" class="form-select form-select-sm aud-cat"><?= $catOpts($pf['error_category'] ?? '') ?></select>
                                    <input name="aud[<?= $k ?>][note]" class="form-control form-control-sm aud-note" placeholder="note" value="<?= e($pf['note'] ?? '') ?>">
                                    <span class="aud-move ms-auto" title="Reorder this added code — sets its billing order">
                                        <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-up"><i class="ti ti-chevron-up"></i></button>
                                        <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-dn"><i class="ti ti-chevron-down"></i></button>
                                    </span>
                                    <button type="button" class="btn btn-sm btn-ghost-danger rmMiss"><i class="ti ti-trash"></i></button>
                                </div>
                                <?php if (!empty($pf['disputed'])): ?><div class="aud-dispute"><i class="ti ti-message-report me-1 text-warning"></i><strong>Coder disputes:</strong> <?= e($pf['dispute_reason'] ?: '(no reason given)') ?></div><?php endif; ?>
                            </div>
                        <?php $k++; endforeach; ?>
                        </div>
                    </div>
                    <?php if (!$hasOpps): ?><div class="text-secondary text-center py-3" id="noCodes">No coder codes entered (chart was <?= e($chart['status']) ?>). Use <strong>Add missing code</strong> and the outcome on the right.</div><?php endif; ?>
                </div>
            </div>
        </div>

        <div class="col-lg-4"><div class="cw-rail">
            <?php if (($chart['rebuttal_status'] ?? 'none') === 'raised'): /* coder dispute open → resolve it RIGHT HERE, then auto-advance to the next rebuttal */ ?>
            <div class="card mb-2 border-warning">
                <div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-message-report me-1 text-warning"></i>Coder rebuttal</h3></div>
                <div class="card-body">
                    <div class="small text-secondary mb-2">Decide <strong>each disputed code</strong> below — <strong>Accept</strong> (the coder was right → the code flips to correct and its defect is removed) or <strong>Keep error</strong> (the flag stands). Accuracy recomputes from what remains.</div>
                    <?php if (trim((string)($chart['rebuttal_comment'] ?? '')) !== ''): ?>
                    <div class="cw-codernote mb-2"><div class="form-label mb-1">Coder's comment</div><div class="cw-codernote-body small"><?= nl2br(e($chart['rebuttal_comment'])) ?></div></div>
                    <?php endif; ?>
                    <?php
                        // The disputed codes — one Accept/Keep decision each. Controls target #rbResolveForm via the HTML5 form= attribute.
                        $dcodes = $pdo->prepare("SELECT id, code_type, code_value, error_category, correct_value, dispute_reason
                            FROM coding_audit_codes WHERE inventory_id=? AND disputed=1 AND result<>'correct' ORDER BY FIELD(code_type,'cpt','modifier','icd'), code_value");
                        $dcodes->execute([$id]); $dcodes = $dcodes->fetchAll();
                    ?>
                    <?php foreach ($dcodes as $dc): $lbl = strtoupper($dc['code_type']==='modifier' ? 'MOD' : (string)$dc['code_type']); $rid=(int)$dc['id']; ?>
                    <div class="border rounded p-2 mb-2">
                        <div><span class="badge bg-secondary-lt"><?= e($lbl) ?></span> <strong><?= e($dc['code_value']) ?></strong>
                            <?php if (trim((string)$dc['error_category']) !== ''): ?><span class="text-danger small">· <?= e($dc['error_category']) ?><?= trim((string)$dc['correct_value']) !== '' ? ' → '.e($dc['correct_value']) : '' ?></span><?php endif; ?></div>
                        <?php if (trim((string)$dc['dispute_reason']) !== ''): ?><div class="text-secondary small mt-1"><i class="ti ti-message-report text-warning me-1"></i><?= e($dc['dispute_reason']) ?></div><?php endif; ?>
                        <div class="btn-group btn-group-sm w-100 mt-1" role="group">
                            <input type="radio" class="btn-check" name="rb[<?= $rid ?>]" value="reject" id="rb_r<?= $rid ?>" form="rbResolveForm" checked>
                            <label class="btn btn-outline-danger" for="rb_r<?= $rid ?>"><i class="ti ti-x me-1"></i>Keep error</label>
                            <input type="radio" class="btn-check" name="rb[<?= $rid ?>]" value="accept" id="rb_a<?= $rid ?>" form="rbResolveForm">
                            <label class="btn btn-outline-success" for="rb_a<?= $rid ?>"><i class="ti ti-check me-1"></i>Accept</label>
                        </div>
                    </div>
                    <?php endforeach; ?>
                    <label class="form-label">Resolution note</label>
                    <textarea name="resolve_note" form="rbResolveForm" class="form-control mb-2" rows="2" placeholder="Optional — a note on your decision"></textarea>
                    <div class="d-grid">
                        <button type="submit" form="rbResolveForm" class="btn btn-primary" name="decision" value="resolve" onclick="return confirm('Resolve this rebuttal? Accepted codes flip to correct and the accuracy recomputes.');"><i class="ti ti-gavel me-1"></i>Resolve rebuttal</button>
                    </div>
                    <div class="form-hint mt-1">Accuracy recomputes from the codes you keep as errors. The next rebuttal opens automatically.</div>
                </div>
            </div>
            <?php endif; ?>
            <?php if (($chart['rebuttal_status'] ?? 'none') !== 'raised'): /* OUTCOME hidden while resolving a rebuttal — resolution happens in the Coder rebuttal card above */ ?>
            <div class="card"><div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-gavel me-1"></i>Outcome</h3></div>
                <div class="card-body">
                    <?php if ($auditLocked): ?>
                    <div class="alert alert-warning py-2 mb-2"><i class="ti ti-lock me-1"></i><?php if (!empty($chart['released_to_billing_at'])): ?><strong>Released to billing.</strong> This chart's audit is frozen and can no longer be changed.<?php else: ?><strong>Audited &amp; locked.</strong> This chart re-opens for re-audit only after the coder raises a rebuttal.<?php endif; ?></div>
                    <?php endif; ?>
                    <div class="cw-codernote mb-2">
                        <div class="d-flex align-items-center mb-1"><span class="form-label mb-0">Coder comment</span>
                            <?php if (trim((string)$chart['comment']) !== ''): ?><button type="button" id="copyCoderNote" class="btn btn-xs btn-outline-secondary ms-auto" title="Copy the coder's comment into your comment box to edit"><i class="ti ti-copy me-1"></i>Copy ↓</button><?php endif; ?></div>
                        <div class="cw-codernote-body small"><?= trim((string)$chart['comment']) !== '' ? nl2br(e($chart['comment'])) : '<span class="text-secondary">— none —</span>' ?></div>
                    </div>
                    <!-- Comment FIRST, then the three outcome buttons below it -->
                    <label class="form-label">Your comment <span class="text-secondary fw-normal">(auditor)</span></label>
                    <textarea name="audit_comment" id="a_comment" class="form-control mb-2" rows="3" placeholder="Your audit note — edit freely after copying the coder's comment"><?= e($chart['audit_comment']) ?></textarea>
                    <!-- RTO target — used only by the "Return to coder" button below -->
                    <div id="a_rtoWrap" class="mb-2"><label class="form-label small mb-1">If returning to the coder, send to</label>
                        <select name="rto_coder" class="form-select form-select-sm">
                            <?php foreach ($rtoCoders as $rc): ?><option value="<?= (int)$rc['id'] ?>" <?= (int)($chart['coder_id'] ?: $chart['assigned_to'])===(int)$rc['id']?'selected':'' ?>><?= e($rc['name']) ?></option><?php endforeach; ?>
                        </select></div>
                    <!-- Three independent outcome buttons. Clicking one turns it green and submits the audit, moving the
                         chart to the matching bucket: stays in queue / finalizes (Audited) / returns to the coder. -->
                    <div class="cw-outcome-btns">
                        <button type="submit" name="audit_status" value="in_progress" class="cw-outcome" title="SAVE — save progress; stays in the Audit queue">SAVE</button>
                        <button type="submit" name="audit_status" value="ready_to_deliver" class="cw-outcome" title="Ready to Deliver — finalize → moves to the Audited tab">Ready to Deliver</button>
                        <button type="submit" name="audit_status" value="rto" class="cw-outcome" formnovalidate title="RTO — Return to coder; audit findings cleared">RTO</button>
                    </div>
                </div>
            </div>
            <?php endif; ?>
        </div><!-- /.cw-rail -->
    </div>
    </div></div></div>
    </form>
    <?php if (($chart['rebuttal_status'] ?? 'none') === 'raised'): /* rebuttal-resolve form lives OUTSIDE #auditForm (no nested forms); the rail controls target it via form="rbResolveForm" */ ?>
    <form id="rbResolveForm" method="POST" action="<?= e(url('/admin/coding-audit.php')) ?>"><?= csrf_field() ?>
        <input type="hidden" name="action" value="rebuttal_resolve"><input type="hidden" name="id" value="<?= $id ?>">
    </form>
    <?php endif; ?>
    </div><!-- /.cw -->

    <!-- ===================== Past coded DOS for this patient ===================== -->
    <div class="page-body"><div class="container-xl">
        <div class="card">
            <div class="card-header py-2"><h3 class="card-title mb-0"><i class="ti ti-history me-1"></i>Past coded DOS for this patient
                <span class="text-secondary fw-normal small">(<?= count($patHistory) ?>)</span></h3></div>
            <?php if (!$patHistory): ?>
                <div class="card-body text-secondary py-3">No prior coded charts for this patient at this facility.</div>
            <?php else: ?>
                <div class="table-responsive"><table class="table table-sm table-vcenter card-table" style="font-size:.8rem">
                    <thead><tr><th class="text-nowrap">DOS</th><th>Provider</th><th>Status</th><th>CPT</th><th class="text-center">Units</th><th>Modifier(s)</th><th>ICD(s)</th><th class="text-end">Accuracy</th></tr></thead>
                    <tbody>
                    <?php foreach ($patHistory as $h): $hl = $h['lines'] ?? []; ?>
                        <tr>
                            <td class="text-nowrap"><?= e(fmt_date($h['dos'] ?? null)) ?: '—' ?></td>
                            <td class="small"><?= e($h['billing_provider'] ?: ($h['provider'] ?: '—')) ?></td>
                            <td><span class="badge bg-secondary-lt"><?= e(str_replace('_', ' ', (string)($h['status'] ?? ''))) ?></span><?php if (!empty($h['audit_status'])): ?> <span class="badge bg-blue-lt"><?= e(str_replace('_', ' ', (string)$h['audit_status'])) ?></span><?php endif; ?></td>
                            <td><?php foreach ($hl as $ln): ?><div><strong><?= e($ln['cpt'] ?? '') ?></strong></div><?php endforeach; ?><?= $hl ? '' : '<span class="text-secondary">—</span>' ?></td>
                            <td class="text-center"><?php foreach ($hl as $ln): ?><div><?= (int)($ln['units'] ?? 1) ?></div><?php endforeach; ?></td>
                            <td class="small"><?php foreach ($hl as $ln): ?><div><?= e(implode(', ', $ln['mods'] ?? [])) ?: '<span class="text-secondary">—</span>' ?></div><?php endforeach; ?></td>
                            <td class="small"><?php foreach ($hl as $ln): ?><div><?= e(implode(', ', $ln['icds'] ?? [])) ?: '<span class="text-secondary">—</span>' ?></div><?php endforeach; ?></td>
                            <td class="text-end small text-secondary"><?= $h['accuracy'] !== null ? e($h['accuracy']) . '%' : '—' ?></td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table></div>
            <?php endif; ?>
        </div>
    </div></div>

    <template id="missTpl">
        <div class="aud-row bad" data-k="__K__">
            <input type="hidden" name="aud[__K__][code_type]" value="cpt" class="m-type">
            <input type="hidden" name="aud[__K__][code_value]" value="" class="m-val">
            <input type="hidden" name="aud[__K__][result]" value="missing">
            <input type="hidden" name="aud[__K__][chart_cpt_id]" value="0" class="m-cpt">
            <span class="code-tag" style="color:#f59e0b;border-color:rgba(245,158,11,.4)">MISSING</span>
            <select class="form-select form-select-sm m-typesel" style="width:auto"><option value="cpt">CPT</option><option value="icd">ICD</option><option value="modifier">Mod</option></select>
            <select class="form-select form-select-sm m-parent" style="width:auto;display:none" title="Which coded CPT this added diagnosis / modifier belongs to"><option value="0">— under CPT —</option></select>
            <span class="ac-box"><input class="form-control form-control-sm text-uppercase m-codein cc-in" data-ct="cpt" style="width:120px" placeholder="missed code"></span>
            <div class="aud-detail" style="display:flex">
                <select name="aud[__K__][error_category]" class="form-select form-select-sm aud-cat"><?= $catOpts() ?></select>
                <input name="aud[__K__][note]" class="form-control form-control-sm aud-note" placeholder="note">
                <span class="aud-move ms-auto" title="Reorder this added code — sets its billing order">
                    <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-up"><i class="ti ti-chevron-up"></i></button>
                    <button type="button" tabindex="-1" class="btn btn-xs btn-ghost-secondary mv-dn"><i class="ti ti-chevron-down"></i></button>
                </span>
                <button type="button" class="btn btn-sm btn-ghost-danger rmMiss"><i class="ti ti-trash"></i></button>
            </div>
        </div>
    </template>

    <script>
    (function(){
        var rows=document.getElementById('audRows'), nextK=<?= $k ?>;
        var API=<?= json_encode(url('/admin/coding-api.php')) ?>, AF=<?= (int)$chart['facility_id'] ?>, AP=<?= (int)($chart['coded_provider_id'] ?: $chart['provider_id']) ?>;
        var CPTS=<?= json_encode(array_map(fn($l)=>['id'=>(int)$l['id'],'label'=>$l['cpt_code'].($l['dos']?(' · '.substr((string)$l['dos'],0,10)):'')], $lines), JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_QUOT|JSON_HEX_APOS) ?>;   // B: chart CPTs for the "under CPT" picker on an added ICD / modifier
        function esc(s){ return (s==null?'':String(s)).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
        function ac(input, fetcher, render, onPick){
            var box=document.createElement('div'); box.className='ac-list';
            (input.closest('.ac-box')||input.parentNode).appendChild(box);
            var items=[], active=-1, t=null;
            function close(){ box.style.display='none'; active=-1; }
            function show(list){ items=list||[]; box.innerHTML=items.map(function(it,i){return '<div class="it" data-i="'+i+'">'+render(it)+'</div>';}).join('');
                box.style.display=items.length?'block':'none';
                [].forEach.call(box.querySelectorAll('.it'),function(e){ e.onmousedown=function(ev){ev.preventDefault(); onPick(items[+e.dataset.i]); close();}; }); }
            input.addEventListener('input',function(){ clearTimeout(t); var q=input.value.trim(); if(q.length<1){close();return;} t=setTimeout(function(){ fetcher(q).then(show); },180); });
            input.addEventListener('keydown',function(ev){ if(box.style.display==='none')return; var its=box.querySelectorAll('.it');
                if(ev.key==='ArrowDown'){ev.preventDefault();active=Math.min(active+1,its.length-1);}
                else if(ev.key==='ArrowUp'){ev.preventDefault();active=Math.max(active-1,0);}
                else if(ev.key==='Enter'){ if(active>=0){ev.preventDefault(); onPick(items[active]); close();} }
                else if(ev.key==='Escape'){close();return;}
                [].forEach.call(its,function(e,i){e.classList.toggle('active',i===active);}); });
            input.addEventListener('blur',function(){ setTimeout(close,150); });
        }
        function ccFetch(t,q){ var u=t==='cpt'?(API+'?action=cpt_search&facility_id='+AF+'&provider_id='+AP+'&q='+encodeURIComponent(q)):(API+'?action=icd_search&q='+encodeURIComponent(q));
            return fetch(u).then(function(r){return r.json();}).then(function(d){return d.items||[];}); }
        function wireCC(scope){ [].forEach.call((scope||document).querySelectorAll('.cc-in'),function(input){ if(input.dataset.w)return; input.dataset.w='1';
            ac(input, function(q){return ccFetch(input.dataset.ct||'icd', q);},
               function(it){ return (input.dataset.ct==='cpt')?('<code>'+esc(it.cpt_code)+'</code> '+esc(it.description||'')):('<code>'+esc(it.code_dotted)+'</code> '+esc(it.description||'')); },
               function(it){ input.value=(input.dataset.ct==='cpt')?it.cpt_code:it.code_dotted; var mv=input.closest('.aud-row'); if(mv){ var h=mv.querySelector('.m-val'); if(h) h.value=input.value; } }); }); }
        function recalc(){
            var all=rows.querySelectorAll('.aud-row'), opp=0, def=0;
            all.forEach(function(r){ opp++; var sel=r.querySelector('.res:checked'); var miss=r.querySelector('input[value="missing"]');
                if(miss){ def++; } else if(sel && sel.value!=='correct'){ def++; } });
            var oppEl=document.getElementById('dpo_opp'), defEl=document.getElementById('dpo_def'), accEl=document.getElementById('dpo_acc'), chip=document.getElementById('dpoChip');
            if(oppEl) oppEl.textContent=opp;
            if(defEl) defEl.textContent=def;
            var acc=opp>0?Math.round(1000*(opp-def)/opp)/10:100;
            if(accEl) accEl.textContent=acc+'%';
            if(chip){ chip.classList.remove('dpo-good','dpo-warn','dpo-bad');
                chip.classList.add(acc>=95?'dpo-good':(acc>=90?'dpo-warn':'dpo-bad')); }
        }
        rows.addEventListener('change', function(e){
            if(e.target.classList.contains('res')){ var row=e.target.closest('.aud-row'); var bad=e.target.value!=='correct';
                var d=row.querySelector('.aud-detail'); if(d) d.style.display=bad?'':'none';
                row.classList.toggle('bad',bad); row.classList.toggle('ok',!bad); }
            recalc();
        });
        function paintRows(){ [].forEach.call(rows.querySelectorAll('.aud-row'),function(row){
            var sel=row.querySelector('.res:checked'), miss=row.querySelector('input[value="missing"]');
            var bad=miss?true:(sel&&sel.value!=='correct'); row.classList.toggle('bad',!!bad); row.classList.toggle('ok',!bad); }); }
        // wire one missing-code row (shared by the Add-missing button and the server-rendered prior missing rows)
        function wireMiss(node){
            var codein=node.querySelector('.m-codein'); if(!codein) return;
            var mtype=node.querySelector('.m-type'), mcpt=node.querySelector('.m-cpt'), parent=node.querySelector('.m-parent');
            if(parent && parent.options.length<=1){ CPTS.forEach(function(c){ var o=document.createElement('option'); o.value=c.id; o.textContent=c.label; parent.appendChild(o); }); }
            if(parent && mcpt) parent.value = mcpt.value || '0';
            // a missing ICD / modifier under a CPT JOINS that CPT's code block (so it can be re-sequenced AMONG the
            // coder's codes via the per-CPT reorder); a missing CPT — or one with no parent yet — waits in #audMissing.
            function placeRow(){ var t=mtype.value, cpt=mcpt?mcpt.value:'0';
                if((t==='icd'||t==='modifier') && cpt && cpt!=='0' && moveIntoGroup(node, cpt, t)) return;
                document.getElementById('audMissing').appendChild(node); }
            function syncParent(){ if(!parent) return; var t=mtype.value;
                if(t==='cpt'){ parent.style.display='none'; parent.value='0'; if(mcpt) mcpt.value='0'; }
                else { parent.style.display=''; if(mcpt) mcpt.value=parent.value||'0'; } }
            if(parent) parent.addEventListener('change',function(){ if(mcpt) mcpt.value=this.value; placeRow(); });
            node.querySelector('.m-typesel').addEventListener('change',function(){ mtype.value=this.value; codein.dataset.ct=this.value; codein.value=''; node.querySelector('.m-val').value=''; codein.classList.remove('is-invalid'); syncParent(); placeRow(); });
            codein.addEventListener('input',function(){ node.querySelector('.m-val').value=this.value.toUpperCase(); codein.classList.remove('is-invalid'); });
            codein.addEventListener('blur',function(){
                var type=codein.dataset.ct||'icd', val=(codein.value||'').trim().toUpperCase(); if(!val) return;
                var dup=[].some.call(rows.querySelectorAll('.aud-row'), function(r){ if(r===node) return false;
                    var t=r.querySelector('input[name$="[code_type]"]'), v=r.querySelector('input[name$="[code_value]"]');
                    return t && v && t.value===type && (v.value||'').trim().toUpperCase()===val; });
                if(dup){ codein.value=''; node.querySelector('.m-val').value=''; codein.classList.add('is-invalid'); recalc();
                    if(window.mToast) mToast((type==='modifier'?'Modifier ':type.toUpperCase()+' ')+val+' is already in this audit — duplicates aren’t allowed','warning');
                    else alert(val+' is already in this audit — duplicates aren’t allowed'); }
            });
            node.querySelector('.rmMiss').addEventListener('click',function(){ node.remove(); recalc(); });
            syncParent(); placeRow();
            wireCC(node);
        }
        // move a missing-code row into its parent CPT's block (after that CPT's last same-type row) so the per-CPT
        // reorder + on-submit reseq capture interleave it with the coder's codes. Returns false if the CPT isn't found.
        function moveIntoGroup(node, cptId, type){
            var cptRow=null;
            [].forEach.call(rows.querySelectorAll('.aud-dos-group .aud-row'), function(r){ if(cptRow) return;
                var m=rowMeta(r); if(m.type==='cpt' && m.cpt===String(cptId)) cptRow=r; });
            if(!cptRow) return false;
            var after=cptRow, sib=cptRow.nextElementSibling;
            while(sib && sib.classList && sib.classList.contains('aud-row')){
                var sm=rowMeta(sib); if(sm.cpt!==String(cptId)) break;   // next CPT in this DOS group
                if(sm.type===type) after=sib;                            // last same-type row of this CPT
                sib=sib.nextElementSibling;
            }
            after.parentNode.insertBefore(node, after.nextSibling);
            return true;
        }
        document.getElementById('addMissing').addEventListener('click', function(){
            var t=document.getElementById('missTpl').innerHTML.replace(/__K__/g, nextK); nextK++;
            var wrap=document.createElement('div'); wrap.innerHTML=t.trim(); var node=wrap.firstChild; document.getElementById('audMissing').appendChild(node);
            wireMiss(node); recalc();
        });
        [].forEach.call(document.getElementById('audMissing').querySelectorAll('.aud-row'), wireMiss);   // D: wire server-rendered prior missing rows
        // B: reorder auditor-ADDED codes within the missing list — order persists (sort_order) and drives billing order.
        document.getElementById('audMissing').addEventListener('click', function(e){
            var btn=e.target.closest('.mv-up,.mv-dn'); if(!btn) return; e.preventDefault(); e.stopPropagation();
            var row=btn.closest('.aud-row'); if(!row) return; var up=btn.classList.contains('mv-up');
            var sib=up?row.previousElementSibling:row.nextElementSibling;
            if(!sib || !sib.classList || !sib.classList.contains('aud-row')) return;
            if(up) row.parentNode.insertBefore(row, sib); else row.parentNode.insertBefore(sib, row);
        });
        recalc(); paintRows(); wireCC();
        // E: clicking an outcome button turns it green, then submits — saving the audit and moving the chart to its bucket.
        [].forEach.call(document.querySelectorAll('.cw-outcome'), function(b){ b.addEventListener('click', function(){
            [].forEach.call(document.querySelectorAll('.cw-outcome'), function(o){ o.classList.remove('cw-outcome-on'); });
            b.classList.add('cw-outcome-on');
        }); });
        <?php if ($auditLocked): /* C: read-only — disable every control in the audit form */ ?>
        [].forEach.call(document.querySelectorAll('#auditForm input:not([type="hidden"]),#auditForm select,#auditForm textarea,#auditForm button'), function(e){ e.disabled=true; });
        <?php endif; ?>
        <?php if (($chart['rebuttal_status'] ?? 'none') === 'raised'): /* resolving a rebuttal: the code grid is read-only CONTEXT — resolution happens via the per-code Accept/Keep controls in the rail (which are outside #codeAuditCard) */ ?>
        [].forEach.call(document.querySelectorAll('#codeAuditCard input,#codeAuditCard select,#codeAuditCard textarea,#codeAuditCard button'), function(e){ e.disabled=true; });
        <?php endif; ?>

        /* one-click copy of the coder's comment into the auditor/validator comment box (then editable) */
        (function(){
            var coderNote = <?= json_encode((string)$chart['comment']) ?>;
            var btn = document.getElementById('copyCoderNote'), box = document.getElementById('a_comment');
            if (!btn || !box) return;
            btn.addEventListener('click', function(){
                var cur = box.value.trim();
                box.value = cur ? (cur + '\n' + coderNote) : coderNote;   // append, don't clobber an existing note
                box.focus(); box.setSelectionRange(box.value.length, box.value.length);
                if (window.mToast) mToast('Coder comment copied — edit it as needed.', 'success');
            });
        })();

        /* reorder a CPT's ICDs / modifiers (auditor correction) — swap with the adjacent same-type row of the same CPT */
        function rowMeta(r){ var t=r.querySelector('input[name$="[code_type]"]'), c=r.querySelector('input[name$="[chart_cpt_id]"]');
            return { type:t?t.value:'', cpt:c?c.value:'' }; }
        rows.addEventListener('click', function(e){
            var btn=e.target.closest('.mv-up,.mv-dn'); if(!btn) return; e.preventDefault();
            var row=btn.closest('.aud-row'); var up=btn.classList.contains('mv-up'); var me=rowMeta(row);
            var sib=up?row.previousElementSibling:row.nextElementSibling;
            if(!sib||!sib.classList||!sib.classList.contains('aud-row')) return;
            var sm=rowMeta(sib); if(sm.type!==me.type||sm.cpt!==me.cpt) return;   // only reorder within the same CPT + type
            if(up) row.parentNode.insertBefore(row, sib); else row.parentNode.insertBefore(sib, row);
        });
        /* on submit, capture the (possibly reordered) ICD/modifier sequence per CPT for the server to persist */
        document.getElementById('auditForm').addEventListener('submit', function(ev){
            // B: every ADDED ICD / modifier must hang off a coded CPT, else it never reaches billing — block submit.
            var bad=null;
            [].forEach.call(document.getElementById('audMissing').querySelectorAll('.aud-row'), function(r){ if(bad) return;
                var t=(r.querySelector('.m-type')||{}).value, cpt=(r.querySelector('.m-cpt')||{}).value, val=(r.querySelector('.m-val')||{}).value;
                if((t==='icd'||t==='modifier') && (val||'').trim()!=='' && (!cpt || cpt==='0')) bad=r; });
            if(bad){ ev.preventDefault();
                var p=bad.querySelector('.m-parent'); if(p){ p.classList.add('is-invalid'); p.focus(); }
                [].forEach.call(document.querySelectorAll('.cw-outcome'), function(o){ o.classList.remove('cw-outcome-on'); });   // un-green the clicked outcome
                if(window.mToast) mToast('Pick the CPT that each added ICD / modifier belongs to.','warning'); else alert('Pick the CPT that each added ICD / modifier belongs to.');
                return; }
            var seq={};
            [].forEach.call(rows.querySelectorAll('.aud-row'), function(r){
                var m=rowMeta(r), v=r.querySelector('input[name$="[code_value]"]');
                if(!v || !m.cpt || m.cpt==='0' || (m.type!=='icd' && m.type!=='modifier')) return;
                if(!seq[m.cpt]) seq[m.cpt]={icds:[],mods:[]};
                (m.type==='icd'?seq[m.cpt].icds:seq[m.cpt].mods).push(v.value);
            });
            document.getElementById('reseq').value=JSON.stringify(seq);
        });
    })();
    </script>
    <?php require __DIR__ . '/../includes/footer.php'; exit;
}

/* ============================ QUEUE LIST ============================ */
$queue = $_GET['queue'] ?? 'audit';
$supervisor = can('coding.supervise');

// ONE unified work queue — every coder-completed chart that hasn't gone to billing yet, EXCEPT
// RFI/Disregard (those are the Validator's, via coding-validate.php, and return to the client via RTO).
// Sampled charts (in_sample=1) are badged "Sample" and should be audited; the auditor then bulk-sends
// any/all completed charts to billing (audit optional — advisory sampling).
//   Audited tab = finalized audit results (quality reference).  Rebuttals tab = coder disputes.
$audAll = "ci.status='completed' AND COALESCE(ci.audit_status,'') NOT IN ('rfi','disregard') AND ci.released_to_billing_at IS NULL";   // all completed awaiting handoff (sampling denominator)
$queues = [
    // Audit queue = still TO audit: completed, not RFI/Disregard, NOT yet finalized (ready_to_deliver) and not released.
    // Once an auditor finalizes a chart (Ready to Deliver) it leaves this queue and shows only on the Audited tab.
    'audit'   => ["$audAll AND COALESCE(ci.audit_status,'') <> 'ready_to_deliver'", 'Audit', 'purple'],
    'audited' => ["$audAll AND ci.audit_status='ready_to_deliver'", 'Audited', 'green'],   // audited but NOT yet released (released → Deliverables)
];
if ($queue !== 'rebuttals' && !isset($queues[$queue])) $queue = 'audit';   // stale ?queue=sample/tobill/rfi/disregard → unified Audit

/* ── FILTERS + the default view window — HOISTED above the counts, deliberately ────────────────────
 * These used to be parsed further down, inside the list render, and the tab badges above counted each
 * queue with NO filters at all — so a filtered list sat under a badge describing something else
 * entirely ("Audit 200" over three rows). Parsing them here lets the badges run through the SAME
 * predicate as the rows.
 *
 * DEFAULT VIEW = today's ACTIVITY (ci.updated_at), matching coding-validate.php. updated_at, not
 * submitted_at, is the axis: a chart reaches this queue when its coder submits, but it also MOVES here
 * on a QA edit or a rebuttal, and those touch updated_at while deliberately preserving submitted_at.
 * Precedence, on VALUES: dall=1 > an explicit Completed range > today.
 *
 * NEVER goes into audit_filter_set()/$carry: audit_next_chart() builds from that array, and it is a WORK
 * DISPATCHER — it must keep handing out the oldest waiting chart regardless of what the view is showing,
 * or the backlog silently starves behind a default nobody chose. */
$fc=(int)($_GET['client_id']??0); $fp=(int)($_GET['process_id']??0); $ff=(int)($_GET['facility_id']??0);
$fpr=(int)($_GET['provider_id']??0); $fco=(int)($_GET['coder_id']??0); $fprio=(int)($_GET['priority']??0);
$fsamp=(int)($_GET['sample']??0);   // sample-only (in_sample=1) — keeps the sampling % visible
$fast = in_array($_GET['astate']??'', ['todo','prog'], true) ? $_GET['astate'] : '';  // audit state
$ffrom = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['from']??'')) ? $_GET['from'] : '';
$fto   = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['to']??''))   ? $_GET['to']   : '';
$fpt   = trim((string)($_GET['patient']??''));   // patient-name search (auto-filters as you type)
$dall   = ($_GET['dall'] ?? '') === '1';
$dToday = !$dall && $ffrom === '' && $fto === '';

/* Common filters — apply to EVERY queue. */
$cW = []; $cP = [];
if ($fc)        { $cW[]='ci.client_id=?';   $cP[]=$fc; }
if ($fp)        { $cW[]='ci.process_id=?';  $cP[]=$fp; }
if ($ff)        { $cW[]='ci.facility_id=?'; $cP[]=$ff; }
if ($fpr)       { $cW[]='COALESCE(ci.coded_provider_id,ci.provider_id)=?'; $cP[]=$fpr; }
if ($fco)       { $cW[]='ci.coder_id=?';    $cP[]=$fco; }
if ($fprio)     { $cW[]='ci.is_priority=1'; }
if ($fpt!=='')  { $cW[]='ci.patient_name LIKE ?'; $cP[]='%'.$fpt.'%'; }
if ($ffrom)     { $cW[]='ci.submitted_at>=?'; $cP[]=$ffrom.' 00:00:00'; }
if ($fto)       { $cW[]='ci.submitted_at<?';  $cP[]=date('Y-m-d',strtotime($fto.' +1 day')).' 00:00:00'; }
/* Audit-queue-only extras — sample / audit-state are meaningless on Audited + Rebuttals. */
$aW = [];
if ($fsamp)          $aW[]='ci.in_sample=1';
if ($fast==='todo')  $aW[]='ci.audit_status IS NULL';
if ($fast==='prog')  $aW[]="ci.audit_status='in_progress'";
/* The today's-activity window. Half-open [today, tomorrow) — index-friendly and DST-safe, unlike
 * DATE(ci.updated_at)=CURDATE(). */
$winW = []; $winP = [];
if ($dToday) {
    $winW[] = 'ci.updated_at>=?'; $winP[] = date('Y-m-d') . ' 00:00:00';
    $winW[] = 'ci.updated_at<?';  $winP[] = date('Y-m-d', strtotime('+1 day')) . ' 00:00:00';
}

/* Counts — run through the filters, then again WITHOUT the window, so $dHidden can say exactly how much
 * the default is holding back. Subtracting the two (rather than counting "updated_at < today") cannot
 * disagree with the badges, and stays correct for NULL stamps. */
$counts = []; $countsAll = []; $dHidden = [];
$qCount = function (array $w, array $p) use ($pdo): int {
    $st = $pdo->prepare("SELECT COUNT(*) FROM coding_inventory ci WHERE " . implode(' AND ', $w));
    $st->execute($p);
    return (int) $st->fetchColumn();
};
foreach ($queues as $k => $q) {
    $isA  = ($k === 'audit');
    $wND  = array_merge([$q[0] . $clientFenceSql('ci.client_id')], $cW, $isA ? $aW : []);
    $pND  = $cP;
    $countsAll[$k] = $qCount($wND, $pND);
    $counts[$k]    = $dToday ? $qCount(array_merge($wND, $winW), array_merge($pND, $winP)) : $countsAll[$k];
    $dHidden[$k]   = max(0, $countsAll[$k] - $counts[$k]);
}
$rbCond = ["ci.rebuttal_status='raised'" . ($supervisor ? '' : " AND ci.auditor_id=" . $uid) . $clientFenceSql('ci.client_id')];
$rebuttalCountAll = $qCount(array_merge($rbCond, $cW), $cP);
$rebuttalCount    = $dToday ? $qCount(array_merge($rbCond, $cW, $winW), array_merge($cP, $winP)) : $rebuttalCountAll;
$dHidden['rebuttals'] = max(0, $rebuttalCountAll - $rebuttalCount);

// Escape hatches. Built by REMOVING keys rather than adding overrides, so the URL never contradicts itself.
$audBase   = array_intersect_key($_GET, array_flip(['queue','client_id','process_id','facility_id','provider_id','coder_id','patient','astate','sample','priority']));
$dallUrl   = url('/admin/coding-audit.php?' . http_build_query(array_merge($audBase, ['dall'=>'1'])));
// "Back to today" must SET from/to empty, not merely omit them: remember_filters replays any saved range
// into $_GET before the window is decided, so omitting would silently land on that range instead of today.
$todayUrl  = url('/admin/coding-audit.php?' . http_build_query(array_merge($audBase, ['from'=>'', 'to'=>''])));

$pageTitle = 'Audit';
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-checklist me-2 text-primary"></i>Audit</h2></div>
    <?php /* 'dall' is in the carried keys deliberately: without it, exporting from the "Show all" view would
             silently download only today's activity — the export must mirror the view it was launched from. */ ?>
    <div class="col-auto"><a href="<?= e(url('/admin/coding-audit.php?' . http_build_query(array_merge(array_intersect_key($_GET, array_flip(['queue','client_id','process_id','facility_id','provider_id','coder_id','from','to','patient','astate','sample','priority','dall'])), ['export'=>'xlsx'])))) ?>" class="btn btn-outline-success" title="Download this view (active queue + filters) with coded lines &amp; audit verdict"><i class="ti ti-file-spreadsheet me-1"></i>Export</a></div>
</div></div></div>
<div class="page-body"><div class="container-xl">
    <?php /* Every badge is counted over the SAME windowed predicate as the rows, so every badge needs to
             admit it — not just the tab you happen to be on, which would leave the others reading a bare 0
             with work hidden behind them. */ ?>
    <?php $hidChip = function (string $k) use ($dToday, $dHidden) {
        if (!$dToday || empty($dHidden[$k])) return '';
        return '<span class="badge bg-secondary-lt ms-1 fw-normal" title="' . (int) $dHidden[$k]
             . ' older chart(s) have not moved today and are outside this window">+' . (int) $dHidden[$k] . '</span>';
    }; ?>
    <ul class="nav nav-tabs mb-3">
        <?php foreach ($queues as $k => $q): ?>
        <li class="nav-item"><a class="nav-link <?= $queue===$k?'active':'' ?>" href="<?= e(url('/admin/coding-audit.php?queue='.$k)) ?>"><?= e($q[1]) ?> <span class="badge bg-<?= $q[2] ?>-lt ms-1"><?= $counts[$k] ?></span><?= $hidChip($k) ?></a></li>
        <?php endforeach; ?>
        <li class="nav-item"><a class="nav-link <?= $queue==='rebuttals'?'active':'' ?>" href="<?= e(url('/admin/coding-audit.php?queue=rebuttals')) ?>">Rebuttals <span class="badge bg-yellow-lt ms-1"><?= $rebuttalCount ?></span><?= $hidChip('rebuttals') ?></a></li>
    </ul>

    <?php /* The today-default is a WORK-QUEUE hazard: a chart submitted yesterday and never audited drops out
             of the default view AND its badge reads 0, so a backlog could age unseen. Say so, and offer the way
             out. (Get next is unaffected — it still dispatches the oldest waiting chart.) */ ?>
    <?php $hidNow = (int) ($dHidden[$queue] ?? 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's activity</strong> — <strong><?= $hidNow ?></strong> chart<?= $hidNow===1?'':'s' ?>
            in this queue <?= $hidNow===1?'has':'have' ?> not moved today and <?= $hidNow===1?'is':'are' ?> hidden.
            <span class="text-secondary">Get next still serves the oldest waiting chart.</span></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>, not just today's activity.</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; ?>

<?php if ($queue === 'rebuttals'):
    /* Filters + today's window, IDENTICAL to what the Rebuttals badge counts ($rbCond + $cW + $winW above).
     * This list previously applied none of them while the badge counted the raw queue — with the badge now
     * filtered, letting the list ignore them would make the two disagree the moment anyone filtered.
     * clients / coding_facilities are LEFT-JOINed: the count doesn't join at all, so an INNER JOIN here would
     * drop any row whose client or facility no longer resolves and put the badge above the rows it describes. */
    $rbW = array_merge($rbCond, $cW, $winW);
    $rb = $pdo->prepare("SELECT ci.*, c.name cn, f.name fn, bp.name billing_prov, u.name coder FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers bp ON bp.id=ci.billing_provider_id
        LEFT JOIN users u ON u.id=ci.coder_id
        WHERE " . implode(' AND ', $rbW) . " ORDER BY ci.rebuttal_at ASC");
    $rb->execute(array_merge($cP, $winP));
    $rb = $rb->fetchAll();
    $rbLines = coding_effective_lines_bulk($pdo, array_column($rb, 'id'));
    // Disputed codes for the listed charts (so the auditor sees exactly what's contested).
    $disp = [];
    if ($rb) {
        $ids = array_column($rb, 'id'); $in = implode(',', array_fill(0, count($ids), '?'));
        $ds = $pdo->prepare("SELECT inventory_id, code_type, code_value, dispute_reason FROM coding_audit_codes WHERE disputed=1 AND inventory_id IN ($in) ORDER BY id");
        $ds->execute($ids);
        foreach ($ds->fetchAll() as $d) $disp[$d['inventory_id']][] = $d;
    }
?>
    <div class="card"><div class="table-responsive"><table class="table table-vcenter card-table table-sm" style="font-size:.8rem">
        <thead><tr><th>Patient</th><th>MRN / PRN</th><th>DOB</th><th>Gender</th><th>Record sign</th><th>Client</th><th>Facility</th><th>Billing prov</th><th>DOS</th><th>CPT</th><th class="text-center">Units</th><th>Modifier(s)</th><th>ICD(s)</th><th>Coder</th><th>Disputed codes</th><th>Coder's comment</th><th>Raised</th><th class="w-1"></th></tr></thead>
        <tbody>
        <?php foreach ($rb as $r): $rl = $rbLines[(int)$r['id']] ?? []; ?>
            <tr>
                <td><strong><?= e($r['patient_name']) ?></strong></td>
                <td class="small"><?= e($r['patient_mrn'] ?: '—') ?></td>
                <td class="small text-nowrap"><?= e(fmt_date($r['patient_dob'])) ?></td>
                <td class="small"><?= e(ucfirst($r['gender'] ?: '—')) ?></td>
                <td class="small text-nowrap"><?= 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['billing_prov'] ?: '—') ?></td>
                <td class="small text-nowrap"><?= coding_lines_cell($rl, 'dos') ?></td>
                <td class="small"><?= coding_lines_cell($rl, 'cpt') ?></td>
                <td class="small text-center"><?= coding_lines_cell($rl, 'units') ?></td>
                <td class="small"><?= coding_lines_cell($rl, 'mods') ?></td>
                <td class="small"><?= coding_lines_cell($rl, 'icds') ?></td>
                <td class="small"><?= e($r['coder'] ?? '—') ?></td>
                <td class="small" style="min-width:190px"><?php foreach (($disp[$r['id']] ?? []) as $d): ?>
                        <div class="mb-1"><span class="badge bg-danger-lt"><?= e(strtoupper($d['code_type'])) ?> <?= e($d['code_value']) ?></span><?php if (!empty($d['dispute_reason'])): ?> <span class="text-secondary">— <?= e($d['dispute_reason']) ?></span><?php endif; ?></div>
                    <?php endforeach; ?><?php if (empty($disp[$r['id']])): ?><span class="text-secondary">whole chart</span><?php endif; ?></td>
                <td class="small"><?= e($r['rebuttal_comment']) ?></td>
                <td class="small text-secondary text-nowrap"><?= e(fmt_dt($r['rebuttal_at'])) ?></td>
                <td>
                    <a href="<?= e(url('/admin/coding-audit.php?id='.(int)$r['id'])) ?>" class="btn btn-sm btn-success text-nowrap" title="Open the chart to review the disputed codes and accept / reject"><i class="ti ti-message-report me-1"></i>Open &amp; resolve</a>
                </td>
            </tr>
        <?php endforeach; ?>
        <?php if (!$rb): ?><tr><td colspan="18" class="text-center text-secondary py-4">No open rebuttals.</td></tr><?php endif; ?>
        </tbody>
    </table></div></div>

<?php else:
    $cond = ($queues[$queue][0] ?? $queues['audit'][0]) . $clientFenceSql('ci.client_id');   // client-assignment fence
    $isAuditQ = ($queue === 'audit');
    $listCarry = audit_filter_set($_GET);   // active filters to carry onto each row's Audit link (persist through the round-trip)
    $listCarryQs = http_build_query($listCarry);
    /* Filters are parsed ONCE, near the top, so the tab badges and these rows share one predicate and cannot
     * drift apart. $cW/$cP = every queue; $aW = audit-queue-only extras; $winW/$winP = today's activity window.
     * The window goes on the LIST only — audit_next_chart() builds from $carry and must stay unwindowed. */
    $w  = array_merge([$cond], $cW, $isAuditQ ? $aW : [], $winW);
    $pa = array_merge($cP, $winP);
    $wsql='WHERE '.implode(' AND ',$w);
    // Column sort (whitelist → SQL). Chart-level columns only (coded lines are multi-value). Default keeps priority-first.
    $ASORT=['patient'=>'ci.patient_name','mrn'=>'ci.patient_mrn','dob'=>'ci.patient_dob','gender'=>'ci.gender','rsd'=>'ci.record_sign_date','client'=>'c.name','facility'=>'f.name','billing'=>'bp.name','coder'=>'u.name','completed'=>'ci.submitted_at'];
    $asort=(string)($_GET['sort']??''); $adir=(($_GET['dir']??'')==='desc')?'DESC':'ASC';
    $aorder=isset($ASORT[$asort])?($ASORT[$asort].' '.$adir.', ci.id DESC'):'ci.is_priority DESC, ci.submitted_at ASC';
    $sortTh=function(string $key,string $label) use($asort,$adir,$listCarry){
        $isCur=($asort===$key); $nd=($isCur&&$adir==='ASC')?'desc':'asc';
        $ico=$isCur?($adir==='ASC'?'ti-caret-up-filled':'ti-caret-down-filled'):'ti-arrows-sort';
        $u='/admin/coding-audit.php?'.http_build_query(array_merge($listCarry,['sort'=>$key,'dir'=>$nd]));
        return '<th data-col="'.e($key).'" data-collabel="'.e($label).'"><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, f.name fn, pr.name prn, bp.name billing_prov, u.name coder, au.name auditor
        FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id
        JOIN coding_facilities f ON f.id=ci.facility_id JOIN coding_providers pr ON pr.id=ci.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
        $wsql ORDER BY $aorder LIMIT 500");
    $rows->execute($pa); $rows=$rows->fetchAll();
    $linesByChart = coding_effective_lines_bulk($pdo, array_column($rows, 'id'));
    $clients=$pdo->query("SELECT DISTINCT c.id,c.name FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id WHERE $cond ORDER BY c.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 WHERE $cond 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) WHERE $cond ORDER BY pr.name")->fetchAll();
    $coders=$pdo->query("SELECT DISTINCT u.id,u.name FROM coding_inventory ci JOIN users u ON u.id=ci.coder_id WHERE $cond ORDER BY u.name")->fetchAll();
    $auditMode = $queue !== 'audited';
?>
    <div class="card mb-3"><div class="card-body py-2"><form method="GET" class="row g-2 align-items-end">
        <input type="hidden" name="queue" value="<?= e($queue) ?>">
        <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 — and without this, Apply from the "Show all" view silently re-armed the today-window.
                 Emitted only when set, so a fresh visit still lands on today. */ ?>
        <?php if ($dall): ?><input type="hidden" name="dall" value="1"><?php endif; ?>
        <?php $selOpt=function($name,$opts,$cur,$allLbl){ $h='<select name="'.e($name).'" class="form-select form-select-sm"><option value="">'.e($allLbl).'</option>';
            foreach($opts as $o) $h.='<option value="'.(int)$o['id'].'"'.($cur===(int)$o['id']?' selected':'').'>'.e($o['name']).'</option>'; return $h.'</select>'; }; ?>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Client</label><?= $selOpt('client_id',$clients,$fc,'All clients') ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Facility</label><?= $selOpt('facility_id',$facils,$ff,'All facilities') ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Provider</label><?= $selOpt('provider_id',$provs,$fpr,'All providers') ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Coder</label><?= $selOpt('coder_id',$coders,$fco,'All coders') ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Patient</label><input type="search" name="patient" value="<?= e($fpt) ?>" class="form-control form-control-sm" placeholder="type a name…" autocomplete="off" oninput="clearTimeout(window.__auptmr);window.__auptmr=setTimeout(()=>this.form.submit(),450);"></div>
        <?php /* "Completed", not "Submitted": this pair filters ci.submitted_at — the SAME field as the
                 "Completed" column in the table and the "Chart completed" export header. It was labelled
                 "Submitted from" here, so the one date on the page carried two different names and the filter
                 didn't look like the one people were after. Matches coding-validate.php's wording too. */ ?>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Completed from</label><input type="date" name="from" class="form-control form-control-sm" value="<?= e($ffrom) ?>"></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">to</label><input type="date" name="to" class="form-control form-control-sm" value="<?= e($fto) ?>"></div>
        <?php if($isAuditQ): ?>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Audit status</label>
            <select name="astate" class="form-select form-select-sm">
                <option value="">All</option>
                <option value="todo"<?=$fast==='todo'?' selected':''?>>Not audited</option>
                <option value="prog"<?=$fast==='prog'?' selected':''?>>In progress</option>
            </select></div>
        <div class="col-auto"><label class="form-check form-switch"><input class="form-check-input" type="checkbox" name="sample" value="1" <?=$fsamp?'checked':''?>><span class="form-check-label small">Sample only</span></label></div>
        <?php endif; ?>
        <div class="col-auto"><label class="form-check form-switch"><input class="form-check-input" type="checkbox" name="priority" value="1" <?=$fprio?'checked':''?>><span class="form-check-label small">Priority only</span></label></div>
        <div class="col-auto"><button class="btn btn-sm btn-primary"><i class="ti ti-filter me-1"></i>Apply</button>
            <a href="<?= e(url('/admin/coding-audit.php?queue='.$queue.'&clear=1')) ?>" class="btn btn-sm btn-ghost-secondary">Clear</a></div>
        <div class="col-auto ms-auto align-self-center text-secondary small"><strong><?= count($rows) ?></strong> chart(s)</div>
    </form></div></div>

    <?php
    $canRelease = false;   // release moved to Deliverables (coding-deliverables.php) — Audit no longer sends to billing
    $wrapForm   = $isAuditQ && $canRelease;   // (always false now) — collapses the old "send to billing" checkboxes/toolbar
    /* Sampling % stays intact & visible: how many completed charts are still flagged for audit. */
    if ($isAuditQ) { $sc = $pdo->query("SELECT COUNT(*) t, COALESCE(SUM(ci.in_sample=1),0) s, COALESCE(SUM(ci.in_sample=1 AND COALESCE(ci.audit_status,'')<>'ready_to_deliver'),0) todo FROM coding_inventory ci WHERE $audAll" . $clientFenceSql('ci.client_id'))->fetch(); }
    ?>

    <?php /* "Audited" tab keeps its one-click "release all ready-to-deliver". */
    if ($queue==='audited' && $canRelease):
        $elig = array_values(array_filter($rows, fn($r)=>$r['audit_status']==='ready_to_deliver' && empty($r['released_to_billing_at'])));
        if ($elig): ?>
        <form method="POST" class="mb-2"><?= csrf_field() ?><input type="hidden" name="action" value="release_to_billing"><input type="hidden" name="queue" value="audited">
            <?php foreach ($elig as $r): ?><input type="hidden" name="ids[]" value="<?= (int)$r['id'] ?>"><?php endforeach; ?>
            <button class="btn btn-sm btn-success" onclick="return confirm('Release <?= count($elig) ?> ready-to-deliver chart(s) to the billing team?');"><i class="ti ti-send me-1"></i>Release all ready (<?= count($elig) ?>) to billing</button>
            <span class="text-secondary small ms-2">RFI / Disregard charts are excluded — they return to the client via RTO.</span>
        </form>
    <?php endif; endif; ?>

    <?php if ($isAuditQ): ?>
        <div class="alert alert-light border d-flex flex-wrap align-items-center gap-2 py-2 mb-2 small">
            <span><i class="ti ti-info-circle me-1 text-purple"></i>All <strong>completed</strong> charts land here. Audit the <span class="badge bg-purple-lt"><i class="ti ti-target-arrow"></i> Sample</span> charts (auditing the rest is optional). Releasing audited charts to billing now happens on the <strong>Deliverables</strong> page. RFI / Disregard never appear here.</span>
            <?php if(!empty($sc)): $pct=(int)$sc['t']>0?round(100*(int)$sc['s']/(int)$sc['t']):0; ?>
            <span class="ms-auto text-secondary"><i class="ti ti-target-arrow me-1"></i>Audit sample: <strong><?= (int)$sc['s'] ?></strong> of <?= (int)$sc['t'] ?> completed (<?= $pct ?>%)<?php if((int)$sc['todo']): ?> · <strong class="text-orange"><?= (int)$sc['todo'] ?></strong> still to audit<?php endif; ?></span>
            <?php endif; ?>
        </div>
    <?php endif; ?>

    <?php if ($wrapForm): ?>
    <form method="POST" id="billForm"><?= csrf_field() ?><input type="hidden" name="action" value="release_to_billing"><input type="hidden" name="queue" value="audit">
    <div class="d-flex flex-wrap gap-2 align-items-center mb-2">
        <button type="submit" class="btn btn-sm btn-success" onclick="if(!document.querySelector('.billchk:checked')){alert('Select at least one chart first.');return false;}return confirm('Send the selected chart(s) to the billing team?');"><i class="ti ti-send me-1"></i>Send selected to billing</button>
        <button type="submit" class="btn btn-sm btn-outline-success" onclick="document.querySelectorAll('.billchk').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>Audit is optional — completed charts can be billed without an audit.</span>
    </div>
    <?php endif; ?>
    <div class="card"><div class="table-responsive"><table class="table table-vcenter card-table table-sm" style="font-size:.8rem" data-coltweak="coding-audit-<?= e($queue) ?>">
        <thead><tr>
            <?php if($wrapForm):?><th class="w-1" data-colfixed><input type="checkbox" id="billAllChk" 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('gender','Gender') ?><?= $sortTh('rsd','Record sign') ?><?= $sortTh('client','Client') ?><?= $sortTh('facility','Facility') ?><?= $sortTh('billing','Billing prov') ?><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><?= $sortTh('coder','Coder') ?><?= $sortTh('completed','Completed') ?>
            <?php if($isAuditQ):?><th data-col="sample">Sample</th><th data-col="auditst">Audit</th><?php endif;?>
            <?php if($queue==='audited'):?><th data-col="outcome">Outcome</th><th data-col="accuracy">Accuracy (DPO)</th><th data-col="auditor">Auditor</th><th data-col="auditcmt" data-collabel="Audit comment">Audit comment</th><th data-col="billingdlv">Billing</th><?php endif;?>
            <th class="w-1" data-colfixed></th></tr></thead>
        <tbody>
        <?php foreach($rows as $r):
            $lines = $linesByChart[(int)$r['id']] ?? [];
            $acc = ((int)$r['audit_opportunities']>0) ? round(100*((int)$r['audit_opportunities']-(int)$r['audit_defects'])/(int)$r['audit_opportunities'],1) : null; ?>
            <tr>
                <?php if($wrapForm):?><td><input type="checkbox" name="ids[]" value="<?= (int)$r['id'] ?>" class="form-check-input m-0 billchk"></td><?php endif;?>
                <td><?= (int)$r['is_priority']?'<span class="badge bg-red" title="Priority"><i class="ti ti-arrow-up"></i></span>':'' ?></td>
                <td><strong><?= e($r['patient_name']) ?></strong></td>
                <td class="small"><?= e($r['patient_mrn'] ?: '—') ?></td>
                <td class="small text-nowrap"><?= e(fmt_date($r['patient_dob'])) ?></td>
                <td class="small"><?= e(ucfirst($r['gender'] ?: '—')) ?></td>
                <td class="small text-nowrap"><?= 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['billing_prov'] ?: '—') ?></td>
                <td class="small text-nowrap"><?= coding_lines_cell($lines, 'dos') ?></td>
                <td class="small"><?= coding_lines_cell($lines, 'cpt') ?></td>
                <td class="small text-center"><?= coding_lines_cell($lines, 'units') ?></td>
                <td class="small"><?= coding_lines_cell($lines, 'mods') ?></td>
                <td class="small"><?= coding_lines_cell($lines, 'icds') ?></td>
                <td class="small"><?= e($r['coder'] ?? '—') ?></td>
                <td class="small text-nowrap" title="When the coder submitted this chart"><?= $r['submitted_at'] ? e(fmt_dt($r['submitted_at'])) : '<span class="text-secondary">—</span>' ?></td>
                <?php if($isAuditQ): ?>
                    <td><?php if((int)$r['in_sample']): ?><span class="badge bg-purple-lt" title="In the audit sample<?= $r['sample_pct']?' · '.(int)$r['sample_pct'].'% tier':'' ?>"><i class="ti ti-target-arrow me-1"></i>Sample</span><?php else: ?><span class="text-secondary">—</span><?php endif; ?></td>
                    <td><?php if($r['audit_status']==='ready_to_deliver'): ?><span class="badge bg-<?= $acc===null||$acc>=95?'success':($acc>=90?'warning':'danger') ?>-lt" title="Audited"><i class="ti ti-checks me-1"></i>audited<?= $acc!==null?' · '.$acc.'%':'' ?></span>
                        <?php elseif($r['audit_status']==='in_progress'): ?><span class="badge bg-info-lt">auditing…</span>
                        <?php else: ?><span class="text-secondary small">not audited</span><?php endif; ?></td>
                <?php endif; ?>
                <?php if($queue==='audited'): ?>
                    <td><span class="badge bg-secondary-lt"><?= e(str_replace('_',' ',$r['audit_status'])) ?></span></td>
                    <td><?php if($acc!==null){ echo '<span class="badge bg-'.($acc>=95?'success':($acc>=90?'warning':'danger')).'-lt">'.$acc.'%</span> <span class="text-secondary small">'.(int)$r['audit_defects'].'/'.(int)$r['audit_opportunities'].'</span>'; } elseif($r['audit_result']){ echo '<span class="badge bg-'.($r['audit_result']==='pass'?'success':'danger').'-lt">'.e($r['audit_result']).'</span>'; } else { echo '<span class="text-secondary">—</span>'; } ?></td>
                    <td class="small"><?= e($r['auditor'] ?? '—') ?></td>
                    <td class="small" style="max-width:260px;white-space:normal;word-break:break-word" title="<?= e((string)($r['audit_comment'] ?? '')) ?>"><?= !empty($r['audit_comment']) ? e($r['audit_comment']) : '<span class="text-secondary">—</span>' ?></td>
                    <td>
                    <?php if (!empty($r['released_to_billing_at'])):
                        $bs = $r['billing_status'] ?: 'pending'; $bc = ['pending'=>'azure','sent'=>'green','void'=>'secondary'][$bs] ?? 'azure'; ?>
                        <span class="badge bg-<?= $bc ?>-lt" title="Released to billing <?= e(fmt_dt($r['released_to_billing_at'])) ?>"><i class="ti ti-receipt me-1"></i><?= e($bs) ?></span>
                    <?php elseif ($r['audit_status']==='ready_to_deliver' && $canRelease): ?>
                        <form method="POST" class="d-inline"><?= csrf_field() ?><input type="hidden" name="action" value="release_to_billing"><input type="hidden" name="queue" value="audited"><input type="hidden" name="id" value="<?= (int)$r['id'] ?>">
                            <button class="btn btn-sm btn-success" title="Release to billing team"><i class="ti ti-send me-1"></i>Release</button></form>
                    <?php else: ?><span class="text-secondary">—</span><?php endif; ?>
                    </td>
                <?php endif; ?>
                <td><a href="<?= e(url('/admin/coding-audit.php?id='.(int)$r['id'] . ($listCarryQs ? '&'.$listCarryQs : ''))) ?>" class="btn btn-sm btn-<?= $auditMode?'primary':'outline-secondary' ?>"><?= !$auditMode ? 'View' : ($r['audit_status']==='in_progress' ? 'Resume' : ($r['audit_status']==='ready_to_deliver' ? 'Re-audit' : 'Audit')) ?></a></td>
            </tr>
        <?php endforeach; ?>
        <?php /* Column count per variant: 16 common (incl. the new "Completed") + 1 trailing action th, PLUS
                 audited → outcome/accuracy/auditor/auditcmt/billingdlv (=22); audit → sample/auditst (=19), +1
                 for the select-all th when $wrapForm (=20). $queue is normalised to audit|audited above (and
                 rebuttals renders its own table), so there is no third case — the old ternary carried a dead
                 third arm whose number was already wrong. Keep this in step when adding a column. */ ?>
        <?php if(!$rows): ?><tr><td colspan="<?= $queue==='audited' ? 22 : ($wrapForm ? 20 : 19) ?>" class="text-center text-secondary py-4">Nothing in this queue.</td></tr><?php endif; ?>
        </tbody>
    </table></div></div>
    <?php if ($wrapForm): ?></form>
    <script>(function(){var a=document.getElementById('billAllChk');if(a)a.addEventListener('change',function(){document.querySelectorAll('.billchk').forEach(function(c){c.checked=a.checked;});});})();</script>
    <?php endif; ?>
<?php endif; ?>
</div></div>
<?php require __DIR__ . '/../includes/footer.php'; ?>
