<?php
/**
 * Validator queue (coding.validate) — the RFI / Disregard HUB.
 *
 * Auditors no longer tag RFI/Disregard (they do DPO quality only). Instead a dedicated
 * "Validator" role (or a supervisor / manager) reviews ONLY coder-flagged RFI/Disregard charts here and:
 *   - TAGS a chart RFI or Disregard (reason + comment; the coder's note is pre-filled to edit), or
 *   - SENDS an already-tagged RFI/Disregard chart back to the SAME or a DIFFERENT coder.
 *
 * CLIENT-SCOPED (changed 2026-07-16 — this page was previously vertical-wide and never team-limited):
 * a validator sees only charts for the clients they are aligned to. Coders may still flag
 * RFI/Disregard while coding; those land in the RFI / Disregard tabs here too.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$RFI = array_values(coding_secondary_reasons($pdo, 'rfi')); $DIS = array_values(coding_secondary_reasons($pdo, 'disregard'));

/* ---------------------------------------------------------------- DATA SCOPE (client level)
 * A validator sees only charts whose client they are aligned to in coding_user_scope; supervisors /
 * managers / SysAdmin stay unrestricted. Same rule as coding-audit.php and coding-deliverables.php —
 * client-level ONLY (not process), deliberately, so the three coding review pages share one policy.
 *
 * Defined HERE, above the POST handlers, because it must fence the WRITES too: every handler below
 * loads its chart by a POSTed id, so a fence applied only to the list/counts would still let a crafted
 * POST tag, send back, or mark-delivered a chart outside the validator's clients.
 *
 * Self-contained SQL (no placeholders): $uid is an int and the vertical id list is built from ints, so
 * it can be appended to any query — including $pdo->query() calls — that aliases coding_inventory `ci`.
 * Empty string for unrestricted users, so appending it is always safe. */
$SCOPE = coding_scope_unrestricted($uid) ? '' :
    ' AND EXISTS (SELECT 1 FROM coding_user_scope s WHERE s.user_id=' . $uid
    . ' AND ' . coding_vertical_scope_sql('s.vertical_id') . ' AND s.client_id=ci.client_id)';

/* After a confirm / send-back from the CHART DETAIL view (after=next), continue the one-by-one flow by
 * jumping to the next chart via Get-next; otherwise fall back to the queue list. $flt carries the active
 * filters so the flow stays scoped to what the validator is working. */
$continueUrl = function (string $nextQ, string $doneQ): string {
    $flt  = trim((string) ($_POST['flt'] ?? ''));
    $tail = $flt !== '' ? '&' . $flt : '';
    if (($_POST['after'] ?? '') === 'next') return '/admin/coding-validate.php?getnext=1&q=' . urlencode($nextQ) . $tail;
    return '/admin/coding-validate.php?q=' . urlencode($doneQ) . $tail;
};

/* ----------------------------------------------------------- POST: tag a reviewed chart RFI / Disregard */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'tag') {
    csrf_check_or_die();
    $id     = (int) input('id');
    $tag    = in_array(input('tag'), ['rfi','disregard'], true) ? (string) input('tag') : '';
    $reason = trim((string) input('reason'));
    $comment= trim((string) input('comment'));
    // $SCOPE fences the WRITE: out-of-scope charts read as "not found" (never confirm they exist).
    $ch = $pdo->prepare("SELECT ci.* FROM coding_inventory ci WHERE ci.id=?$SCOPE"); $ch->execute([$id]); $ch = $ch->fetch();
    if (!$ch)        { flash('error', 'Chart not found.'); redirect('/admin/coding-validate.php'); }
    if (!$tag)       { flash('error', 'Choose RFI or Disregard.'); redirect('/admin/coding-validate.php?q=toreview'); }
    // Taggable = a chart the CODER flagged RFI/Disregard awaiting validation — must mirror $REVIEW exactly:
    // status IN('rfi','disregard') AND audit_status IS NULL AND not released. ANY non-NULL audit_status
    // (rfi/disregard already confirmed, or an auditor's ready_to_deliver/in_progress score) is rejected.
    if (!in_array($ch['status'], ['rfi','disregard'], true) || $ch['audit_status'] !== null || $ch['released_to_billing_at']) {
        flash('error', 'That chart is not available for RFI / Disregard tagging.'); redirect('/admin/coding-validate.php?q=toreview');
    }
    if ($reason === '')  { flash('error', 'Select a reason.'); redirect('/admin/coding-validate.php?q=toreview'); }
    if ($comment === '') { flash('error', 'Add a comment.'); redirect('/admin/coding-validate.php?q=toreview'); }
    $valid = $tag === 'rfi' ? $RFI : $DIS;
    if (!in_array($reason, $valid, true)) { flash('error', 'Pick a valid reason.'); redirect('/admin/coding-validate.php?q=toreview'); }

    $pdo->beginTransaction();
    try {
        // RFI/Disregard is not code-scored — clear any DPO findings, stamp the disposition.
        $pdo->prepare("UPDATE coding_inventory SET audit_status=?, audit_secondary_reason=?, audit_comment=?,
                        audit_result=NULL, audit_opportunities=NULL, audit_defects=NULL,
                        audited_at=COALESCE(audited_at, NOW()), updated_at=NOW() WHERE id=?")
            ->execute([$tag, $reason, $comment, $id]);
        $pdo->prepare("DELETE FROM coding_audit_codes WHERE inventory_id=?")->execute([$id]);
        coding_log_event($pdo, $id, 'validate_tag', $uid, $reason . ($comment !== '' ? ' — ' . $comment : ''), ($ch['audit_status'] ?: $ch['status']), $tag);
        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        flash('error', 'Tag failed: ' . (APP_DEBUG ? $e->getMessage() : 'server error')); redirect('/admin/coding-validate.php?q=toreview');
    }
    audit_log('coding.validate.tag', 'coding_inventory', $id, null, ['tag'=>$tag, 'reason'=>$reason]);
    flash('success', strtoupper($tag) . ' set — the chart moves to the ' . $tag . ' queue for client delivery.');
    redirect($continueUrl('toreview', $tag));   // after=next → next to-review chart; else → the queue it moved to
}

/* ----------------------------------------------------------- POST: send an RFI/Disregard chart back to a coder */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'send_back') {
    csrf_check_or_die();
    $id = (int) input('id');
    $to = (int) input('to_coder');
    if (!$to) { flash('error', 'Choose a coder to send it to.'); redirect('/admin/coding-validate.php'); }
    $ch = $pdo->prepare("SELECT ci.* FROM coding_inventory ci WHERE ci.id=?$SCOPE"); $ch->execute([$id]); $ch = $ch->fetch();
    if (!$ch) { flash('error', 'Chart not found.'); redirect('/admin/coding-validate.php'); }
    // Sendable = a chart actually in one of the validator's two states, not yet billed:
    //   coder-flagged (status IN('rfi','disregard') AND audit_status IS NULL)  OR  validator-confirmed (audit_status IN('rfi','disregard')).
    $coderFlagged = in_array($ch['status'], ['rfi','disregard'], true) && $ch['audit_status'] === null;
    $confirmed    = in_array($ch['audit_status'] ?? '', ['rfi','disregard'], true);
    if ($ch['released_to_billing_at'] || (!$coderFlagged && !$confirmed)) {
        flash('error', 'That chart cannot be sent back from here.'); redirect('/admin/coding-validate.php');
    }
    // The validator first picks the RESPONSE TYPE (validator notes OR client response), then types ONE
    // comment. It is logged to the durable event log ONLY — the coder's own `comment` is left UNTOUCHED.
    // The coder sees this back-and-forth in a read-only "Comment history" panel on their chart, never
    // mixed into their editable comment box.
    $rtype = in_array($_POST['response_type'] ?? 'validator', ['validator','client'], true) ? $_POST['response_type'] : 'validator';
    $cmt   = trim((string) input('sb_comment'));
    $note       = $rtype === 'validator' ? $cmt : '';
    $clientResp = $rtype === 'client'    ? $cmt : '';
    $respDate   = trim((string) input('client_response_date'));
    $respDateSql = ($rtype === 'client' && preg_match('/^\d{4}-\d{2}-\d{2}$/', $respDate)) ? $respDate : null;
    $pdo->beginTransaction();
    try {
        $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', rebuttal_comment=NULL, rebuttal_at=NULL, rebuttal_resolved_at=NULL, ack_at=NULL, ack_by=NULL,
                        client_sent_at=NULL, client_sent_by=NULL,
                        client_response_comment=?, client_response_date=?, client_response_by=?,
                        rto_at=NOW(), rto_by=?, updated_at=NOW() WHERE id=?")
            ->execute([$to, $uid,
                       $clientResp !== '' ? $clientResp : null, $respDateSql, $clientResp !== '' ? $uid : null,
                       $uid, $id]);
        $pdo->prepare("DELETE FROM coding_audit_codes WHERE inventory_id=?")->execute([$id]);
        if ($clientResp !== '') coding_log_event($pdo, $id, 'client_response', $uid, ($respDateSql ? "received $respDateSql — " : '') . $clientResp, null, null);
        // Log only the substantive validator note (empty for client-response / no-note send-backs, so the
        // coder's Comment-history panel doesn't show a status-only "sent back" line). Timeline still recorded.
        coding_log_event($pdo, $id, 'validate_return', $uid, $note, ($ch['audit_status'] ?: $ch['status']), 'pending');
        $pdo->commit();
    } catch (Throwable $e) {
        if ($pdo->inTransaction()) $pdo->rollBack();
        flash('error', 'Send-back failed: ' . (APP_DEBUG ? $e->getMessage() : 'server error')); redirect('/admin/coding-validate.php');
    }
    audit_log('coding.validate', 'coding_inventory', $id, null, ['to'=>$to]);
    flash('success', 'Sent back to the coder’s open queue at TOP priority.');
    redirect($continueUrl((string) ($_POST['q'] ?? 'toreview'), (string) ($_POST['q'] ?? 'toreview')));
}

/* ----------------------------------------------------------- POST: mark RFI/Disregard charts SENT TO CLIENT (or undo) */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array($_POST['action'] ?? '', ['client_sent','client_unsent'], true)) {
    csrf_check_or_die();
    $ids = array_values(array_filter(array_map('intval', (array) ($_POST['ids'] ?? []))));
    $qBack = isset($_GET['q'], $VQ[$_GET['q']]) ? $_GET['q'] : (in_array($_POST['q'] ?? '', ['rfi','disregard'], true) ? $_POST['q'] : 'rfi');
    /* Carry the active filters back, exactly as the tag / send-back forms do via $continueUrl. This USED to be
     * cosmetic — dropping them just meant "back to the unfiltered list", which showed everything. It is not
     * cosmetic any more: with the today-default in place, a redirect carrying no window means "today's activity
     * only", so a validator who clicked "Show all", ticked 10 of 40 charts and hit Mark-sent would land on a
     * list showing only what moved today — the other 30 would look like they had vanished. Same-origin safe:
     * $back keeps its leading /admin/... path, so url() cannot be steered off-site by the posted string. */
    $flt  = trim((string) ($_POST['flt'] ?? ''));
    $back = '/admin/coding-validate.php?q=' . $qBack . ($flt !== '' ? '&' . $flt : '');
    if (!$ids) { flash('error', 'Select at least one chart.'); redirect($back); }
    $in = implode(',', array_fill(0, count($ids), '?'));
    /* Resolve WHICH of the posted ids actually qualify (in scope + in a delivery state) BEFORE writing, then
     * update and log exactly those. The old code fired the UPDATE over every posted id and logged a
     * client_sent event for every one of them — including ids the UPDATE's own WHERE skipped — and reported
     * rowCount(), which counts CHANGED rows, not matched. With $SCOPE in play that would also stamp an event
     * onto a chart outside the validator's clients. Re-inlining the resolved ids is safe: they are ints. */
    if ($_POST['action'] === 'client_sent') {
        $sel = $pdo->prepare("SELECT ci.id FROM coding_inventory ci WHERE ci.id IN ($in)
                AND ci.audit_status IN ('rfi','disregard') AND ci.released_to_billing_at IS NULL AND ci.client_sent_at IS NULL$SCOPE");
        $sel->execute($ids);
        $ok = array_map('intval', $sel->fetchAll(PDO::FETCH_COLUMN));
        if ($ok) {
            $pdo->prepare("UPDATE coding_inventory SET client_sent_at=NOW(), client_sent_by=?, updated_at=NOW()
                    WHERE id IN (" . implode(',', $ok) . ")")->execute([$uid]);
            foreach ($ok as $cid) coding_log_event($pdo, $cid, 'client_sent', $uid, 'sent to client', null, null);
        }
        flash('success', count($ok) . ' chart(s) marked sent to client.');
    } else {
        $sel = $pdo->prepare("SELECT ci.id FROM coding_inventory ci WHERE ci.id IN ($in)
                AND ci.audit_status IN ('rfi','disregard') AND ci.client_sent_at IS NOT NULL$SCOPE");
        $sel->execute($ids);
        $ok = array_map('intval', $sel->fetchAll(PDO::FETCH_COLUMN));
        if ($ok) {
            $pdo->prepare("UPDATE coding_inventory SET client_sent_at=NULL, client_sent_by=NULL, updated_at=NOW()
                    WHERE id IN (" . implode(',', $ok) . ")")->execute();
        }
        flash('success', count($ok) . ' chart(s) moved back to pending delivery.');
    }
    audit_log('coding.client_delivery', 'coding_inventory', null, null, ['action'=>$_POST['action'],'ids'=>$ids]);
    redirect($back);
}

/* ----------------------------------------------------------- queues (vertical-wide, NO team scope)
 *  To review = ONLY charts the CODER flagged RFI / Disregard awaiting validation
 *              (`status IN('rfi','disregard') AND audit_status IS NULL`). The validator CONFIRMS the
 *              disposition (→ RFI/Disregard tab) or, if codable, sends it back to the coder.
 *              Completed / audited charts are NOT here — they flow through Audit → Deliverables.
 *  RFI / Disregard tabs = charts the validator has CONFIRMED (audit_status) → client delivery. */
$REVIEW = "(ci.status IN ('rfi','disregard') AND ci.audit_status IS NULL AND ci.released_to_billing_at IS NULL)";
$VQ = [
    'toreview'  => $REVIEW,
    'rfi'       => "ci.audit_status='rfi'",
    'disregard' => "ci.audit_status='disregard'",
];
$q = isset($_GET['q'], $VQ[$_GET['q']]) ? $_GET['q'] : 'toreview';
remember_filters('coding_validate_' . $q, ['client_id','facility_id','provider_id','coder_id','ctag','from','to','cs']);   // per-session filter memory

/* ---- filters: client / facility / provider / coder / coder-tag / COMPLETED-date range ----
 * The date pair filters ci.submitted_at — the stamp set when the CODER completes/submits the chart —
 * so it is the "chart completed" window (labelled as such in the form). */
$f = ['client_id'=>(int)($_GET['client_id']??0), 'facility_id'=>(int)($_GET['facility_id']??0),
      'provider_id'=>(int)($_GET['provider_id']??0), 'coder_id'=>(int)($_GET['coder_id']??0)];
$ctag = in_array($_GET['ctag']??'', ['rfi','disregard'], true) ? $_GET['ctag'] : '';
$from = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['from']??'')) ? $_GET['from'] : '';
$to   = preg_match('/^\d{4}-\d{2}-\d{2}$/', (string)($_GET['to']??''))   ? $_GET['to']   : '';
$cs   = in_array($_GET['cs']??'', ['sent','pending'], true) ? $_GET['cs'] : '';   // client-delivery filter (RFI/Disregard only)

/* DEFAULT VIEW = today's ACTIVITY (ci.updated_at) — deliberately NOT today's completed-date. This
 * distinction is load-bearing, not a nuance: charts enter these queues at moments unrelated to when the
 * coder completed them.
 *   - billing_raise_rfi() (includes/billing.php:755) drops an ALREADY-RELEASED chart straight back into
 *     $REVIEW — "so it lands in the validator's To review queue", per its own comment — and never touches
 *     submitted_at, which by then is days or weeks old.
 *   - a QA edit (coding-chart.php:416, `$submitted = !$qaEdit && ...`) preserves the original stamp on
 *     purpose when an auditor flags rfi/disregard.
 * Both stamp updated_at=NOW(). Defaulting the window on submitted_at therefore exiled two of the three
 * inflow paths from the default view on the very day they arrived AND every day after — a billing RFI,
 * which BLOCKS billing, could never once be "current". updated_at = "when this chart last moved", which is
 * what "today's records" means for an arrival-driven work queue.
 * The user-facing Completed From–To filter still filters submitted_at (below) — a separate, explicit axis.
 *
 * Precedence, decided on VALUES so it cannot drift from what the form echoes back:
 *   dall=1 (the banner's "Show all" escape)  >  an explicit Completed range  >  today's activity.
 * An explicit Completed range MUST switch the activity window off: "completed in June" AND "moved today"
 * intersects to almost nothing — the same exile bug, user-triggered. dall is deliberately not remembered
 * (absent from the remember_filters list above), so every fresh visit lands back on today as specified. */
$dall   = ($_GET['dall'] ?? '') === '1';
$dToday = !$dall && $from === '' && $to === '';

/* $fwND = every real filter, INCLUDING the Completed range — drives the backlog count behind the banner.
 * $fw = $fwND + today's activity window — drives what the user actually sees (counts, list, export, next).
 * So $dHidden below reads exactly as: "matches your filters, but did not move today". */
$fwND = ''; $fpND = [];
if ($f['client_id'])   { $fwND .= ' AND ci.client_id=?';                                  $fpND[] = $f['client_id']; }
if ($f['facility_id']) { $fwND .= ' AND ci.facility_id=?';                                $fpND[] = $f['facility_id']; }
if ($f['provider_id']) { $fwND .= ' AND COALESCE(ci.coded_provider_id,ci.provider_id)=?'; $fpND[] = $f['provider_id']; }
if ($f['coder_id'])    { $fwND .= ' AND ci.coder_id=?';                                   $fpND[] = $f['coder_id']; }
if ($ctag)             { $fwND .= ' AND ci.status=?';                                     $fpND[] = $ctag; }
if ($from) { $fwND .= ' AND ci.submitted_at>=?'; $fpND[] = $from.' 00:00:00'; }
if ($to)   { $fwND .= ' AND ci.submitted_at<?';  $fpND[] = date('Y-m-d', strtotime($to.' +1 day')).' 00:00:00'; }
$fw = $fwND; $fp = $fpND;
if ($dToday) {
    // Half-open [today, tomorrow) — index-friendly and DST-safe, unlike DATE(ci.updated_at)=CURDATE().
    $fw .= ' AND ci.updated_at>=? AND ci.updated_at<?';
    $fp[] = date('Y-m-d') . ' 00:00:00';
    $fp[] = date('Y-m-d', strtotime('+1 day')) . ' 00:00:00';
}
// Client-delivery filter applies only to the confirmed RFI/Disregard buckets (kept OUT of $fw so the
// sent/pending sub-counts stay accurate; applied via $csWhere only in the row list query).
$csWhere = ($cs && $q !== 'toreview') ? ($cs === 'sent' ? ' AND ci.client_sent_at IS NOT NULL' : ' AND ci.client_sent_at IS NULL') : '';

/* ---- shared by Get-next / Export / Detail / list ---- */
// drops 0 / '' values. Carries dall so the "Show all" escape survives tab switches / Get-next / Export
// (under dall the dates are '' and drop out on their own, so the two can never contradict each other).
$qbase  = array_filter(['client_id'=>$f['client_id'],'facility_id'=>$f['facility_id'],'provider_id'=>$f['provider_id'],'coder_id'=>$f['coder_id'],'ctag'=>$ctag,'from'=>$from,'to'=>$to,'cs'=>$cs,'dall'=>$dall?'1':'']);
// The same filters as a querystring, for POST forms to round-trip through their redirect (name="flt").
// Defined HERE at top level, not inside the ?id= detail branch where it used to live — the client-delivery
// form lives in the LIST view, where that copy would simply not exist.
$flt    = http_build_query($qbase);

/* The SCRIBE is the chart's RENDERING provider — but only when the chart is actually flagged as scribed.
   coding-chart.php never lets a scribe become the billing provider (billing defaults to rendering EXCEPT when
   scribe_flag is set), so whenever a scribe is involved these are two DIFFERENT people and the validator needs
   to see both. Returns '' when there is no scribe, so callers just show the billing provider on its own.
   Needs `scribe_flag` (from ci.*) + `rendering_prov` (LEFT JOIN coding_providers rp) on the row. */
$scribeOf = static fn(array $r): string =>
    ((int) ($r['scribe_flag'] ?? 0) && !empty($r['rendering_prov'])) ? (string) $r['rendering_prov'] : '';
$coders = $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();

/* ============================================================ GET NEXT — open charts one-by-one (like coding inventory).
 * Serves the next chart in the active queue (coder-tagged first, then oldest) honoring the current filters. */
if (($_GET['getnext'] ?? '') === '1') {
    /* skip=<id> excludes the chart the validator is skipping PAST. Without it "Skip → next" was a no-op: the
     * ordering below is deterministic and skipping changes nothing about the chart, so the same row came
     * straight back, forever. Int-cast and inlined deliberately — keeping it out of $fp means $fw and $fp stay
     * in lockstep (emulation is OFF, so a count mismatch is a hard error).
     * LIMITATION: this excludes only the chart being skipped FROM, so skipping A then B returns to A. Chaining
     * skips needs per-session state and a decision on when to clear it — deliberately not built on a guess. */
    $skip    = (int) ($_GET['skip'] ?? 0);
    $skipSql = $skip > 0 ? ' AND ci.id <> ' . $skip : '';
    /* $fwND / $fpND — the real filters WITHOUT the today's-activity window — NOT $fw.
     * Get-next is a WORK DISPATCHER, not a view: it must hand out the oldest waiting chart. The today-default
     * is a convenience for the LIST. Running Get-next through $fw meant a chart flagged yesterday and never
     * validated could never be served at all, and the validator got "No more charts 🎉" while the banner
     * directly above said "43 older still awaiting validation" — the queue silently starved itself.
     * An EXPLICIT Completed-date range IS still honoured: it lives in $fwND, because the user chose it. */
    $nx = $pdo->prepare("SELECT ci.id FROM coding_inventory ci WHERE {$VQ[$q]} $fwND$SCOPE$skipSql
        ORDER BY (ci.status IN ('rfi','disregard') AND ci.audit_status IS NULL) DESC, ci.submitted_at ASC, ci.id ASC LIMIT 1");
    $nx->execute($fpND); $nid = (int) $nx->fetchColumn();
    if ($nid) redirect('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q,'id'=>$nid], $qbase)));
    // Honest wording when skipping: the queue is NOT empty — the skipped chart is still in it.
    flash('success', $skip > 0
        ? 'Nothing else in the ' . $q . ' queue — the chart you skipped is the only one left.'
        : 'No more charts in the ' . $q . ' queue. 🎉');
    redirect('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q], $qbase)));
}

/* ============================================================ EXCEL EXPORT — complete chart detail + supporting line
 * data (one row per CPT line), honoring the active queue + filters. */
if (($_GET['export'] ?? '') === 'xlsx') {
    require_once __DIR__ . '/../includes/xlsx.php';
    $ex = $pdo->prepare("SELECT ci.*, c.name cn, f.name fn, pr.name uprov,
            bp.name billing_prov, rp.name rendering_prov, cp.name coded_prov, u.name coder, vu.name validator,
            COALESCE(ci.audit_secondary_reason, ci.secondary_reason) AS reason
        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 coding_providers rp ON rp.id=ci.rendering_provider_id
        LEFT JOIN coding_providers cp ON cp.id=ci.coded_provider_id
        LEFT JOIN users u ON u.id=ci.coder_id
        LEFT JOIN (SELECT inventory_id, SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN event_type='validate_tag' THEN actor_id END ORDER BY created_at DESC, id DESC), ',', 1) validator_id
                   FROM coding_chart_events GROUP BY inventory_id) ve ON ve.inventory_id=ci.id
        LEFT JOIN users vu ON vu.id=ve.validator_id
        WHERE {$VQ[$q]} $fw$SCOPE $csWhere ORDER BY ci.updated_at DESC LIMIT 20000");   // $csWhere: the export must mirror the on-screen list
    $ex->execute($fp); $erows = $ex->fetchAll();
    /* batch the coder's coded lines for every chart, so the export carries the supporting codes */
    $linesByChart = [];
    $invIds = array_map('intval', array_column($erows, 'id'));
    if ($invIds) {
        $in = implode(',', $invIds);
        $cptRows = $pdo->query("SELECT id, inventory_id, dos, cpt_code, units FROM coding_chart_cpts WHERE inventory_id IN ($in) ORDER BY dos, sort_order, id")->fetchAll();
        $cptIds = array_map('intval', array_column($cptRows, 'id'));
        $icdBy = []; $modBy = [];
        if ($cptIds) {
            $cin = implode(',', $cptIds);
            foreach ($pdo->query("SELECT chart_cpt_id, icd_code FROM coding_chart_cpt_icds WHERE chart_cpt_id IN ($cin) ORDER BY sort_order, id")->fetchAll() as $rr) $icdBy[$rr['chart_cpt_id']][] = $rr['icd_code'];
            foreach ($pdo->query("SELECT chart_cpt_id, modifier_code FROM coding_chart_cpt_modifiers WHERE chart_cpt_id IN ($cin) ORDER BY sort_order, id")->fetchAll() as $rr) $modBy[$rr['chart_cpt_id']][] = $rr['modifier_code'];
        }
        foreach ($cptRows as $c) $linesByChart[(int)$c['inventory_id']][] = ['dos'=>$c['dos'], 'cpt'=>$c['cpt_code'], 'units'=>(int)($c['units'] ?? 1), 'icds'=>$icdBy[$c['id']] ?? [], 'mods'=>$modBy[$c['id']] ?? []];
    }
    $x = new SimpleXlsx();
    $hdr = ['Patient','MRN','DOB','Gender','Client','Facility','Billing provider','Scribe (rendering)','Coder','Validator','DOS','CPT','Units','Modifiers','ICDs','Coder tag','Validator disposition','Reason','Coder comment','Validator comment','Submitted','Audited','Sent to client','Client response date','Client response'];
    $data = [];
    foreach ($erows as $r) {
        $lines = $linesByChart[(int)$r['id']] ?? [];
        if (!$lines) $lines = [['dos'=>$r['dos'], 'cpt'=>'', 'units'=>1, 'icds'=>[], 'mods'=>[]]];   // keep the chart even with no codes
        $billing   = $r['billing_prov'] ?: $r['coded_prov'] ?: $r['uprov'];
        foreach ($lines as $ln) {
            $data[] = [
                $r['patient_name'], $r['patient_mrn'], $r['patient_dob'], $r['gender'],
                $r['cn'], $r['fn'], $billing, $scribeOf($r), $r['coder'], $r['validator'],
                $ln['dos'], $ln['cpt'], (int)($ln['units'] ?? 1), implode(', ', $ln['mods']), implode(', ', $ln['icds']),
                $r['status'], ($r['audit_status'] ?: '(pending)'), $r['reason'],
                $r['comment'], $r['audit_comment'], $r['submitted_at'], $r['audited_at'],
                $r['client_sent_at'], $r['client_response_date'], $r['client_response_comment'],
            ];
        }
    }
    $x->addSheet(ucfirst($q), $hdr, $data);
    $x->download('coding-validate-' . $q . date('-Ymd') . '.xlsx');
}

/* ============================================================ DETAIL — open ONE chart: confirm RFI/Disregard or send back */
$openId = (int) ($_GET['id'] ?? 0);
if ($openId) {
    $d = $pdo->prepare("SELECT ci.*, c.name cn, f.name fn, pr.name uprov,
            bp.name billing_prov, rp.name rendering_prov, cp.name coded_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 coding_providers rp ON rp.id=ci.rendering_provider_id
        LEFT JOIN coding_providers cp ON cp.id=ci.coded_provider_id
        LEFT JOIN users u ON u.id=ci.coder_id
        LEFT JOIN users au ON au.id=ci.auditor_id
        WHERE ci.id=?$SCOPE");   // deep-linking an out-of-scope id reads as "not found"
    $d->execute([$openId]); $d = $d->fetch();
    if (!$d) { flash('error', 'Chart not found.'); redirect('/admin/coding-validate.php?q=' . $q); }
    $isConfirmed = in_array($d['audit_status'] ?? '', ['rfi','disregard'], true);
    $inReview    = in_array($d['status'], ['rfi','disregard'], true) && $d['audit_status'] === null && !$d['released_to_billing_at'];
    if (!$isConfirmed && !$inReview) { flash('error', 'That chart is not in the validation queue (it may already be released or sent back).'); redirect('/admin/coding-validate.php?q=' . $q); }
    $lines     = coding_chart_lines($pdo, $openId);
    $billing   = $d['billing_prov'] ?: $d['coded_prov'] ?: $d['uprov'];
    $cst       = $d['status'];
    $backUrl   = url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q], $qbase)));
    // skip=<this chart> so Get-next serves a DIFFERENT one; without it Skip re-served this same chart forever.
    $nextUrl   = url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q,'getnext'=>1,'skip'=>$openId], $qbase)));

    $pageTitle = 'Validate · ' . $d['patient_name'];
    require __DIR__ . '/../includes/header.php';
    ?>
    <link rel="stylesheet" href="<?= e(url('/admin/coding-ui.css')) ?>">
    <div class="page-header"><div class="container-xl"><div class="row align-items-center">
        <div class="col-auto"><a href="<?= e($backUrl) ?>" class="btn btn-ghost-secondary"><i class="ti ti-arrow-left me-1"></i>Queue</a></div>
        <div class="col"><h2 class="page-title"><i class="ti ti-shield-check me-2 text-primary"></i>Validate chart</h2>
            <div class="text-secondary small">Review one chart — confirm <strong>RFI</strong> / <strong>Disregard</strong>, or send it back to a coder if it&rsquo;s codable.</div></div>
        <div class="col-auto"><a href="<?= e($nextUrl) ?>" class="btn btn-outline-primary">Skip &rarr; next<i class="ti ti-arrow-right ms-1"></i></a></div>
    </div></div></div>
    <div class="page-body"><div class="container-xl"><div class="row g-3">
        <div class="col-lg-7">
            <div class="card mb-3"><div class="card-body">
                <div class="d-flex align-items-start">
                    <div><h3 class="mb-0"><?= e($d['patient_name']) ?></h3>
                        <div class="text-secondary small">MRN <?= e($d['patient_mrn'] ?: '—') ?> · <?= e($d['gender'] ?: '—') ?> · DOB <?= e($d['patient_dob'] ?: '—') ?></div></div>
                    <div class="ms-auto text-end">
                        <?php if ($d['audit_status']==='rfi'): ?><span class="badge bg-orange">RFI confirmed</span>
                        <?php elseif ($d['audit_status']==='disregard'): ?><span class="badge bg-danger">Disregard confirmed</span>
                        <?php elseif ($cst==='disregard'): ?><span class="badge bg-danger-lt">Disregard — coder flagged</span>
                        <?php else: ?><span class="badge bg-orange-lt">RFI — coder flagged</span><?php endif; ?>
                    </div>
                </div>
                <hr class="my-2">
                <div class="row g-2" style="font-size:.82rem">
                    <div class="col-6 col-md-4"><div class="text-secondary">Client</div><div class="fw-bold"><?= e($d['cn']) ?></div></div>
                    <div class="col-6 col-md-4"><div class="text-secondary">Facility</div><div class="fw-bold"><?= e($d['fn']) ?></div></div>
                    <?php $chartDos = $d['dos'] ?: null; if (!$chartDos) { foreach ($lines as $lx) { if (!empty($lx['dos'])) { $chartDos = $lx['dos']; break; } } } ?>
                    <div class="col-6 col-md-4"><div class="text-secondary">DOS</div><div class="fw-bold"><?= e($chartDos ? fmt_date($chartDos) : '—') ?></div></div>
                    <div class="col-6 col-md-4"><div class="text-secondary">Coder</div><div class="fw-bold"><?= e($d['coder'] ?: '—') ?></div></div>
                    <div class="col-6 col-md-4"><div class="text-secondary">Billing provider</div><div class="fw-bold"><?= e($billing ?: '—') ?></div></div>
                    <?php $scribeName = $scribeOf($d); if ($scribeName !== ''): /* only when this encounter actually has a scribe */ ?>
                    <div class="col-6 col-md-4"><div class="text-secondary"><i class="ti ti-writing me-1"></i>Scribe <span class="opacity-75">(rendering)</span></div><div class="fw-bold"><?= e($scribeName) ?></div></div>
                    <?php endif; ?>
                    <div class="col-6 col-md-4"><div class="text-secondary">Submitted</div><div class="fw-bold"><?= e($d['submitted_at'] ?: '—') ?></div></div>
                </div>
            </div></div>
            <div class="card"><div class="card-header py-2 d-flex align-items-center"><h3 class="card-title mb-0"><i class="ti ti-list-numbers me-1"></i>Coded lines</h3>
                <span class="text-secondary small ms-2"><?= count($lines) ?> line(s)</span></div>
                <div class="table-responsive"><table class="table card-table table-sm table-vcenter">
                    <thead><tr><th>DOS</th><th>CPT</th><th>Units</th><th>Modifiers</th><th>ICD(s)</th></tr></thead>
                    <tbody>
                    <?php foreach ($lines as $ln): ?>
                        <tr><td class="text-nowrap small"><?= $ln['dos'] ? e(fmt_date($ln['dos'])) : '—' ?></td>
                            <td><strong><?= e($ln['cpt_code']) ?></strong></td>
                            <td><?= (int)($ln['units'] ?? 1) ?></td>
                            <td class="small"><?= e(implode(', ', $ln['mods'])) ?: '<span class="text-secondary">—</span>' ?></td>
                            <td class="small"><?= e(implode(', ', $ln['icds'])) ?: '<span class="text-secondary">—</span>' ?></td></tr>
                    <?php endforeach; ?>
                    <?php if (!$lines): ?><tr><td colspan="5" class="text-center text-secondary py-3">No coded lines on this chart.</td></tr><?php endif; ?>
                    </tbody>
                </table></div></div>
        </div>
        <div class="col-lg-5">
            <?php if ($inReview): ?>
            <div class="card mb-3"><div class="card-header py-2"><h3 class="card-title mb-0 text-primary"><i class="ti ti-shield-check me-1"></i>Validate chart</h3></div>
                <div class="card-body">
                    <?php if (trim((string)$d['comment']) !== ''): ?>
                    <div class="cw-codernote mb-2"><div class="d-flex align-items-center"><span class="text-secondary" style="font-size:.62rem;text-transform:uppercase;font-weight:700">Coder comment</span>
                        <button type="button" class="btn btn-xs btn-outline-secondary ms-auto v-copy" data-note="<?= e($d['comment']) ?>" title="Copy coder comment into your comment box"><i class="ti ti-copy me-1"></i>Copy</button></div>
                        <div class="cw-codernote-body"><?= e($d['comment']) ?></div></div>
                    <?php endif; ?>
                    <form method="POST" class="vform" id="vForm">
                        <?= csrf_field() ?>
                        <input type="hidden" name="action" value="tag" id="vAction">
                        <input type="hidden" name="id" value="<?= (int)$d['id'] ?>">
                        <input type="hidden" name="q" value="<?= e($q) ?>"><input type="hidden" name="after" value="next"><input type="hidden" name="flt" value="<?= e($flt) ?>">
                        <input type="hidden" name="response_type" value="validator">
                        <label class="form-label mb-1">Tag</label>
                        <select name="tag" class="form-select form-select-sm v-tag mb-2" id="vTag">
                            <option value="rfi" <?= $cst==='rfi'?'selected':'' ?>>RFI</option>
                            <option value="disregard" <?= $cst==='disregard'?'selected':'' ?>>Disregard</option>
                            <option value="codable">Codable — send back to coder</option>
                        </select>
                        <div id="vReasonWrap"><label class="form-label mb-1">Reason</label>
                            <select name="reason" class="form-select form-select-sm v-reason mb-2" data-current="<?= e($d['secondary_reason'] ?? '') ?>"></select></div>
                        <div id="vCoderWrap" style="display:none"><label class="form-label mb-1">Send to coder</label>
                            <select name="to_coder" class="form-select form-select-sm mb-2">
                                <?php foreach ($coders as $o): ?><option value="<?= (int)$o['id'] ?>" <?= (int)$d['coder_id']===(int)$o['id']?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?>
                            </select></div>
                        <label class="form-label mb-1" id="vCommentLabel">Your comment</label>
                        <textarea name="comment" class="form-control form-control-sm v-comment mb-2" id="vCommentTag" rows="3" placeholder="Validation comment (required)"></textarea>
                        <textarea name="sb_comment" class="form-control form-control-sm mb-2" id="vCommentSb" rows="3" maxlength="1000" placeholder="Note to coder (optional)…" style="display:none"></textarea>
                        <button class="btn btn-primary w-100" id="vBtn"><i class="ti ti-check me-1"></i>Confirm &amp; next</button>
                    </form>
                    <script>(function(){var tag=document.getElementById('vTag'); if(!tag) return;
                        var act=document.getElementById('vAction'),rw=document.getElementById('vReasonWrap'),cw=document.getElementById('vCoderWrap'),
                            ct=document.getElementById('vCommentTag'),cs=document.getElementById('vCommentSb'),lbl=document.getElementById('vCommentLabel'),btn=document.getElementById('vBtn');
                        function sync(){var cod=tag.value==='codable'; act.value=cod?'send_back':'tag';
                            rw.style.display=cod?'none':''; cw.style.display=cod?'':'none';
                            ct.style.display=cod?'none':''; cs.style.display=cod?'':'none'; ct.required=!cod;
                            lbl.textContent=cod?'Note to coder (optional)':'Your comment';
                            btn.className=cod?'btn btn-outline-secondary w-100':'btn btn-primary w-100';
                            btn.innerHTML=cod?'<i class="ti ti-arrow-back-up me-1"></i>Send back to coder':'<i class="ti ti-check me-1"></i>Confirm &amp; next';}
                        tag.addEventListener('change',sync); sync();})();</script>
                </div></div>
            <?php else: ?>
            <div class="alert alert-success"><i class="ti ti-circle-check me-1"></i><strong><?= e(strtoupper((string)$d['audit_status'])) ?> confirmed.</strong> Awaiting client delivery — you can still send it back below.</div>
            <?php $sentToClient = !empty($d['client_sent_at']); ?>
            <div class="card"><div class="card-header py-2"><h3 class="card-title mb-0"><i class="ti ti-arrow-back-up me-1"></i><?= $sentToClient ? 'Client response &amp; send back to coder' : 'Send back to coder' ?></h3></div>
                <div class="card-body">
                    <?php if (trim((string)$d['comment']) !== '' || trim((string)$d['audit_comment']) !== ''): ?>
                    <div class="mb-2 p-2 rounded" style="background:var(--tblr-bg-surface-secondary,#f6f8fb)">
                        <div class="text-secondary" style="font-size:.62rem;text-transform:uppercase;font-weight:700">Comment history</div>
                        <?php if (trim((string)$d['comment']) !== ''): ?><div class="small mt-1"><span class="badge bg-blue-lt me-1">Coder</span><?= nl2br(e($d['comment'])) ?></div><?php endif; ?>
                        <?php if (trim((string)$d['audit_comment']) !== ''): ?><div class="small mt-1"><span class="badge bg-orange-lt me-1">Validator</span><?= nl2br(e($d['audit_comment'])) ?></div><?php endif; ?>
                    </div>
                    <?php endif; ?>
                    <?php if ($sentToClient): ?>
                    <div class="alert alert-info py-1 px-2 small mb-2"><i class="ti ti-send me-1"></i>Sent to client on <?= e(fmt_date($d['client_sent_at'])) ?> — record the client's response below if received.</div>
                    <?php endif; ?>
                    <form method="POST">
                        <?= csrf_field() ?>
                        <input type="hidden" name="action" value="send_back"><input type="hidden" name="id" value="<?= (int)$d['id'] ?>">
                        <input type="hidden" name="q" value="<?= e($q) ?>"><input type="hidden" name="after" value="next"><input type="hidden" name="flt" value="<?= e($flt) ?>">
                        <label class="form-label mb-1">Response type</label>
                        <select name="response_type" class="form-select form-select-sm mb-2" id="v-rtype">
                            <option value="validator">Validator notes</option>
                            <option value="client">Client response</option>
                        </select>
                        <div id="v-rdate-wrap" style="display:none">
                            <label class="form-label mb-1">Response received date</label>
                            <input type="date" name="client_response_date" class="form-control form-control-sm mb-2" max="<?= e(date('Y-m-d')) ?>">
                        </div>
                        <label class="form-label mb-1" id="v-cmt-label">Comment</label>
                        <textarea name="sb_comment" id="v-sb-comment" class="form-control form-control-sm mb-2" rows="3" maxlength="1000" placeholder="Type your validator note…"></textarea>
                        <label class="form-label mb-1">Coder</label>
                        <select name="to_coder" class="form-select form-select-sm mb-2">
                            <?php foreach ($coders as $o): ?><option value="<?= (int)$o['id'] ?>" <?= (int)$d['coder_id']===(int)$o['id']?'selected':'' ?>><?= e($o['name']) ?></option><?php endforeach; ?>
                        </select>
                        <button class="btn btn-outline-secondary w-100"><i class="ti ti-arrow-back-up me-1"></i>Send back to coder</button>
                    </form>
                    <script>(function(){var t=document.getElementById('v-rtype'),w=document.getElementById('v-rdate-wrap'),
                        l=document.getElementById('v-cmt-label'),c=document.getElementById('v-sb-comment');
                        if(!t) return;
                        function sync(){var cl=t.value==='client'; w.style.display=cl?'':'none';
                            if(l) l.textContent=cl?'Client response':'Validator note';
                            if(c) c.placeholder=cl?'What the client replied to this query…':'Type your validator note…';}
                        t.addEventListener('change',sync); sync();})();</script>
                </div></div>
            <?php endif; ?>
        </div>
    </div></div></div>
    <script>
    (function(){
      var RFI=<?= json_encode(array_values($RFI)) ?>, DIS=<?= json_encode(array_values($DIS)) ?>;
      function fill(sel, list){ var cur=sel.dataset.current||''; sel.innerHTML='<option value="">— reason —</option>'+list.map(function(r){return '<option'+(r===cur?' selected':'')+'>'+r.replace(/</g,'&lt;')+'</option>';}).join(''); }
      document.querySelectorAll('.vform').forEach(function(f){
        var tag=f.querySelector('.v-tag'), rsn=f.querySelector('.v-reason');
        if(tag&&rsn){ var sync=function(){ fill(rsn, tag.value==='disregard'?DIS:RFI); }; tag.addEventListener('change', sync); sync(); }
      });
      // Copy coder comment → validation comment box. The Copy button lives ABOVE the form (outside .vform),
      // so wire it at document scope rather than within the form.
      var cp=document.querySelector('.v-copy'), cm=document.querySelector('.v-comment');
      if(cp&&cm) cp.addEventListener('click', function(){ var c=cm.value.trim(); cm.value=c?(c+'\n'+(cp.dataset.note||'')):(cp.dataset.note||''); cm.focus(); });
    })();
    </script>
    <?php
    require __DIR__ . '/../includes/footer.php';
    exit;
}

// NB: named $cntStmt, NOT $cs — $cs is the client-delivery FILTER string ('' | 'pending' | 'sent') set above and
// still needed by the All/Pending/Sent buttons further down. Assigning the statement to $cs clobbered it, so those
// buttons compared a PDOStatement against strings and never highlighted the active one.
$cntStmt = $pdo->prepare("SELECT
    COALESCE(SUM($REVIEW),0) toreview,
    COALESCE(SUM(ci.audit_status='rfi'),0) rfi,
    COALESCE(SUM(ci.audit_status='disregard'),0) disregard
  FROM coding_inventory ci WHERE 1=1 $fw$SCOPE");
$cntStmt->execute($fp); $cnt = $cntStmt->fetch();

/* The today-default is a WORK-QUEUE hazard: a chart flagged yesterday and never validated drops out of the
 * default view AND its tab badge reads 0 — a backlog could age completely unseen. So when the window is on,
 * re-run the same counts WITHOUT it and surface the difference as a banner + escape.
 * Subtracting the two counts (rather than counting "updated_at < today") is deliberate on two grounds: it
 * cannot disagree with the badges, being literally the same expression minus the window; and it stays correct
 * for NULL stamps, which this table really does produce (an RTO clears submitted_at) and which would match
 * neither side of a naive < / >= split. */
$dHidden = ['toreview'=>0, 'rfi'=>0, 'disregard'=>0];
if ($dToday) {
    $ca = $pdo->prepare("SELECT
        COALESCE(SUM($REVIEW),0) toreview,
        COALESCE(SUM(ci.audit_status='rfi'),0) rfi,
        COALESCE(SUM(ci.audit_status='disregard'),0) disregard
      FROM coding_inventory ci WHERE 1=1 $fwND$SCOPE");
    $ca->execute($fpND); $cAll = $ca->fetch() ?: [];
    foreach ($dHidden as $k => $_) $dHidden[$k] = max(0, (int) ($cAll[$k] ?? 0) - (int) ($cnt[$k] ?? 0));
}

/* Fencing fails CLOSED, which is right for PHI — but a validator with NO alignment rows would then just see
 * three zeroed tabs and an empty table, which is indistinguishable from "no work today". That is how a queue
 * silently stalls. Detect the state explicitly and say so. One cheap COUNT, only for fenced users. */
$noScope = false;
if ($SCOPE !== '') {
    $ns = $pdo->prepare("SELECT COUNT(*) FROM coding_user_scope s
        WHERE s.user_id=? AND " . coding_vertical_scope_sql('s.vertical_id'));
    $ns->execute([$uid]);
    $noScope = ((int) $ns->fetchColumn() === 0);
}

$rs = $pdo->prepare("SELECT ci.*, c.name cn, f.name fn, pr.name prn, bp.name billing_prov, rp.name rendering_prov, u.name coder, au.name auditor, vu.name validator,
        COALESCE(ci.audit_secondary_reason, ci.secondary_reason) AS reason,
        COALESCE(ci.audit_comment, ci.comment) AS note,
        CASE WHEN ci.audit_status IN ('rfi','disregard') THEN ci.audit_status ELSE ci.status END AS tag,
        CASE WHEN ci.audit_status IN ('rfi','disregard') THEN 'auditor/validator' ELSE 'coder' END AS tagged_by
    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 coding_providers rp ON rp.id=ci.rendering_provider_id
    LEFT JOIN users u ON u.id=ci.coder_id LEFT JOIN users au ON au.id=ci.auditor_id
    LEFT JOIN (SELECT inventory_id, SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN event_type='validate_tag' THEN actor_id END ORDER BY created_at DESC, id DESC), ',', 1) validator_id
               FROM coding_chart_events GROUP BY inventory_id) ve ON ve.inventory_id=ci.id
    LEFT JOIN users vu ON vu.id=ve.validator_id
    WHERE {$VQ[$q]} $fw$SCOPE $csWhere ORDER BY (ci.status IN ('rfi','disregard') AND ci.audit_status IS NULL) DESC, ci.updated_at DESC LIMIT 500");
$rs->execute($fp); $rows = $rs->fetchAll();
$linesByChart = coding_effective_lines_bulk($pdo, array_column($rows, 'id'));

/* filter dropdown options — only entities present in the validate universe (any of the 3 queues).
 * $SCOPE is folded in here so ALL FOUR dropdowns below inherit it: without it the option lists would
 * still name every client / facility / provider / coder in the vertical, leaking exactly what the fence
 * is meant to hide (and offering filters that can only ever return nothing). */
$U = '(' . $REVIEW . " OR ci.audit_status IN ('rfi','disregard'))" . $SCOPE;   // same two states the queues use — derived from $REVIEW so it can't drift
$optClients = $pdo->query("SELECT DISTINCT c.id,c.name FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id WHERE $U ORDER BY c.name")->fetchAll();
$optFac     = $pdo->query("SELECT DISTINCT f.id,f.name FROM coding_inventory ci JOIN coding_facilities f ON f.id=ci.facility_id WHERE $U ORDER BY f.name")->fetchAll();
$optProv    = $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 $U ORDER BY pr.name")->fetchAll();
$optCoders  = $pdo->query("SELECT DISTINCT u.id,u.name FROM coding_inventory ci JOIN users u ON u.id=ci.coder_id WHERE $U AND ci.coder_id IS NOT NULL ORDER BY u.name")->fetchAll();

$tabUrl = fn($k) => url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$k], $qbase)));
$actUrl = fn($extra) => url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q], $qbase, $extra)));
// The date-window escapes, each built by REMOVING keys rather than adding overrides — dall=1 alongside a
// stale from/to would still work (dall wins in the filter block) but would read as contradictory in the URL.
$dallUrl  = url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q], array_diff_key($qbase, ['from'=>1,'to'=>1]), ['dall'=>'1'])));
/* "Back to today" must SET from/to empty rather than merely omit them. Omitting is not enough: remember_filters
 * replays any saved from/to into $_GET before the window is decided, so a session holding a real range would
 * silently land the user back on that range instead of today. Sending from=&to= explicitly wins, because an
 * already-set key is never overwritten by the replay (helpers.php: `if (!isset($_GET[$k]))`). */
$todayUrl = url('/admin/coding-validate.php?' . http_build_query(
    array_merge(['q'=>$q], array_diff_key($qbase, ['from'=>1,'to'=>1,'dall'=>1]), ['from'=>'', 'to'=>''])));

$pageTitle = 'Validate';
require __DIR__ . '/../includes/header.php';
$qMeta = ['toreview'=>['To review','blue','toreview'], 'rfi'=>['RFI','orange','rfi'], 'disregard'=>['Disregard','danger','disregard']];
?>
<link rel="stylesheet" href="<?= e(url('/admin/coding-ui.css')) ?>">
<div class="page-header"><div class="container-xl"><div class="row align-items-center">
    <div class="col"><h2 class="page-title"><i class="ti ti-shield-check me-2 text-primary"></i>Validate — RFI / Disregard</h2>
        <div class="text-secondary small">Only charts the coder flagged <strong>RFI</strong> / <strong>Disregard</strong> land here. Open one-by-one with <strong>Get next</strong>: confirm the disposition, or send a codable chart back to any coder. Auditors do quality only.</div>
    </div>
    <div class="col-auto btn-list">
        <a href="<?= e($actUrl(['getnext'=>1])) ?>" class="btn btn-primary"><i class="ti ti-player-play me-1"></i>Get next</a>
        <a href="<?= e($actUrl(['export'=>'xlsx'])) ?>" class="btn btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i>Excel</a>
    </div>
</div></div></div>
<div class="page-body"><div class="container-xl">
    <ul class="nav nav-tabs mb-3">
        <?php foreach ($qMeta as $k=>$m): ?>
        <li class="nav-item"><a class="nav-link <?= $q===$k?'active':'' ?>" href="<?= e($tabUrl($k)) ?>">
            <?= e($m[0]) ?> <span class="badge bg-<?= $m[1] ?>-lt ms-1"><?= (int)$cnt[$m[2]] ?></span>
            <?php /* EVERY badge is counted over $fw, so every badge is windowed to today — not just the active
                     tab's. The amber banner below only explains the tab you are ON, which left the other two
                     reading a bare "0" with no hint that work is hidden behind them. $dHidden already holds all
                     three counts, so surface each one on its own badge. */ ?>
            <?php if ($dToday && $dHidden[$m[2]] > 0): ?>
                <span class="badge bg-secondary-lt ms-1 fw-normal"
                      title="<?= (int)$dHidden[$m[2]] ?> older chart(s) have not moved today and are outside this window">
                    +<?= (int)$dHidden[$m[2]] ?></span>
            <?php endif; ?></a></li>
        <?php endforeach; ?>
    </ul>

    <?php if ($noScope): ?>
    <div class="alert alert-danger d-flex flex-wrap align-items-center gap-2 py-2 px-3 mb-3">
        <i class="ti ti-lock-exclamation"></i>
        <span><strong>You are not aligned to any client.</strong> This queue only shows charts for the clients
            assigned to you, so nothing can appear here until a supervisor aligns your account
            (Coding &rsaquo; Access &rsaquo; User alignment). The counts above are zero for the same reason —
            it does not mean the queue is empty.</span>
    </div>
    <?php endif; ?>

    <?php /* Sits directly under the badges because it explains them: the badges count the ACTIVE window, so
             on the today-default they can read 0 while older charts still wait. Never let that pass silently. */ ?>
    <?php if ($dToday && $dHidden[$q] > 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><?= (int) $dHidden[$q] ?></strong> older
            <?= e(strtolower($qMeta[$q][0])) ?> chart<?= $dHidden[$q] === 1 ? '' : 's' ?>
            <?= $q === 'toreview' ? 'still awaiting validation have' : 'have' ?> not moved today.</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; ?>

    <!-- filters -->
    <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="q" value="<?= e($q) ?>">
        <input type="hidden" name="_f" value="1">
        <?php /* Carry the view-state that has NO visible control in this card. A method=GET form replaces the
                 whole query string, so anything not echoed here is dropped by Apply — and Apply is the control
                 a validator uses constantly while working a backlog:
                   - dall: dropping it silently re-armed the today-window, undoing "Show all" on the next Apply.
                   - cs:   it IS in the remember_filters list, so dropping it made array_intersect_key wipe the
                           Pending/Sent choice out of the remembered set entirely (pre-existing).
                 Emitted only when set, so a fresh visit still lands on today per the stated design. */ ?>
        <?php if ($dall): ?><input type="hidden" name="dall" value="1"><?php endif; ?>
        <?php if ($cs !== ''): ?><input type="hidden" name="cs" value="<?= e($cs) ?>"><?php endif; ?>
        <?php $selOpt = function($name,$opts,$cur){ $h='<select name="'.e($name).'" class="form-select form-select-sm"><option value="">All</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',$optClients,$f['client_id']) ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Facility</label><?= $selOpt('facility_id',$optFac,$f['facility_id']) ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Provider</label><?= $selOpt('provider_id',$optProv,$f['provider_id']) ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Coder</label><?= $selOpt('coder_id',$optCoders,$f['coder_id']) ?></div>
        <div class="col-6 col-md-2"><label class="form-label mb-1">Coder tag</label>
            <select name="ctag" class="form-select form-select-sm">
                <option value="">All</option>
                <option value="rfi" <?= $ctag==='rfi'?'selected':'' ?>>RFI (coder)</option>
                <option value="disregard" <?= $ctag==='disregard'?'selected':'' ?>>Disregard (coder)</option>
            </select></div>
        <div class="col-6 col-md-1"><label class="form-label mb-1">Completed from</label><input type="date" name="from" class="form-control form-control-sm" value="<?= e($from) ?>"></div>
        <div class="col-6 col-md-1"><label class="form-label mb-1">to</label><input type="date" name="to" class="form-control form-control-sm" value="<?= e($to) ?>"></div>
        <div class="col-auto"><label class="form-label mb-1 d-block">&nbsp;</label><button class="btn btn-sm btn-primary"><i class="ti ti-filter me-1"></i>Apply</button>
            <a href="<?= e(url('/admin/coding-validate.php?q='.$q.'&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 if ($q === 'toreview'): /* ===== to-review: open one-by-one to confirm RFI/Disregard or send back ===== */ ?>
    <div class="card"><div class="table-responsive"><table class="table table-vcenter card-table table-sm" style="font-size:.8rem" data-coltweak="coding-validate-toreview">
        <thead><tr>
            <th data-col="patient">Patient</th><th data-col="mrn">MRN/PRN</th><th data-col="dob">DOB</th><th data-col="gender">Gender</th><th data-col="rsd">Record sign</th>
            <th data-col="client">Client</th><th data-col="facility">Facility</th><th data-col="billing">Billing prov</th>
            <th data-col="dos">DOS</th><th data-col="cpt">CPT</th><th class="text-center" data-col="units">Units</th><th data-col="mods">Modifier(s)</th><th data-col="icd">ICD(s)</th>
            <th data-col="coder">Coder</th><th data-col="codertag">Coder tag / reason</th><th data-col="submitted">Submitted</th><th class="w-1" data-colfixed></th></tr></thead>
        <tbody>
        <?php foreach ($rows as $r): $cst=$r['status']; $lines=$linesByChart[(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'] ?: '—') ?>
                    <?php $scRow = $scribeOf($r); if ($scRow !== ''): /* sub-line ONLY when this encounter has a scribe */ ?>
                        <div class="text-secondary" style="font-size:.72rem" title="Scribe (rendering provider)"><i class="ti ti-writing me-1"></i><?= e($scRow) ?></div>
                    <?php endif; ?>
                </td>
                <td class="small text-nowrap"><?= $lines ? coding_lines_cell($lines, 'dos') : ($r['dos'] ? e(fmt_date($r['dos'])) : '<span class="text-secondary">—</span>') ?></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">
                    <?php if ($cst==='disregard'): ?><span class="badge bg-danger-lt">Disregard — coder flagged</span>
                    <?php else: ?><span class="badge bg-orange-lt">RFI — coder flagged</span><?php endif; ?>
                    <?php if ($r['secondary_reason']): ?><div class="text-secondary" style="font-size:.68rem"><?= e($r['secondary_reason']) ?></div><?php endif; ?>
                </td>
                <td class="small text-secondary text-nowrap"><?= e($r['submitted_at'] ?: '—') ?></td>
                <td><a href="<?= e($actUrl(['id'=>$r['id']])) ?>" class="btn btn-sm btn-primary text-nowrap"><i class="ti ti-folder-open me-1"></i>Open</a></td>
            </tr>
        <?php endforeach; ?>
        <?php if (!$rows): ?><tr><td colspan="17" class="text-center text-secondary py-4">No charts awaiting validation. 🎉</td></tr><?php endif; ?>
        </tbody>
    </table></div></div>

<?php else: /* ===== RFI / Disregard queues — track client delivery + open one-by-one to send back ===== */
    // Sent-to-client vs pending counts for THIS bucket (respect the page filters, ignore the cs sub-filter).
    $dq = $pdo->prepare("SELECT COALESCE(SUM(ci.client_sent_at IS NOT NULL),0) sent, COALESCE(SUM(ci.client_sent_at IS NULL),0) pending
        FROM coding_inventory ci WHERE {$VQ[$q]} $fw$SCOPE");
    $dq->execute($fp); $dlv = $dq->fetch() ?: ['sent'=>0,'pending'=>0];
    $csUrl = fn($v) => url('/admin/coding-validate.php?' . http_build_query(array_merge(['q'=>$q], array_diff_key($qbase,['cs'=>1]), $v ? ['cs'=>$v] : [])));
?>
    <div class="d-flex flex-wrap gap-2 align-items-center mb-2">
        <span class="text-secondary small">Client delivery:</span>
        <div class="btn-group btn-group-sm" role="group">
            <a href="<?= e($csUrl('')) ?>" class="btn btn-outline-secondary <?= $cs===''?'active':'' ?>">All</a>
            <a href="<?= e($csUrl('pending')) ?>" class="btn btn-outline-warning <?= $cs==='pending'?'active':'' ?>">Pending <span class="badge bg-warning-lt ms-1"><?= (int)$dlv['pending'] ?></span></a>
            <a href="<?= e($csUrl('sent')) ?>" class="btn btn-outline-success <?= $cs==='sent'?'active':'' ?>">Sent to client <span class="badge bg-success-lt ms-1"><?= (int)$dlv['sent'] ?></span></a>
        </div>
    </div>
    <form method="POST" action="<?= e(url('/admin/coding-validate.php?q='.$q)) ?>">
        <?= csrf_field() ?><input type="hidden" name="q" value="<?= e($q) ?>">
        <?php /* flt = the active filters, so the post-action redirect returns to the SAME view the batch was
                 selected from rather than collapsing to the today-default. Mirrors the tag / send-back forms. */ ?>
        <input type="hidden" name="flt" value="<?= e($flt) ?>">
    <div class="card">
        <div class="card-header py-2 d-flex flex-wrap gap-2 align-items-center">
            <h3 class="card-title mb-0"><?= e(ucfirst($q)) ?> — <?= count($rows) ?> chart(s)</h3>
            <div class="ms-auto d-flex gap-2">
                <button class="btn btn-sm btn-success" name="action" value="client_sent"><i class="ti ti-send me-1"></i>Mark sent to client</button>
                <button class="btn btn-sm btn-outline-secondary" name="action" value="client_unsent" onclick="return confirm('Move selected back to pending delivery?');">Mark pending</button>
            </div>
        </div>
        <div class="table-responsive"><table class="table table-vcenter card-table table-sm" style="font-size:.8rem">
        <thead><tr>
            <th class="w-1"><input type="checkbox" class="form-check-input m-0" onclick="document.querySelectorAll('.vcb').forEach(c=>c.checked=this.checked)"></th>
            <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>Validator</th><th>Reason / note</th><th>Client delivery</th><th class="w-1"></th></tr></thead>
        <tbody>
        <?php foreach ($rows as $r): $lines=$linesByChart[(int)$r['id']] ?? []; ?>
            <tr>
                <td><input type="checkbox" class="form-check-input m-0 vcb" name="ids[]" value="<?= (int)$r['id'] ?>"></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'] ?: '—') ?>
                    <?php $scRow = $scribeOf($r); if ($scRow !== ''): /* sub-line ONLY when this encounter has a scribe */ ?>
                        <div class="text-secondary" style="font-size:.72rem" title="Scribe (rendering provider)"><i class="ti ti-writing me-1"></i><?= e($scRow) ?></div>
                    <?php endif; ?>
                </td>
                <td class="small text-nowrap"><?= $lines ? coding_lines_cell($lines, 'dos') : ($r['dos'] ? e(fmt_date($r['dos'])) : '<span class="text-secondary">—</span>') ?></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"><?= e($r['validator'] ?? '—') ?></td>
                <td class="small"><?= e($r['reason'] ?? '—') ?><?php if($r['note']): ?><div class="text-secondary" style="max-width:260px"><?= e($r['note']) ?></div><?php endif; ?></td>
                <td class="small text-nowrap"><?php if ($r['client_sent_at']): ?><span class="badge bg-success-lt">Sent</span><div class="text-secondary"><?= e(fmt_date($r['client_sent_at'])) ?></div><?php else: ?><span class="badge bg-warning-lt">Pending</span><?php endif; ?>
                    <?php if ($r['client_response_date']): ?><div class="text-info" title="<?= e($r['client_response_comment'] ?? '') ?>">Resp <?= e(fmt_date($r['client_response_date'])) ?></div><?php endif; ?></td>
                <td><a href="<?= e($actUrl(['id'=>$r['id']])) ?>" class="btn btn-sm btn-outline-primary text-nowrap"><i class="ti ti-folder-open me-1"></i>Open</a></td>
            </tr>
        <?php endforeach; ?>
        <?php if (!$rows): ?><tr><td colspan="19" class="text-center text-secondary py-4">No <?= e($q) ?> charts. 🎉</td></tr><?php endif; ?>
        </tbody>
    </table></div></div>
    </form>
<?php endif; ?>
</div></div>
<?php require __DIR__ . '/../includes/footer.php'; ?>
