<?php
/**
 * Billing allocation (billing.allocate) — the SUPERVISOR / MANAGER console, organised into TABS:
 *   • Upload backdated inventory (SysAdmin) — upload a workbook into the pool + list/export/DELETE unallocated inventory.
 *   • Run allocation     — scope by client / process / facility, pick billers, distribute round-robin by patient.
 *   • Current allocation — table of allocated charts; Reassign, Send to origin, Reallocate Hold/Unbilled, Raise RFI.
 *   • Pull back untouched — reset allocated-but-never-worked charts so they can be reshuffled.
 * Every tab has the shared filter bar + a detailed Excel export (one row per coded CPT line, with codes/units/mods/ICDs).
 * All business logic lives in includes/billing.php; this page is the UI.
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];
$isSys = is_sysadmin();

/* ---- Filters (shared across tabs). ---- */
$fClient   = (int) ($_GET['client_id'] ?? 0);
$fProcess  = (int) ($_GET['process_id'] ?? 0);
$fFacility = (int) ($_GET['facility_id'] ?? 0);
$fWho      = in_array($_GET['who'] ?? 'all', ['all','to_work','worked','hold_unbilled','unallocated'], true) ? ($_GET['who'] ?? 'all') : 'all';
$fQ        = trim((string) ($_GET['q'] ?? ''));
$fDosFrom  = coding_parse_date($_GET['dos_from'] ?? '') ?: null;
$fDosTo    = coding_parse_date($_GET['dos_to'] ?? '') ?: null;
$fBiller   = (int) ($_GET['biller_id'] ?? 0);
$tab       = in_array($_GET['tab'] ?? '', ['upload','run','current','pullback'], true) ? $_GET['tab'] : 'run';
if ($tab === 'upload' && !$isSys) $tab = 'run';                          // upload tab is SysAdmin-only

/* ---- Team scope (reports_to tree, via coding_team_scope). A SUPERVISOR sees only their team's billers; a
   MANAGER sees their whole sub-tree and can narrow with the Supervisor filter; SYSADMIN sees everyone. The
   page-handler fences below are the security boundary — the Supervisor/Biller filters are only UI narrowing. ---- */
$teamIds          = coding_team_scope($pdo, $uid);                        // null = sysadmin (unrestricted)
$allBillerIds     = billing_allocatable_biller_ids($pdo);
$allowedBillerIds = $teamIds === null ? null : array_values(array_intersect($allBillerIds, $teamIds));
$fSup = (int) ($_GET['sup_id'] ?? 0);
if ($fSup && $teamIds !== null && !in_array($fSup, $teamIds, true)) $fSup = 0;   // never filter outside your scope
if ($fBiller && $allowedBillerIds !== null && !in_array($fBiller, $allowedBillerIds, true)) $fBiller = 0;   // biller filter fenced to scope
$effBillerIds = $allowedBillerIds;                                        // table/scope set (honours the Supervisor filter)
if ($fSup) {
    $supTeam = coding_managed_user_ids($pdo, $fSup);                      // the supervisor's REPORTS only — NOT the supervisor
    $supBillers = array_values(array_intersect($allBillerIds, $supTeam));
    $effBillerIds = $allowedBillerIds === null ? $supBillers : array_values(array_intersect($allowedBillerIds, $supBillers));
}
if ($effBillerIds !== null) $effBillerIds = array_values(array_diff($effBillerIds, [$uid]));
$pickerIds = array_values(array_diff(($effBillerIds === null ? $allBillerIds : $effBillerIds), [$uid]));
// Security fences (applied to every POST — UI filters can be forged).
$fenceBillers  = fn(array $sel) => $allowedBillerIds === null
    ? array_values(array_filter(array_map('intval', $sel)))
    : array_values(array_intersect(array_map('intval', $sel), $allowedBillerIds));
$fenceAllocIds = function (array $ids) use ($pdo, $teamIds): array {
    $ids = array_values(array_filter(array_map('intval', $ids)));
    if (!$ids || $teamIds === null) return $ids;                         // sysadmin: no restriction
    if (!$teamIds) return [];
    $in  = implode(',', array_fill(0, count($ids), '?'));
    $tin = implode(',', array_map('intval', $teamIds));
    $q = $pdo->prepare("SELECT id FROM billing_allocation WHERE id IN ($in) AND allocated_biller IN ($tin)");
    $q->execute($ids);
    return array_map('intval', $q->fetchAll(PDO::FETCH_COLUMN));
};

$rw = billing_released_where('ci');

/* Cascade dropdowns: the Process list depends on the picked Client, the Facility list on Client + Process
 * (coding_facilities has no client_id — the link is facility→process→client). A stale child selection (e.g. a
 * process that isn't under the newly-picked client, submitted by the auto-submit before the DOM resets) is reset
 * to 0. This MUST run BEFORE $ctx / $applyChartFilters capture $fProcess/$fFacility, or the data queries would
 * still filter by the stale id. Lists keep the "only facilities that actually have released inventory" behaviour. */
$clients = $pdo->query("SELECT DISTINCT c.id,c.name FROM coding_inventory ci JOIN clients c ON c.id=ci.client_id WHERE $rw ORDER BY c.name")->fetchAll();
$procW = [$rw]; $procP = [];
if ($fClient) { $procW[] = 'ci.client_id=?'; $procP[] = $fClient; }
$ps = $pdo->prepare("SELECT DISTINCT p.id,p.name,c.name cn FROM coding_inventory ci JOIN processes p ON p.id=ci.process_id JOIN clients c ON c.id=ci.client_id WHERE " . implode(' AND ', $procW) . " ORDER BY c.name,p.name");
$ps->execute($procP); $procs = $ps->fetchAll();
if ($fProcess && !in_array($fProcess, array_map(fn($o) => (int) $o['id'], $procs), true)) $fProcess = 0;   // reset stale process
$facW = [$rw]; $facP = [];
if ($fClient)  { $facW[] = 'ci.client_id=?';  $facP[] = $fClient; }
if ($fProcess) { $facW[] = 'ci.process_id=?'; $facP[] = $fProcess; }
$fs = $pdo->prepare("SELECT DISTINCT f.id,f.name FROM coding_inventory ci JOIN coding_facilities f ON f.id=ci.facility_id WHERE " . implode(' AND ', $facW) . " ORDER BY f.name");
$fs->execute($facP); $facils = $fs->fetchAll();
if ($fFacility && !in_array($fFacility, array_map(fn($o) => (int) $o['id'], $facils), true)) $fFacility = 0;   // reset stale facility

$ctx = array_filter([
    'client_id'=>$fClient?:null, 'process_id'=>$fProcess?:null, 'facility_id'=>$fFacility?:null,
    'who'=>$fWho!=='all'?$fWho:null, 'sup_id'=>$fSup?:null, 'q'=>$fQ!==''?$fQ:null,
    'dos_from'=>$fDosFrom, 'dos_to'=>$fDosTo, 'biller_id'=>$fBiller?:null, 'tab'=>$tab,
], fn($v)=>$v!==null && $v!=='' && $v!==0);
$selfUrl = fn(array $o = []) => '/admin/billing-allocation.php' . (($q = array_merge($ctx,$o))?'?'.http_build_query(array_filter($q, fn($v)=>$v!==null&&$v!==''&&$v!==0)):'');

/* Add the shared chart-level filters (client/process/facility/patient-or-MRN/DOS range) to a WHERE builder. */
$applyChartFilters = function (array &$w, array &$p) use ($fClient,$fProcess,$fFacility,$fQ,$fDosFrom,$fDosTo) {
    if ($fClient)   { $w[]='ci.client_id=?';   $p[]=$fClient; }
    if ($fProcess)  { $w[]='ci.process_id=?';  $p[]=$fProcess; }
    if ($fFacility) { $w[]='ci.facility_id=?'; $p[]=$fFacility; }
    if ($fQ !== '') { $w[]='(ci.patient_name LIKE ? OR ci.patient_mrn LIKE ?)'; $p[]="%$fQ%"; $p[]="%$fQ%"; }
    if ($fDosFrom)  { $w[]='ci.dos>=?'; $p[]=$fDosFrom; }
    if ($fDosTo)    { $w[]='ci.dos<=?'; $p[]=$fDosTo; }
};

/* Detailed-export helper: expand each chart row into ONE ROW PER coded (audit-corrected) CPT LINE, appending the
 * Line DOS / CPT / Units / Modifier(s) / ICD(s) columns after the caller's base columns. Charts with no coded line
 * still emit one row (blank code columns). $baseVals($row) returns the leading columns for that chart. */
$lineHeaders = ['Line DOS','CPT','Units','Modifier(s)','ICD(s)'];
$emitLines = function (array $chartRows, callable $baseVals) use ($pdo): array {
    $invIds = array_map(fn($r) => (int) $r['id'], $chartRows);
    $linesByInv = coding_effective_lines_bulk($pdo, $invIds);
    $out = [];
    foreach ($chartRows as $r) {
        $base  = $baseVals($r);
        $lines = $linesByInv[(int) $r['id']] ?? [];
        if (!$lines) { $out[] = array_merge($base, ['', '', '', '', '']); continue; }
        foreach ($lines as $ln) {
            $out[] = 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'] ?? []),
            ]);
        }
    }
    return $out;
};

/* ============================================================================ EXCEL EXPORTS (detailed, with codes) */
$export = (string) ($_GET['export'] ?? '');

/* --- Unallocated inventory (Upload tab, SysAdmin) — released to billing, not yet allocated to any biller. --- */
if ($export === 'unalloc') {
    if (!$isSys) { http_response_code(403); exit('Forbidden'); }
    $w = [$rw, "NOT EXISTS (SELECT 1 FROM billing_allocation a WHERE a.inventory_id=ci.id AND a.allocated_biller IS NOT NULL)"]; $p = [];
    $applyChartFilters($w, $p);
    $q = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.patient_dob, ci.gender, ci.dos, ci.released_to_billing_at,
            c.name cn, p.name pn, f.name fn, pr.name prn
        FROM coding_inventory ci
        LEFT JOIN clients c ON c.id=ci.client_id LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        WHERE " . implode(' AND ', $w) . " ORDER BY ci.released_to_billing_at DESC, ci.id DESC LIMIT 20000");
    $q->execute($p); $rows = $q->fetchAll();
    $out = $emitLines($rows, fn($r) => [
        (int) $r['id'], $r['patient_name'], $r['patient_mrn'] ?: '',
        !empty($r['patient_dob']) ? date('Y-m-d', strtotime($r['patient_dob'])) : '',
        $r['gender'] ?: '', $r['cn'], $r['pn'], $r['fn'], $r['prn'],
        !empty($r['dos']) ? date('Y-m-d', strtotime($r['dos'])) : '',
        disp_dt($r['released_to_billing_at']),
    ]);
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $x->addSheet('Unallocated inventory', array_merge(['Chart #','Patient','MRN','DOB','Gender','Client','Process','Facility','Provider','Chart DOS','Received at'], $lineHeaders), $out);
    audit_log('billing.allocation.export_unalloc', 'scope', null, null, ['rows'=>count($out), 'client'=>$fClient, 'process'=>$fProcess, 'facility'=>$fFacility]);
    $x->download('billing-unallocated-inventory.xlsx');
    exit;
}

/* --- Received inventory (Run allocation tab) — everything released to billing in scope (unallocated + allocated).
   Unallocated pool is shared; allocated rows are fenced to the actor's team (sysadmin = all). --- */
if ($export === 'received') {
    $w = [$rw]; $p = [];
    $applyChartFilters($w, $p);
    if ($allowedBillerIds !== null) {
        $ids = $allowedBillerIds ? implode(',', array_map('intval', $allowedBillerIds)) : '0';
        $w[] = "(a.allocated_biller IS NULL OR a.allocated_biller IN ($ids))";
    }
    // Biller filter narrows the ALLOCATED portion to that biller (the shared unallocated pool always stays visible).
    if ($fBiller) $w[] = "(a.allocated_biller IS NULL OR a.allocated_biller=" . (int) $fBiller . ")";
    $q = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.dos, ci.released_to_billing_at,
            c.name cn, p.name pn, f.name fn, pr.name prn,
            a.allocated_biller, a.inventory_status, a.billing_status, a.carrier, a.allocated_at, u.name biller
        FROM coding_inventory ci
        LEFT JOIN billing_allocation a ON a.inventory_id=ci.id
        LEFT JOIN clients c ON c.id=ci.client_id LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        LEFT JOIN users u ON u.id=a.allocated_biller
        WHERE " . implode(' AND ', $w) . "
        ORDER BY (a.allocated_biller IS NOT NULL), ci.released_to_billing_at ASC, ci.id ASC LIMIT 20000");
    $q->execute($p); $rows = $q->fetchAll();
    $out = $emitLines($rows, function ($r) {
        $alloc = $r['allocated_biller'] ? ($r['inventory_status'] === 'worked' ? 'Worked' : 'Allocated – to work') : 'Unallocated';
        return [(int) $r['id'], $r['patient_name'], $r['patient_mrn'] ?: '', $r['cn'], $r['pn'], $r['fn'], $r['prn'],
            !empty($r['dos']) ? date('Y-m-d', strtotime($r['dos'])) : '',
            disp_dt($r['released_to_billing_at']),
            $alloc, $r['biller'] ?: '', $r['billing_status'] ?: '', $r['carrier'] ?: '',
            disp_dt($r['allocated_at'])];
    });
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $x->addSheet('Received inventory', array_merge(['Chart #','Patient','MRN','Client','Process','Facility','Provider','Chart DOS','Received at','Allocation','Biller','Billing status','Carrier','Allocated at'], $lineHeaders), $out);
    audit_log('billing.allocation.export_received', 'scope', null, null, ['rows'=>count($out), 'client'=>$fClient, 'process'=>$fProcess, 'facility'=>$fFacility]);
    $x->download('billing-received-inventory.xlsx');
    exit;
}

/* --- Current allocation (Current tab) + Untouched (Pull back tab): allocated charts, team-fenced + filters.
       EXCEPTION: Current tab with who=unallocated mirrors the on-screen UNALLOCATED pool instead (coding_inventory
       base, no owner → shared pool, no team fence) so the download matches what the screen shows. --- */
if ($export === 'current' || $export === 'untouched') {
    $unallocExport = ($export === 'current' && $fWho === 'unallocated');
    if ($unallocExport) {
        $w = [$rw, 'a.allocated_biller IS NULL']; $p = [];
        $applyChartFilters($w, $p);
        $from = "FROM coding_inventory ci LEFT JOIN billing_allocation a ON a.inventory_id=ci.id";
    } else {
        $w = ['a.allocated_biller IS NOT NULL', $rw]; $p = [];
        $applyChartFilters($w, $p);
        if ($export === 'untouched') { $w[] = "a.inventory_status='allocated'"; }
        elseif ($fWho === 'to_work')      { $w[] = "a.inventory_status='allocated'"; }
        elseif ($fWho === 'worked')       { $w[] = "a.inventory_status='worked'"; }
        elseif ($fWho === 'hold_unbilled'){ $w[] = "a.billing_status IN ('hold_deductible','unbilled')"; }
        if ($fBiller) { $w[] = 'a.allocated_biller=?'; $p[] = $fBiller; }
        if ($effBillerIds !== null) $w[] = $effBillerIds ? ('a.allocated_biller IN (' . implode(',', array_map('intval', $effBillerIds)) . ')') : '1=0';
        $from = "FROM billing_allocation a JOIN coding_inventory ci ON ci.id=a.inventory_id";
    }
    $q = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.dos,
            c.name cn, p.name pn, f.name fn, pr.name prn,
            a.inventory_status, a.billing_status, a.carrier, a.secondary_reason, a.allocated_at, a.last_worked_date, u.name biller
        $from
        LEFT JOIN clients c ON c.id=ci.client_id LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        LEFT JOIN users u ON u.id=a.allocated_biller
        WHERE " . implode(' AND ', $w) . " ORDER BY " . ($unallocExport ? 'c.name' : 'u.name') . ", ci.patient_name LIMIT 20000");
    $q->execute($p); $rows = $q->fetchAll();
    $invLabel = ['allocated'=>'Allocated – to work', 'worked'=>'Worked'];
    $bsLabel  = ['billed'=>'Billed', 'hold_deductible'=>'Hold – deductible', 'unbilled'=>'Unbilled'];
    $out = $emitLines($rows, fn($r) => [
        (int) $r['id'], $r['patient_name'], $r['patient_mrn'] ?: '', $r['cn'], $r['pn'], $r['fn'], $r['prn'],
        !empty($r['dos']) ? date('Y-m-d', strtotime($r['dos'])) : '',
        $r['biller'] ?: '(unassigned)',
        $invLabel[$r['inventory_status']] ?? ($r['inventory_status'] ?: '—'),
        $bsLabel[$r['billing_status']] ?? ($r['billing_status'] ?: '—'),
        $r['carrier'] ?: '', $r['secondary_reason'] ?: '',
        disp_dt($r['allocated_at']),
        !empty($r['last_worked_date']) ? date('Y-m-d', strtotime($r['last_worked_date'])) : '',
    ]);
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $title = $export === 'untouched' ? 'Untouched allocation' : ($unallocExport ? 'Unallocated inventory' : 'Current allocation');
    $x->addSheet($title, array_merge(['Chart #','Patient','MRN','Client','Process','Facility','Provider','Chart DOS','Biller','Inventory status','Billing status','Carrier','Secondary reason','Allocated at','Last worked'], $lineHeaders), $out);
    audit_log('billing.allocation.export_' . $export, 'scope', null, null, ['rows'=>count($out), 'client'=>$fClient, 'process'=>$fProcess, 'facility'=>$fFacility, 'who'=>$fWho, 'sup'=>$fSup, 'biller'=>$fBiller]);
    $x->download('billing-' . ($unallocExport ? 'unallocated' : $export) . '-allocation.xlsx');
    exit;
}

/* ---- Backdated-inventory upload template (.xlsx) — SysAdmin only. ---- */
if (($_GET['inv_template'] ?? '') === 'xlsx') {
    if (!$isSys) { http_response_code(403); exit('Forbidden'); }
    require_once __DIR__ . '/../includes/xlsx.php';
    $x = new SimpleXlsx();
    $x->addSheet('Backdated inventory',
        ['Client', 'Process', 'Facility', 'Provider', 'Patient name', 'MRN (optional)', 'DOB', 'DOS', 'CPT', 'Units', 'Modifier(s)', 'ICD(s)', 'Received date (optional)'],
        [
            ['01 Maximize', '01 Peyman Younesi', 'ABC - Main Hospital', 'Smith, John MD', 'DOE, JANE', 'MRN12345', '1980-05-01', '2026-01-15', '99213', '1', '25', 'E11.9, I10', ''],
            ['01 Maximize', '01 Peyman Younesi', 'ABC - Main Hospital', 'Smith, John MD', 'DOE, JANE', 'MRN12345', '1980-05-01', '2026-01-15', '93000', '1', '', 'I10', ''],
        ]);
    $x->download('backdated-inventory-template.xlsx');
    exit;
}

/* ============================================================================ POST (PRG) */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf_check_or_die();
    if (!can('billing.allocate')) { http_response_code(403); exit('Forbidden'); }
    $act = (string) ($_POST['action'] ?? '');

    /* SysAdmin-only: upload a backdated-inventory workbook → creates released-to-billing charts in the pool. */
    if ($act === 'upload_inventory') {
        if (!$isSys) { flash('error', 'Only SysAdmin can upload backdated inventory.'); redirect($selfUrl(['tab'=>'upload'])); }
        if (empty($_FILES['file']['tmp_name']) || !is_uploaded_file($_FILES['file']['tmp_name'])) { flash('error', 'Choose an .xlsx file to upload.'); redirect($selfUrl(['tab'=>'upload'])); }
        try {
            require_once __DIR__ . '/../includes/xlsx-reader.php';
            $rd = new XlsxReader($_FILES['file']['tmp_name']); $rows = []; $i = 0;
            foreach ($rd->rows(0) as $r) {
                $i++; if ($i === 1) continue;                                     // header row
                if (trim((string)($r[0] ?? '')) === '' && trim((string)($r[4] ?? '')) === '' && trim((string)($r[8] ?? '')) === '') continue;   // blank row
                $rows[] = ['client'=>$r[0]??'','process'=>$r[1]??'','facility'=>$r[2]??'','provider'=>$r[3]??'','patient'=>$r[4]??'','mrn'=>$r[5]??'','dob'=>$r[6]??'','dos'=>$r[7]??'','cpt'=>$r[8]??'','units'=>$r[9]??'','mods'=>$r[10]??'','icds'=>$r[11]??'','received'=>$r[12]??''];
            }
            $rd->close();
            if (!$rows) { flash('error', 'No data rows found on the first sheet.'); redirect($selfUrl(['tab'=>'upload'])); }
            $rep = billing_import_backdated_inventory($pdo, $rows, $uid);
            $_SESSION['bkdated_inv_result'] = ['at' => time(), 'rows' => $rep['rows'] ?? []];
            audit_log('billing.inventory.upload', 'coding_inventory', null, null, ['created'=>(int)$rep['created'], 'lines'=>(int)($rep['lines'] ?? 0), 'errors'=>count($rep['errors'])]);
            $errN = count($rep['errors']);
            flash($rep['created'] ? 'success' : 'error', (int)$rep['created'] . ' chart(s) with ' . (int)($rep['lines'] ?? 0) . ' coded line(s) added to the billing pool (released to billing).' . ($errN ? " {$errN} row(s) had errors — see the results below." : ''));
        } catch (Throwable $e) {
            flash('error', 'Could not read the workbook: ' . (defined('APP_DEBUG') && APP_DEBUG ? $e->getMessage() : 'invalid .xlsx file'));
        }
        redirect($selfUrl(['tab'=>'upload', 'showupload' => 1]));
    }

    /* SysAdmin-only: delete UNALLOCATED released charts (bad upload cleanup). */
    if ($act === 'delete_unalloc') {
        if (!$isSys) { flash('error', 'Only SysAdmin can delete unallocated inventory.'); redirect($selfUrl(['tab'=>'upload'])); }
        $ids = array_map('intval', (array) ($_POST['ids'] ?? []));
        if (!$ids) { flash('error', 'Select at least one unallocated chart to delete.'); redirect($selfUrl(['tab'=>'upload'])); }
        try {
            $res = billing_delete_unallocated_inventory($pdo, $ids, $uid);
            flash($res['deleted'] ? 'success' : 'error',
                $res['deleted'] . ' unallocated chart(s) deleted.' . ($res['skipped'] ? " {$res['skipped']} skipped (already allocated / not eligible)." : ''));
        } catch (Throwable $e) { flash('error', 'Delete failed: ' . (defined('APP_DEBUG') && APP_DEBUG ? $e->getMessage() : 'error')); }
        redirect($selfUrl(['tab'=>'upload']));
    }

    if ($act === 'run_allocation') {
        $billers = $fenceBillers((array) ($_POST['biller_ids'] ?? []));   // only billers on your team
        if (!$billers) flash('error', 'Pick at least one biller on your team.');
        else {
            $res = billing_run_allocation($pdo, ['client_id'=>(int)($_POST['client_id']??0),'process_id'=>(int)($_POST['process_id']??0),'facility_id'=>(int)($_POST['facility_id']??0)], $billers, $uid);
            $n = (int) $res['allocated']; $m = count(array_filter($res['per_user'] ?? [], fn($c)=>$c>0)); $pc = (int) ($res['patients'] ?? 0);
            flash($n?'success':'error', $n ? "Allocated $n chart(s) ($pc patient(s)) across $m biller(s) — every chart of a patient goes to one biller." : 'No unallocated released charts matched that scope.');
        }
        redirect($selfUrl(['tab'=>'run']));
    }

    if ($act === 'reassign') {
        $ids = $fenceAllocIds((array) ($_POST['ids'] ?? []));            // only charts owned by your team
        $to  = (int) ($_POST['to_user'] ?? 0);
        if ($to && !$fenceBillers([$to])) { flash('error', 'That biller is not on your team.'); redirect($selfUrl(['tab'=>'current'])); }
        if (!$ids) flash('error', 'Select at least one chart on your team.');
        else { $n = billing_reassign($pdo, $ids, $to ?: null, $uid);
            flash('success', $to ? "Reassigned $n chart(s)." : "Sent $n chart(s) back to origin (unallocated)."); }
        redirect($selfUrl(['tab'=>'current']));
    }

    if ($act === 'reallocate') {
        $ids = $fenceAllocIds((array) ($_POST['ids'] ?? []));            // only charts owned by your team
        $billers = $fenceBillers((array) ($_POST['biller_ids'] ?? []));   // only billers on your team
        if (!$ids)          flash('error', 'Select Hold-Deductible / Unbilled chart(s) on your team to reallocate.');
        elseif (!$billers)  flash('error', 'Pick at least one biller on your team to redistribute across.');
        else { $n = billing_reallocate($pdo, $ids, $billers, $uid, $allowedBillerIds);
            flash($n?'success':'error', $n ? "Reallocated $n Hold/Unbilled chart(s)." : 'None of the selected charts were Hold-Deductible or Unbilled.'); }
        redirect($selfUrl(['tab'=>'current']));
    }

    if ($act === 'raise_rfi') {
        $ids = $fenceAllocIds((array) ($_POST['ids'] ?? []));            // only charts owned by your team
        $reason = trim((string) input('rfi_reason'));
        if (!$ids) flash('error', 'Select at least one account on your team to raise as RFI.');
        else {
            $in = implode(',', array_fill(0, count($ids), '?'));
            $st = $pdo->prepare("SELECT inventory_id FROM billing_allocation WHERE id IN ($in)");
            $st->execute($ids); $invIds = array_map('intval', $st->fetchAll(PDO::FETCH_COLUMN));
            $n = billing_raise_rfi($pdo, $invIds, $uid, $reason);
            flash($n?'success':'error', $n ? "Raised $n account(s) to coding as RFI (removed from billing)." : 'Those accounts could not be raised (already pulled back).');
        }
        redirect($selfUrl(['tab'=>'current']));
    }

    if ($act === 'pullback') {
        $onlyUser = (int) ($_POST['only_user'] ?? 0);
        if ($onlyUser && !$fenceBillers([$onlyUser])) { flash('error', 'That biller is not on your team.'); redirect($selfUrl(['tab'=>'pullback'])); }
        $n = billing_pullback_untouched($pdo, $onlyUser ?: null, $uid, $allowedBillerIds);
        flash('success', "Pulled $n untouched chart(s) back to origin.");
        redirect($selfUrl(['tab'=>'pullback']));
    }
    redirect($selfUrl());
}

/* ============================================================================ GET data */
/* $clients/$procs/$facils are built above (cascade + stale-reset). */
$billers  = billing_allocatable_billers($pdo, $pickerIds);   // who you allocate TO: team/supervisor-scoped, minus self

/* Supervisor-filter options = the reports_to supervisors OF the allocatable billers (managers/sysadmin only). */
$supOpts = [];
if ($allBillerIds) {
    $bin = implode(',', array_map('intval', $allBillerIds));
    $supOpts = $pdo->query("SELECT DISTINCT sup.id, sup.name FROM users b JOIN users sup ON sup.id=b.reports_to
        WHERE b.id IN ($bin) AND sup.id<>" . (int) $uid . coding_team_in_sql($teamIds, 'sup.id') . " ORDER BY sup.name")->fetchAll();
}

$unalloc = billing_unallocated_count($pdo, ['client_id'=>$fClient,'process_id'=>$fProcess,'facility_id'=>$fFacility]);
/* Total claims sent to bill (released to billing) in this scope — allocated + unallocated. Drives the Run-allocation headline. */
$sentToBill = (function () use ($pdo, $rw, $fClient, $fProcess, $fFacility) {
    $w = [$rw]; $p = [];
    if ($fClient)   { $w[] = 'ci.client_id=?';   $p[] = (int) $fClient; }
    if ($fProcess)  { $w[] = 'ci.process_id=?';  $p[] = (int) $fProcess; }
    if ($fFacility) { $w[] = 'ci.facility_id=?'; $p[] = (int) $fFacility; }
    $st = $pdo->prepare('SELECT COUNT(*) FROM coding_inventory ci WHERE ' . implode(' AND ', $w));
    $st->execute($p); return (int) $st->fetchColumn();
})();

/* ---- Per-tab data (only the active tab's heavy query runs). ---- */
$allocRows = []; $untouchedRows = []; $unallocRows = []; $unallocTotal = 0; $allocSummary = [];

if ($tab === 'current' && $fWho !== 'unallocated') {
    $where = ['a.allocated_biller IS NOT NULL', $rw]; $params = [];
    $applyChartFilters($where, $params);
    if ($fWho==='to_work')            $where[]="a.inventory_status='allocated'";
    elseif ($fWho==='worked')         $where[]="a.inventory_status='worked'";
    elseif ($fWho==='hold_unbilled')  $where[]="a.billing_status IN ('hold_deductible','unbilled')";
    if ($fBiller) { $where[]='a.allocated_biller=?'; $params[]=$fBiller; }
    if ($effBillerIds !== null) $where[] = $effBillerIds ? ('a.allocated_biller IN (' . implode(',', array_map('intval', $effBillerIds)) . ')') : '1=0';
    $wsql = implode(' AND ', $where);
    // SUMMARY (the primary view): per biller → process → facility → provider — claim count + most recent DOS.
    $sumSt = $pdo->prepare("SELECT a.allocated_biller, u.name biller, p.name pn, f.name fn,
            COALESCE(bp.name, cp.name, pr.name) provider, COUNT(*) claims, MAX(ci.dos) recent_dos
        FROM billing_allocation a
        JOIN coding_inventory ci ON ci.id=a.inventory_id
        LEFT JOIN users u ON u.id=a.allocated_biller
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers bp ON bp.id=ci.billing_provider_id
        LEFT JOIN coding_providers cp ON cp.id=ci.coded_provider_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        WHERE $wsql
        GROUP BY a.allocated_biller, u.name, p.name, f.name, provider
        ORDER BY u.name, p.name, f.name, provider LIMIT 5000");
    $sumSt->execute($params); $allocSummary = $sumSt->fetchAll();
    // DETAIL (chart-level, for the actions): kept behind a toggle.
    $st = $pdo->prepare("SELECT a.id, a.inventory_id, a.inventory_status, a.billing_status, a.carrier, a.secondary_reason, a.last_worked_date,
            ci.patient_name, ci.dos, c.name cn, f.name fn, u.name biller
        FROM billing_allocation a
        JOIN coding_inventory ci ON ci.id=a.inventory_id
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN users u ON u.id=a.allocated_biller
        WHERE $wsql ORDER BY u.name, ci.patient_name LIMIT 2000");
    $st->execute($params); $allocRows = $st->fetchAll();
}

if ($tab === 'current' && $fWho === 'unallocated') {
    // UNALLOCATED: released charts with NO biller — anchor on coding_inventory (the allocated-only base can't yield
    // them). No owner to team-fence on, so this shows the shared unallocated pool in the client/process/facility
    // scope (same shape as the received export). Rendered read-only: no allocation id → no reassign/RFI actions.
    $where = [$rw, 'a.allocated_biller IS NULL']; $params = [];
    $applyChartFilters($where, $params);
    $wsql = implode(' AND ', $where);
    $sumSt = $pdo->prepare("SELECT p.name pn, f.name fn, COALESCE(bp.name, cp.name, pr.name) provider,
            COUNT(*) claims, MAX(ci.dos) recent_dos
        FROM coding_inventory ci
        LEFT JOIN billing_allocation a ON a.inventory_id=ci.id
        LEFT JOIN processes p ON p.id=ci.process_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN coding_providers bp ON bp.id=ci.billing_provider_id
        LEFT JOIN coding_providers cp ON cp.id=ci.coded_provider_id
        LEFT JOIN coding_providers pr ON pr.id=ci.provider_id
        WHERE $wsql GROUP BY p.name, f.name, provider ORDER BY p.name, f.name, provider LIMIT 5000");
    $sumSt->execute($params);
    $allocSummary = array_map(fn($r) => ['allocated_biller'=>null, 'biller'=>'(unallocated)', 'pn'=>$r['pn'],
        'fn'=>$r['fn'], 'provider'=>$r['provider'], 'claims'=>$r['claims'], 'recent_dos'=>$r['recent_dos']], $sumSt->fetchAll());
    $st = $pdo->prepare("SELECT NULL AS id, ci.id AS inventory_id, NULL AS inventory_status, NULL AS billing_status,
            NULL AS carrier, NULL AS secondary_reason, NULL AS last_worked_date,
            ci.patient_name, ci.dos, c.name cn, f.name fn, NULL AS biller
        FROM coding_inventory ci
        LEFT JOIN billing_allocation a ON a.inventory_id=ci.id
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        WHERE $wsql ORDER BY c.name, ci.patient_name LIMIT 2000");
    $st->execute($params); $allocRows = $st->fetchAll();
}

if ($tab === 'pullback') {
    $where = ['a.allocated_biller IS NOT NULL', "a.inventory_status='allocated'", $rw]; $params = [];
    $applyChartFilters($where, $params);
    if ($fBiller) { $where[]='a.allocated_biller=?'; $params[]=$fBiller; }
    if ($effBillerIds !== null) $where[] = $effBillerIds ? ('a.allocated_biller IN (' . implode(',', array_map('intval', $effBillerIds)) . ')') : '1=0';
    $st = $pdo->prepare("SELECT a.id, a.inventory_id, a.allocated_at, ci.patient_name, ci.dos, c.name cn, f.name fn, u.name biller
        FROM billing_allocation a
        JOIN coding_inventory ci ON ci.id=a.inventory_id
        LEFT JOIN clients c ON c.id=ci.client_id
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN users u ON u.id=a.allocated_biller
        WHERE " . implode(' AND ', $where) . " ORDER BY u.name, ci.patient_name LIMIT 2000");
    $st->execute($params); $untouchedRows = $st->fetchAll();
}

if ($tab === 'upload' && $isSys) {
    $where = [$rw, "NOT EXISTS (SELECT 1 FROM billing_allocation a WHERE a.inventory_id=ci.id AND a.allocated_biller IS NOT NULL)"]; $params = [];
    $applyChartFilters($where, $params);
    $wsql = implode(' AND ', $where);
    $unallocTotal = (int) (function () use ($pdo, $wsql, $params) {
        $c = $pdo->prepare("SELECT COUNT(*) FROM coding_inventory ci WHERE $wsql"); $c->execute($params); return $c->fetchColumn();
    })();
    $st = $pdo->prepare("SELECT ci.id, ci.patient_name, ci.patient_mrn, ci.dos, ci.released_to_billing_at,
            c.name cn, f.name fn, pr.name prn,
            (SELECT COUNT(*) FROM coding_chart_cpts cc WHERE cc.inventory_id=ci.id) AS line_n
        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
        WHERE $wsql ORDER BY ci.released_to_billing_at DESC, ci.id DESC LIMIT 1000");
    $st->execute($params); $unallocRows = $st->fetchAll();
}

/* Facility-wise claim summary (Run tab, req 3): ALL released charts in the applied scope grouped by facility,
 * split allocated vs unallocated. Honours the full shared filter set (client/process/facility/patient/DOS). */
$facilitySummary = [];
if ($tab === 'run') {
    $w = [$rw]; $p = [];
    $applyChartFilters($w, $p);
    $fst = $pdo->prepare("SELECT ci.facility_id, f.name fn, COUNT(*) claims,
            COALESCE(SUM(a.allocated_biller IS NOT NULL),0) allocated,
            COALESCE(SUM(a.allocated_biller IS NULL),0)     unallocated
        FROM coding_inventory ci
        LEFT JOIN coding_facilities f ON f.id=ci.facility_id
        LEFT JOIN billing_allocation a ON a.inventory_id=ci.id
        WHERE " . implode(' AND ', $w) . "
        GROUP BY ci.facility_id, f.name ORDER BY claims DESC");
    $fst->execute($p); $facilitySummary = $fst->fetchAll();
}

/* Per-biller existing-vs-pending workload (Run tab, req 5) — over the pickable billers. */
$billerLoad = ($tab === 'run') ? billing_users_open_load($pdo, array_column($billers, 'id')) : [];

/* Pull-back by-biller breakdown of untouched (allocated, never worked) charts (Pull-back tab, req 6). Fenced with
 * $effBillerIds (NOT $allowedBillerIds) so the breakdown, its total, and each drill-down target match the detail
 * table below — which honours the Supervisor filter + self-exclusion. */
$untouchedBreakdown = ($tab === 'pullback') ? billing_untouched_breakdown($pdo, $effBillerIds) : ['by_user'=>[], 'total'=>0];

$bsBadge = ['billed'=>'green','hold_deductible'=>'orange','unbilled'=>'red'];
$pageTitle = 'Allocate billing';
require __DIR__ . '/../includes/header.php';

/** Searchable, scrollable biller picker (handles 100+ billers). */
$billerPicker = function () use ($billers) {
    static $seq = 0; $seq++;
    ?>
    <div class="biller-picker" data-bp="<?= $seq ?>">
        <div class="d-flex flex-wrap gap-2 mb-1 align-items-center">
            <input type="search" class="form-control form-control-sm bp-search" placeholder="Search billers…" style="max-width:240px" autocomplete="off">
            <button type="button" class="btn btn-sm btn-outline-secondary bp-all"><i class="ti ti-checks me-1"></i>Select all shown</button>
            <button type="button" class="btn btn-sm btn-ghost-secondary bp-none">Clear</button>
            <span class="text-secondary small bp-count"></span>
        </div>
        <div class="row g-1 bp-list border rounded p-2" style="max-height:240px;overflow:auto">
            <?php foreach ($billers as $b): ?>
                <div class="col-md-4 col-sm-6 bp-item" data-name="<?= e(mb_strtolower($b['name'] . ' ' . ($b['email'] ?? ''))) ?>">
                    <label class="form-check mb-0"><input class="form-check-input bp-cb" type="checkbox" name="biller_ids[]" value="<?= (int)$b['id'] ?>"><span class="form-check-label small"><?= e($b['name']) ?></span></label>
                </div>
            <?php endforeach; ?>
        </div>
    </div>
    <?php
};

/* Tab nav helper. */
$tabs = [];
if ($isSys) $tabs['upload'] = ['Upload backdated inventory', 'ti-upload'];
$tabs['run']      = ['Run allocation', 'ti-player-play'];
$tabs['current']  = ['Current allocation', 'ti-users'];
$tabs['pullback'] = ['Pull back untouched', 'ti-arrow-back-up'];
$exportLabel  = ['upload'=>'Download unallocated inventory', 'run'=>'Download received inventory', 'current'=>'Download current allocation', 'pullback'=>'Download untouched charts'];
?>
<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-arrows-split me-2 text-primary"></i>Allocate billing</h2>
        <div class="text-secondary small">Distribute the charts the coding team <strong>released to billing</strong> across your billers. Every tab shares the filters below; each has a detailed Excel export (one row per coded CPT line).</div>
    </div>
    <div class="col-auto"><a href="<?= e(url('/admin/billing-codes.php')) ?>" class="btn btn-outline-secondary"><i class="ti ti-list-details me-1"></i>Carriers &amp; codes</a></div>
</div></div></div>

<div class="page-body"><div class="container-xl">

    <!-- =============================================== shared filters -->
    <form method="GET" class="card mb-3"><div class="card-body">
        <input type="hidden" name="tab" value="<?= e($tab) ?>">
        <div class="row g-2 align-items-end">
            <div class="col-md-3 col-lg-2"><label class="form-label">Client</label><select name="client_id" class="form-select form-select-sm" onchange="this.form.submit()"><option value="">All clients</option><?php foreach($clients as $o):?><option value="<?=(int)$o['id']?>" <?=$fClient===(int)$o['id']?'selected':''?>><?=e($o['name'])?></option><?php endforeach;?></select></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">Process</label><select name="process_id" class="form-select form-select-sm" onchange="this.form.submit()"><option value="">All processes</option><?php foreach($procs as $o):?><option value="<?=(int)$o['id']?>" <?=$fProcess===(int)$o['id']?'selected':''?>><?=e($fClient ? $o['name'] : $o['cn'].' · '.$o['name'])?></option><?php endforeach;?></select></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">Facility</label><select name="facility_id" class="form-select form-select-sm"><option value="">All facilities</option><?php foreach($facils as $o):?><option value="<?=(int)$o['id']?>" <?=$fFacility===(int)$o['id']?'selected':''?>><?=e($o['name'])?></option><?php endforeach;?></select></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">Patient / MRN</label><input name="q" value="<?= e($fQ) ?>" class="form-control form-control-sm" placeholder="name or MRN"></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">Biller</label><select name="biller_id" class="form-select form-select-sm"><option value="">All billers</option><?php foreach($billers as $b):?><option value="<?=(int)$b['id']?>" <?=$fBiller===(int)$b['id']?'selected':''?>><?=e($b['name'])?></option><?php endforeach;?></select></div>
            <?php if ($supOpts): ?>
            <div class="col-md-3 col-lg-2"><label class="form-label">Supervisor <span class="text-secondary small">(team)</span></label><select name="sup_id" class="form-select form-select-sm"><option value="">All my supervisors</option><?php foreach($supOpts as $o):?><option value="<?=(int)$o['id']?>" <?=$fSup===(int)$o['id']?'selected':''?>><?=e($o['name'])?></option><?php endforeach;?></select></div>
            <?php endif; ?>
            <div class="col-md-3 col-lg-2"><label class="form-label">DOS from</label><input type="date" name="dos_from" value="<?= e($fDosFrom) ?>" class="form-control form-control-sm"></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">DOS to</label><input type="date" name="dos_to" value="<?= e($fDosTo) ?>" class="form-control form-control-sm"></div>
            <div class="col-md-3 col-lg-2"><label class="form-label">Show <span class="text-secondary small">(Current tab)</span></label><select name="who" class="form-select form-select-sm">
                <?php foreach(['all'=>'All allocated','to_work'=>'Not yet worked','worked'=>'Worked','hold_unbilled'=>'Hold-Deduct / Unbilled','unallocated'=>'Unallocated (no biller)'] as $k=>$v):?><option value="<?=$k?>" <?=$fWho===$k?'selected':''?>><?=e($v)?></option><?php endforeach;?>
            </select></div>
            <div class="col-md-3 col-lg-2 d-flex gap-2"><button class="btn btn-sm btn-primary"><i class="ti ti-filter me-1"></i>Apply</button><a href="<?= e(url('/admin/billing-allocation.php?tab='.$tab)) ?>" class="btn btn-sm btn-ghost-secondary">Clear</a></div>
        </div>
    </div></form>

    <!-- =============================================== tab nav -->
    <ul class="nav nav-tabs mb-0">
        <?php foreach ($tabs as $k => [$lbl, $icon]): ?>
        <li class="nav-item"><a class="nav-link <?= $tab===$k?'active':'' ?>" href="<?= e(url($selfUrl(['tab'=>$k]))) ?>"><i class="ti <?= $icon ?> me-1"></i><?= e($lbl) ?></a></li>
        <?php endforeach; ?>
    </ul>
    <div class="card border-top-0 rounded-top-0 mb-3"><div class="card-body">

    <?php /* =============================================== TAB: Upload backdated inventory (SysAdmin) */ ?>
    <?php if ($tab === 'upload' && $isSys): $bkRes = (($_GET['showupload'] ?? '') === '1') ? ($_SESSION['bkdated_inv_result']['rows'] ?? []) : []; ?>
        <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
            <h3 class="card-title mb-0"><i class="ti ti-upload me-1"></i>Upload backdated inventory <span class="badge bg-yellow-lt ms-1">SysAdmin</span></h3>
            <div class="ms-auto d-flex gap-2">
                <a href="<?= e(url($selfUrl(['inv_template'=>'xlsx']))) ?>" class="btn btn-sm btn-outline-secondary"><i class="ti ti-download me-1"></i>Template</a>
                <a href="<?= e(url($selfUrl(['export'=>'unalloc']))) ?>" class="btn btn-sm btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i><?= e($exportLabel['upload']) ?></a>
            </div>
        </div>
        <p class="text-secondary small mb-2">Upload an <code>.xlsx</code> where <strong>each row is one CPT charge line</strong>: Client · Process · Facility · Provider · Patient name · MRN · DOB · DOS · <strong>CPT · Units · Modifier(s) · ICD(s)</strong> · optional Received date. Rows for the <strong>same patient + DOS</strong> are grouped into ONE chart carrying all its CPT lines. Modifiers/ICDs may be comma-separated. <strong>MRN is optional</strong> (blank → identify by name + DOB). Charts are created <strong>released to internal billing</strong> and join the pool.</p>
        <form method="POST" enctype="multipart/form-data" action="<?= e(url($selfUrl(['tab'=>'upload']))) ?>" class="d-flex gap-2 align-items-center flex-wrap mb-2">
            <?= csrf_field() ?><input type="hidden" name="action" value="upload_inventory">
            <input type="file" name="file" accept=".xlsx" class="form-control form-control-sm" style="max-width:340px" required>
            <button class="btn btn-sm btn-primary"><i class="ti ti-upload me-1"></i>Upload &amp; add to pool</button>
        </form>
        <?php if ($bkRes): $eN = 0; foreach ($bkRes as $rr) if (($rr['status'] ?? '') === 'error') $eN++; ?>
        <div class="mb-2 small"><span class="badge bg-green-lt me-1"><?= count($bkRes) - $eN ?> created</span><span class="badge bg-<?= $eN?'red':'secondary' ?>-lt"><?= $eN ?> error<?= $eN===1?'':'s' ?></span></div>
        <div class="table-responsive mb-3" style="max-height:280px;overflow:auto">
            <table class="table table-sm table-vcenter card-table">
                <thead><tr><th class="w-1">Row</th><th>Client » Process » Facility</th><th>Provider</th><th>Patient</th><th class="w-1">CPT</th><th class="w-1">Status</th><th>Error</th></tr></thead>
                <tbody>
                <?php foreach ($bkRes as $rr): $ok = ($rr['status'] ?? '') !== 'error'; ?>
                    <tr class="<?= $ok ? '' : 'table-danger' ?>">
                        <td class="text-secondary"><?= (int)($rr['row'] ?? 0) ?></td>
                        <td class="small"><?= e(trim(($rr['client'] ?? '') . ' » ' . ($rr['process'] ?? '') . ' » ' . ($rr['facility'] ?? ''), ' »')) ?></td>
                        <td class="small"><?= e($rr['provider'] ?? '') ?></td>
                        <td class="small"><?= e($rr['patient'] ?? '') ?></td>
                        <td class="small fw-bold"><?= e($rr['cpt'] ?? '') ?></td>
                        <td><span class="badge bg-<?= $ok ? 'green' : 'red' ?>-lt"><?= e($rr['status'] ?? '') ?></span></td>
                        <td class="small text-danger"><?= e($rr['error'] ?? '') ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table>
        </div>
        <?php endif; ?>

        <hr>
        <form method="POST" action="<?= e(url($selfUrl(['tab'=>'upload']))) ?>" onkeydown="if(event.key==='Enter'&&event.target.tagName!=='BUTTON'){event.preventDefault();}">
            <?= csrf_field() ?><input type="hidden" name="action" value="delete_unalloc">
            <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
                <h3 class="card-title mb-0"><i class="ti ti-trash me-1 text-danger"></i>Unallocated inventory <span class="badge bg-azure-lt ms-1"><?= number_format($unallocTotal) ?></span></h3>
                <span class="text-secondary small">Released to billing, not yet allocated to any biller. SysAdmin can delete bad uploads here.</span>
                <div class="ms-auto"><button class="btn btn-sm btn-danger" onclick="return confirm('Permanently delete the selected unallocated chart(s) and all their coded lines? This cannot be undone.');"><i class="ti ti-trash me-1"></i>Delete selected</button></div>
            </div>
            <div class="table-responsive"><table class="table table-vcenter card-table table-sm">
                <thead><tr><th class="w-1"><input type="checkbox" class="form-check-input m-0" onclick="document.querySelectorAll('.ucb').forEach(c=>c.checked=this.checked)"></th>
                    <th class="w-1">Chart #</th><th>Patient</th><th>MRN</th><th>Client</th><th>Facility</th><th>Provider</th><th>DOS</th><th class="text-center">Lines</th><th>Received</th></tr></thead>
                <tbody>
                <?php if (!$unallocRows): ?><tr><td colspan="10" class="text-center text-secondary py-4">No unallocated inventory in this scope.</td></tr>
                <?php else: foreach ($unallocRows as $r): ?>
                    <tr>
                        <td><input type="checkbox" class="form-check-input m-0 ucb" name="ids[]" value="<?= (int)$r['id'] ?>"></td>
                        <td class="text-secondary small">#<?= (int)$r['id'] ?></td>
                        <td class="small"><strong><?= e($r['patient_name']) ?></strong></td>
                        <td class="small"><?= e($r['patient_mrn'] ?: '—') ?></td>
                        <td class="small text-secondary"><?= e($r['cn']) ?></td>
                        <td class="small"><?= e($r['fn']) ?></td>
                        <td class="small"><?= e($r['prn'] ?: '—') ?></td>
                        <td class="small text-nowrap"><?= e(fmt_date($r['dos'])) ?></td>
                        <td class="text-center"><span class="badge bg-blue-lt"><?= (int)$r['line_n'] ?></span></td>
                        <td class="small text-nowrap text-secondary"><?= e($r['released_to_billing_at'] ? date('Y-m-d', strtotime($r['released_to_billing_at'])) : '—') ?></td>
                    </tr>
                <?php endforeach; endif; ?>
                </tbody>
            </table></div>
            <?php if (count($unallocRows) >= 1000): ?><div class="text-secondary small mt-1">Showing first 1000 — narrow with the filters. (Export includes up to 20,000.)</div><?php endif; ?>
        </form>

    <?php /* =============================================== TAB: Run allocation */ ?>
    <?php elseif ($tab === 'run'): ?>
        <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
            <h3 class="card-title mb-0"><i class="ti ti-player-play me-1"></i>Run allocation</h3>
            <span class="badge bg-azure-lt"><?= number_format($sentToBill) ?> claim(s) sent to bill in this scope</span>
            <span class="badge bg-orange-lt"><?= number_format($unalloc) ?> waiting to allocate</span>
            <div class="ms-auto"><a href="<?= e(url($selfUrl(['export'=>'received']))) ?>" class="btn btn-sm btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i><?= e($exportLabel['run']) ?></a></div>
        </div>

        <?php /* req 3: facility-wise claim summary for the applied scope (allocated + unallocated) */ ?>
        <div class="card mb-3"><div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-building-hospital me-1"></i>Claims by facility <span class="text-secondary ms-1">— in the applied filters</span></h3></div>
            <div class="table-responsive" style="max-height:320px"><table class="table table-vcenter card-table table-sm mb-0">
                <thead><tr><th>Facility</th><th class="text-end">Total claims</th><th class="text-end">Allocated</th><th class="text-end">Waiting to allocate</th></tr></thead>
                <tbody>
                <?php if (!$facilitySummary): ?><tr><td colspan="4" class="text-center text-secondary py-3">No released claims in this scope.</td></tr>
                <?php else: $ftC=0;$ftA=0;$ftU=0; foreach ($facilitySummary as $s): $ftC+=(int)$s['claims'];$ftA+=(int)$s['allocated'];$ftU+=(int)$s['unallocated']; ?>
                    <tr><td class="small"><?= e($s['fn'] ?: '—') ?></td>
                        <td class="text-end fw-medium"><?= number_format((int)$s['claims']) ?></td>
                        <td class="text-end text-secondary"><?= number_format((int)$s['allocated']) ?></td>
                        <td class="text-end"><?php $u=(int)$s['unallocated']; ?><?= $u ? '<span class="badge bg-orange-lt">'.number_format($u).'</span>' : '<span class="text-secondary">0</span>' ?></td></tr>
                <?php endforeach; ?>
                    <tr class="fw-bold"><td class="text-end">Total</td><td class="text-end"><?= number_format($ftC) ?></td><td class="text-end"><?= number_format($ftA) ?></td><td class="text-end"><?= number_format($ftU) ?></td></tr>
                <?php endif; ?>
                </tbody>
            </table></div>
        </div>

        <?php /* req 5: each pickable biller's existing allocations vs pending, so you allocate to who has room */ ?>
        <?php if ($billers): ?>
        <div class="card mb-3"><div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-users me-1"></i>Biller workload <span class="text-secondary ms-1">— existing allocations vs pending (all processes)</span></h3></div>
            <div class="table-responsive" style="max-height:320px"><table class="table table-vcenter card-table table-sm mb-0" id="billerLoadTbl">
                <thead><tr>
                    <th role="button" onclick="blSort(this,0,'s')">Biller</th>
                    <th class="text-end" role="button" onclick="blSort(this,1,'n')" title="Total charts currently allocated">Allocated</th>
                    <th class="text-end" role="button" onclick="blSort(this,2,'n')" title="Allocated but not yet worked">Pending</th>
                    <th class="text-end" role="button" onclick="blSort(this,3,'n')" title="Worked">Worked</th>
                    <th class="text-end" role="button" onclick="blSort(this,4,'n')" title="Hold-Deductible">Hold</th>
                    <th class="text-end" role="button" onclick="blSort(this,5,'n')" title="Unbilled">Unbilled</th>
                </tr></thead>
                <tbody id="billerLoadBody">
                <?php foreach ($billers as $b): $ld = $billerLoad[(int)$b['id']] ?? ['total'=>0,'pending'=>0,'worked'=>0,'hold'=>0,'unbilled'=>0]; ?>
                    <tr>
                        <td class="small"><?= e($b['name']) ?></td>
                        <td class="text-end" data-v="<?= (int)$ld['total'] ?>"><?= number_format((int)$ld['total']) ?></td>
                        <td class="text-end fw-medium" data-v="<?= (int)$ld['pending'] ?>"><?php $pn=(int)$ld['pending']; ?><?= $pn ? '<span class="badge bg-blue-lt">'.number_format($pn).'</span>' : '<span class="text-secondary">0</span>' ?></td>
                        <td class="text-end text-secondary" data-v="<?= (int)$ld['worked'] ?>"><?= number_format((int)$ld['worked']) ?></td>
                        <td class="text-end text-secondary" data-v="<?= (int)$ld['hold'] ?>"><?= number_format((int)$ld['hold']) ?></td>
                        <td class="text-end text-secondary" data-v="<?= (int)$ld['unbilled'] ?>"><?= number_format((int)$ld['unbilled']) ?></td>
                    </tr>
                <?php endforeach; ?>
                </tbody>
            </table></div>
        </div>
        <?php endif; ?>

        <form method="POST" action="<?= e(url($selfUrl(['tab'=>'run']))) ?>">
            <?= csrf_field() ?>
            <input type="hidden" name="action" value="run_allocation">
            <input type="hidden" name="client_id" value="<?= $fClient ?>"><input type="hidden" name="process_id" value="<?= $fProcess ?>"><input type="hidden" name="facility_id" value="<?= $fFacility ?>">
            <label class="form-label">Allocate to (round-robin by <strong>patient</strong> — all of a patient's charts go to one biller)</label>
            <?php if (!$billers): ?><div class="text-secondary">No billers found (need <code>billing.work</code>).</div>
            <?php else: ?><div class="mb-2"><?php $billerPicker(); ?></div><?php endif; ?>
            <button class="btn btn-primary" <?= ($billers && $unalloc)?'':'disabled' ?>><i class="ti ti-player-play me-1"></i>Allocate <?= number_format($unalloc) ?> claim(s)</button>
            <span class="text-secondary small ms-2">Assigns the <?= number_format($unalloc) ?> waiting claim(s) in the selected scope evenly across the checked billers. (<?= number_format($sentToBill) ?> total sent to bill; the rest are already allocated.)</span>
        </form>

    <?php /* =============================================== TAB: Current allocation */ ?>
    <?php elseif ($tab === 'current'): ?>
        <?php $sumTotal = array_sum(array_map(fn($s) => (int) $s['claims'], $allocSummary)); $isUnalloc = ($fWho === 'unallocated'); ?>
        <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
            <h3 class="card-title mb-0"><i class="ti ti-users me-1"></i><?= $isUnalloc ? 'Unallocated claims — summary' : 'Current allocation — summary' ?></h3>
            <span class="text-secondary small"><?= number_format($sumTotal) ?> claim(s) · <?= number_format(count($allocRows)) ?> chart(s)</span>
            <div class="ms-auto d-flex gap-2">
                <button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#allocDetail"><i class="ti ti-list-check me-1"></i>Chart detail<?= $isUnalloc ? '' : ' &amp; actions' ?></button>
                <a href="<?= e(url($selfUrl(['export'=>'current']))) ?>" class="btn btn-sm btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i><?= e($exportLabel['current']) ?></a>
            </div>
        </div>
        <?php if ($isUnalloc): ?>
            <div class="text-secondary small mb-2"><strong>Unallocated</strong> released claims (no biller yet) in this scope, rolled up by <strong>Process » Facility » Provider</strong>. Allocate them on the <a href="<?= e(url($selfUrl(['tab'=>'run']))) ?>">Run allocation</a> tab.</div>
        <?php else: ?>
            <div class="text-secondary small mb-2">Claims allocated to each biller, rolled up by <strong>Process » Facility » Provider</strong> with the <strong>most recent DOS</strong> in each group. Open <em>Chart detail &amp; actions</em> to reassign / reallocate / raise RFI on individual charts.</div>
        <?php endif; ?>
        <div class="table-responsive mb-3"><table class="table table-vcenter card-table table-sm">
            <thead><tr><th>Biller</th><th>Process</th><th>Facility</th><th>Provider</th><th class="text-end">Claims</th><th>Most recent DOS</th></tr></thead>
            <tbody>
            <?php if (!$allocSummary): ?><tr><td colspan="6" class="text-center text-secondary py-4">Nothing allocated in this scope yet.</td></tr>
            <?php else:
                $grouped = [];
                foreach ($allocSummary as $s) { $bn = $s['biller'] ?: '(unassigned)'; $grouped[$bn]['rows'][] = $s; $grouped[$bn]['total'] = ($grouped[$bn]['total'] ?? 0) + (int) $s['claims']; }
                foreach ($grouped as $bn => $grp): ?>
                <tr class="table-active"><td><strong><?= e($bn) ?></strong></td><td colspan="3" class="text-secondary small"><?= count($grp['rows']) ?> group(s)</td><td class="text-end"><strong><?= number_format($grp['total']) ?></strong></td><td class="text-secondary small">total claims</td></tr>
                <?php foreach ($grp['rows'] as $s): ?>
                <tr>
                    <td></td>
                    <td class="small"><?= e($s['pn'] ?: '—') ?></td>
                    <td class="small"><?= e($s['fn'] ?: '—') ?></td>
                    <td class="small"><?= e($s['provider'] ?: '—') ?></td>
                    <td class="text-end"><?= number_format((int) $s['claims']) ?></td>
                    <td class="small text-nowrap"><?= e(fmt_date($s['recent_dos'])) ?></td>
                </tr>
                <?php endforeach; ?>
                <?php endforeach; endif; ?>
            </tbody>
        </table></div>

        <div class="collapse" id="allocDetail">
        <form method="POST" action="<?= e(url($selfUrl(['tab'=>'current']))) ?>" id="allocform"
              onkeydown="if(event.key==='Enter'&&event.target.tagName!=='TEXTAREA'&&event.target.tagName!=='BUTTON'){event.preventDefault();}">
            <?= csrf_field() ?>
            <?php if ($isUnalloc): ?>
            <div class="text-secondary small py-2"><i class="ti ti-info-circle me-1"></i>Unallocated charts have no owner — reassign / reallocate / RFI don't apply here. Use the <a href="<?= e(url($selfUrl(['tab'=>'run']))) ?>">Run allocation</a> tab to assign them.</div>
            <?php else: ?>
            <div class="border-bottom py-2 mb-2 d-flex flex-wrap align-items-center gap-2">
                <select name="to_user" class="form-select form-select-sm w-auto"><option value="">→ back to origin (unallocate)</option><?php foreach($billers as $b):?><option value="<?=(int)$b['id']?>"><?=e($b['name'])?></option><?php endforeach;?></select>
                <button class="btn btn-sm btn-primary" name="action" value="reassign"><i class="ti ti-arrows-exchange me-1"></i>Reassign selected</button>
                <span class="vr"></span>
                <button class="btn btn-sm btn-outline-primary" type="button" data-bs-toggle="collapse" data-bs-target="#reallocbox"><i class="ti ti-refresh me-1"></i>Reallocate Hold/Unbilled…</button>
                <button class="btn btn-sm btn-outline-warning" type="button" data-bs-toggle="collapse" data-bs-target="#rfibox"><i class="ti ti-arrow-back-up me-1"></i>Raise RFI to coding…</button>
            </div>
            <div class="collapse mb-2" id="reallocbox"><div class="p-2 bg-body-tertiary rounded">
                <div class="text-secondary small mb-1">Redistribute the selected <strong>Hold-Deductible / Unbilled</strong> charts across these billers (by patient; others ignored):</div>
                <?php $billerPicker(); ?>
                <button class="btn btn-sm btn-primary mt-2" name="action" value="reallocate"><i class="ti ti-refresh me-1"></i>Reallocate selected</button>
            </div></div>
            <div class="collapse mb-2" id="rfibox"><div class="p-2 bg-body-tertiary rounded">
                <div class="text-secondary small mb-1">Raise the selected accounts to the <strong>coding team as RFI</strong> — pulled out of billing back into coding.</div>
                <div class="d-flex flex-wrap gap-2 align-items-center">
                    <input name="rfi_reason" class="form-control form-control-sm w-auto" placeholder="Reason for RFI (optional)" maxlength="255" style="min-width:280px">
                    <button class="btn btn-sm btn-warning" name="action" value="raise_rfi" onclick="return confirm('Raise the selected account(s) to coding as RFI? They leave the billing queue.');"><i class="ti ti-arrow-back-up me-1"></i>Raise RFI</button>
                </div>
            </div></div>
            <?php endif; /* !$isUnalloc action bars */ ?>
            <div class="table-responsive"><table class="table table-vcenter card-table table-sm">
                <thead><tr><?php if (!$isUnalloc): ?><th class="w-1"><input type="checkbox" class="form-check-input m-0" onclick="document.querySelectorAll('.bcb').forEach(c=>c.checked=this.checked)"></th><?php endif; ?>
                    <th>Patient</th><th>Client</th><th>Facility</th><th>DOS</th><th>Biller</th><th>Inv. status</th><th>Billing status</th><th>Carrier</th><th>Reason</th><th>Last worked</th></tr></thead>
                <tbody>
                <?php if (!$allocRows): ?><tr><td colspan="<?= $isUnalloc ? 10 : 11 ?>" class="text-center text-secondary py-4"><?= $isUnalloc ? 'No unallocated claims in this scope.' : 'Nothing allocated in this scope yet.' ?></td></tr>
                <?php else: foreach ($allocRows as $r): $bs=(string)($r['billing_status']??''); $sk=(string)($r['inventory_status']??''); ?>
                    <tr>
                        <?php if (!$isUnalloc): ?><td><input type="checkbox" class="form-check-input m-0 bcb" name="ids[]" value="<?= (int)$r['id'] ?>"></td><?php endif; ?>
                        <td class="small"><strong><?= e($r['patient_name']) ?></strong></td>
                        <td class="small text-secondary"><?= e($r['cn']) ?></td>
                        <td class="small"><?= e($r['fn']) ?></td>
                        <td class="small text-nowrap"><?= e(fmt_date($r['dos'])) ?></td>
                        <td class="small"><?= e($r['biller'] ?? '—') ?></td>
                        <td><span class="badge bg-<?= $sk==='worked'?'green':($sk==='allocated'?'blue':'secondary') ?>-lt"><?= e($sk!==''?str_replace('_',' ',$sk):'—') ?></span></td>
                        <td><?= $bs!=='' ? '<span class="badge bg-'.($bsBadge[$bs]??'secondary').'-lt">'.e(BILLING_WORK_STATUSES[$bs]??$bs).'</span>' : '<span class="text-secondary">—</span>' ?></td>
                        <td class="small"><?= e($r['carrier'] ?: '—') ?></td>
                        <td class="small text-secondary"><?= e($r['secondary_reason'] ?: '') ?></td>
                        <td class="small text-nowrap text-secondary"><?= e(fmt_date($r['last_worked_date'])) ?></td>
                    </tr>
                <?php endforeach; endif; ?>
                </tbody>
            </table></div>
            <?php if (count($allocRows) >= 2000): ?><div class="text-secondary small mt-1">Showing first 2000 — narrow with the filters. (Export includes up to 20,000.)</div><?php endif; ?>
        </form>
        </div>

    <?php /* =============================================== TAB: Pull back untouched */ ?>
    <?php elseif ($tab === 'pullback'): ?>
        <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
            <h3 class="card-title mb-0"><i class="ti ti-arrow-back-up me-1"></i>Pull back untouched</h3>
            <span class="text-secondary small"><?= number_format(count($untouchedRows)) ?> untouched chart(s) in scope</span>
            <div class="ms-auto"><a href="<?= e(url($selfUrl(['export'=>'untouched']))) ?>" class="btn btn-sm btn-outline-success"><i class="ti ti-file-spreadsheet me-1"></i><?= e($exportLabel['pullback']) ?></a></div>
        </div>
        <p class="text-secondary small">Resets charts that were <strong>allocated but never worked</strong> back to origin (unallocated), so they can be reshuffled. Worked charts are never touched. Uses the filters above (Biller included).</p>

        <?php /* req 6: by-biller summary of untouched charts — each with its own pull-back + drill-down into the detail below */ ?>
        <?php $ub = $untouchedBreakdown; ?>
        <div class="card mb-3"><div class="card-header py-2"><h3 class="card-title small"><i class="ti ti-users me-1"></i>Untouched by biller <span class="text-secondary ms-1">— not-yet-worked charts across your scope</span></h3>
            <div class="card-actions text-secondary small"><?= number_format((int)$ub['total']) ?> chart(s)</div></div>
            <div class="table-responsive" style="max-height:340px"><table class="table table-vcenter card-table table-sm mb-0">
                <thead><tr><th>Biller</th><th class="text-end">Untouched charts</th><th class="w-1"></th><th class="w-1"></th></tr></thead>
                <tbody>
                <?php if (!$ub['by_user']): ?><tr><td colspan="4" class="text-center text-secondary py-3">No untouched charts in your scope. 🎉</td></tr>
                <?php else: foreach ($ub['by_user'] as $r): ?>
                    <tr <?= $fBiller===(int)$r['uid']?'class="table-active"':'' ?>>
                        <td class="small"><?= e($r['uname'] ?? '—') ?></td>
                        <td class="text-end fw-medium"><?= number_format((int)$r['charts']) ?></td>
                        <td><a href="<?= e(url($selfUrl(['biller_id'=>(int)$r['uid']]))) ?>" class="btn btn-sm btn-outline-secondary" title="Show this biller's untouched charts in the detail table below"><i class="ti ti-list-search me-1"></i>Drill down</a></td>
                        <td><form method="POST" action="<?= e(url($selfUrl(['tab'=>'pullback']))) ?>" onsubmit="return confirm('Pull back <?= (int)$r['charts'] ?> untouched chart(s) from <?= e(addslashes((string)($r['uname']??''))) ?>?');">
                            <?= csrf_field() ?><input type="hidden" name="action" value="pullback"><input type="hidden" name="only_user" value="<?= (int)$r['uid'] ?>">
                            <button class="btn btn-sm btn-outline-warning" title="Pull back this biller's untouched charts"><i class="ti ti-arrow-back-up"></i></button></form></td>
                    </tr>
                <?php endforeach; endif; ?>
                </tbody>
            </table></div>
        </div>

        <form method="POST" action="<?= e(url($selfUrl(['tab'=>'pullback']))) ?>" class="row g-2 align-items-end mb-3">
            <?= csrf_field() ?><input type="hidden" name="action" value="pullback">
            <div class="col-auto"><label class="form-label">Limit to biller (optional)</label>
                <select name="only_user" class="form-select form-select-sm"><option value="">All billers in scope</option><?php foreach($billers as $b):?><option value="<?=(int)$b['id']?>" <?=$fBiller===(int)$b['id']?'selected':''?>><?=e($b['name'])?></option><?php endforeach;?></select></div>
            <div class="col-auto"><button class="btn btn-outline-warning" onclick="return confirm('Pull all untouched charts back to origin?');"><i class="ti ti-arrow-back-up me-1"></i>Pull back untouched</button></div>
        </form>
        <div class="table-responsive"><table class="table table-vcenter card-table table-sm">
            <thead><tr><th class="w-1">Chart #</th><th>Patient</th><th>Client</th><th>Facility</th><th>DOS</th><th>Biller</th><th>Allocated at</th></tr></thead>
            <tbody>
            <?php if (!$untouchedRows): ?><tr><td colspan="7" class="text-center text-secondary py-4">No untouched (allocated-but-never-worked) charts in this scope.</td></tr>
            <?php else: foreach ($untouchedRows as $r): ?>
                <tr>
                    <td class="text-secondary small">#<?= (int)$r['inventory_id'] ?></td>
                    <td class="small"><strong><?= e($r['patient_name']) ?></strong></td>
                    <td class="small text-secondary"><?= e($r['cn']) ?></td>
                    <td class="small"><?= e($r['fn']) ?></td>
                    <td class="small text-nowrap"><?= e(fmt_date($r['dos'])) ?></td>
                    <td class="small"><?= e($r['biller'] ?? '—') ?></td>
                    <td class="small text-nowrap text-secondary"><?= e($r['allocated_at'] ? date('Y-m-d H:i', strtotime($r['allocated_at'])) : '—') ?></td>
                </tr>
            <?php endforeach; endif; ?>
            </tbody>
        </table></div>
        <?php if (count($untouchedRows) >= 2000): ?><div class="text-secondary small mt-1">Showing first 2000 — narrow with the filters.</div><?php endif; ?>
    <?php endif; ?>

    </div></div>

</div></div>
<script>
// Sort the Run-tab "Biller workload" table by a column (numeric via data-v, or text).
function blSort(th,col,type){
    var tb=document.getElementById('billerLoadBody'); if(!tb) return;
    var rows=Array.prototype.slice.call(tb.querySelectorAll('tr'));
    var dir=th.dataset.dir==='asc'?'desc':'asc'; th.dataset.dir=dir;
    rows.sort(function(a,b){ var x,y;
        if(type==='n'){ x=parseFloat(a.children[col].dataset.v||0); y=parseFloat(b.children[col].dataset.v||0); }
        else { x=(a.children[col].textContent||'').trim().toLowerCase(); y=(b.children[col].textContent||'').trim().toLowerCase(); }
        if(x<y) return dir==='asc'?-1:1; if(x>y) return dir==='asc'?1:-1; return 0; });
    rows.forEach(function(r){ tb.appendChild(r); });
}
// Wire every searchable biller picker: filter by name, select-all-shown, clear, live count.
document.querySelectorAll('.biller-picker').forEach(function (bp) {
    var search = bp.querySelector('.bp-search'),
        count  = bp.querySelector('.bp-count'),
        items  = Array.prototype.slice.call(bp.querySelectorAll('.bp-item'));
    function visibleCbs() { return items.filter(function (it) { return it.style.display !== 'none'; })
        .map(function (it) { return it.querySelector('.bp-cb'); }); }
    function refresh() {
        var sel = bp.querySelectorAll('.bp-cb:checked').length, total = items.length;
        if (count) count.textContent = sel + ' of ' + total + ' selected';
    }
    if (search) {
        search.addEventListener('input', function () {
            var q = this.value.trim().toLowerCase();
            items.forEach(function (it) { it.style.display = (!q || it.dataset.name.indexOf(q) !== -1) ? '' : 'none'; });
        });
        search.addEventListener('keydown', function (e) { if (e.key === 'Enter') e.preventDefault(); });
    }
    bp.querySelector('.bp-all').addEventListener('click', function () { visibleCbs().forEach(function (c) { c.checked = true; }); refresh(); });
    bp.querySelector('.bp-none').addEventListener('click', function () { items.forEach(function (it) { it.querySelector('.bp-cb').checked = false; }); refresh(); });
    bp.addEventListener('change', function (e) { if (e.target.classList.contains('bp-cb')) refresh(); });
    refresh();
});
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
