<?php
/**
 * Permissions — global FEATURE-permission controller (SysAdmin only).  ac-perms-v2
 *
 *  TWO VIEWS on one page (tab=…):
 *   - 'user'    By user: see & edit ONE user's whole capability set (Grant / Deny / Inherit per code).
 *   - 'common'  Bulk: tick MANY users × MANY permission codes, apply one action.
 *
 *  Stores only DEVIATIONS from each role's defaults (user_permissions table), so changing a role later
 *  still flows through. SysAdmins are the '*' superuser and are never editable here.
 *
 *  DATA-SCOPE (which client » process » facility » provider a user may work, in any vertical) is NOT managed
 *  here — it's mapped on the User alignment page (admin/user-alignment.php), the one unified flow for every
 *  vertical. This page is feature permissions only. The per-user "Data access → manage…" link points there.
 *  (2026-06-18 access-control split; 2026-06-21 the data-access link unified onto user-alignment.php.)
 */
require_once __DIR__ . '/../includes/config.php';
$user = require_login('internal');
$pdo  = db();
$uid  = (int) $user['id'];

$isSysadmin = user_has_role($uid, 'sysadmin');
if (!$isSysadmin) { http_response_code(403); exit('Only SysAdmin can edit feature permissions. Coder data access is managed on the Coder access page.'); }
$canPerms = $canUser = true;                                 // sysadmin-only page

$team = coding_team_scope($pdo, $uid);                       // null for sysadmin → no team narrowing
$teamInUser = $team === null ? '' : (' AND u.id IN (' . implode(',', $team ?: [0]) . ')');

$tabsAvail = ['user', 'common'];
$tab = $_GET['tab'] ?? $_POST['tab'] ?? 'user';
if (!in_array($tab, $tabsAvail, true)) $tab = 'user';

$redir = function (array $over = []) use ($tab) {
    $q = array_merge(['tab' => $tab], $over);
    redirect('/admin/access-control.php?' . http_build_query(array_filter($q, fn($v) => $v !== '' && $v !== 0)));
};

/* ───────────────────────────── POST: bulk feature permissions ───────────────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'bulk_perms') {
    csrf_check_or_die();
    if (!$canPerms) { flash('error','Only SysAdmin can edit feature permissions.'); $redir(); }
    $userIds = array_values(array_filter(array_map('intval', (array) ($_POST['user_ids'] ?? []))));
    $effect  = in_array($_POST['effect'] ?? '', ['grant','deny','inherit'], true) ? $_POST['effect'] : '';
    $codesIn = (array) ($_POST['codes'] ?? []);
    $allCodes = $pdo->query('SELECT code FROM permissions')->fetchAll(PDO::FETCH_COLUMN);
    $codes = array_values(array_intersect($codesIn, $allCodes));
    if (!$userIds)  { flash('error','Pick at least one user.'); $redir(); }
    if (!$effect)   { flash('error','Choose Grant, Deny or Inherit.'); $redir(); }
    if (!$codes)    { flash('error','Pick at least one permission.'); $redir(); }
    // Never touch sysadmins (full access, not overridable).
    $userIds = array_values(array_filter($userIds, fn($id) => !user_has_role($id, 'sysadmin')));
    if (!$userIds) { flash('error','Selected users are all SysAdmins — not overridable.'); $redir(); }

    $del = $pdo->prepare('DELETE FROM user_permissions WHERE user_id=? AND permission_code=?');
    $up  = $pdo->prepare('INSERT INTO user_permissions (user_id,permission_code,effect,granted_by)
                          VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE effect=VALUES(effect), granted_by=VALUES(granted_by)');
    foreach ($userIds as $u) foreach ($codes as $c) {
        if ($effect === 'inherit') { $del->execute([$u,$c]); }
        else                       { $up->execute([$u,$c,$effect,$uid]); }
    }
    audit_log('user.permissions.bulk', 'user', null, null, ['users'=>count($userIds),'codes'=>count($codes),'effect'=>$effect]);
    $word = $effect==='inherit' ? 'reset to role default' : ($effect==='grant'?'granted':'denied');
    flash('success', count($codes)." permission(s) $word for ".count($userIds)." user(s).");
    $redir();
}

/* ─────────────────── POST: save ONE user's whole permission set (By-user tab) ─────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'set_user_perms') {
    csrf_check_or_die();
    if (!$canUser) { flash('error','Only SysAdmin can edit a user\'s permissions.'); $redir(); }
    $tUid = (int) ($_POST['u'] ?? 0);
    if (!$tUid)                              { flash('error','No user selected.'); $redir(); }
    if (user_has_role($tUid, 'sysadmin'))    { flash('error','SysAdmin has full access — not editable.'); $redir(['u'=>$tUid]); }
    $effs = (array) ($_POST['eff'] ?? []);                                  // eff[<permission_code>] = grant|deny|inherit
    $allCodes = array_flip($pdo->query('SELECT code FROM permissions')->fetchAll(PDO::FETCH_COLUMN));
    // Role-default codes for this user (what its roles already allow) — so we only STORE real deviations.
    $rc = $pdo->prepare("SELECT DISTINCT pm.code FROM user_roles ur JOIN role_permissions rp ON rp.role_id=ur.role_id
                         JOIN permissions pm ON pm.id=rp.permission_id WHERE ur.user_id=?");
    $rc->execute([$tUid]); $roleCodes = array_flip($rc->fetchAll(PDO::FETCH_COLUMN));
    $del = $pdo->prepare('DELETE FROM user_permissions WHERE user_id=? AND permission_code=?');
    $up  = $pdo->prepare('INSERT INTO user_permissions (user_id,permission_code,effect,granted_by)
                          VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE effect=VALUES(effect), granted_by=VALUES(granted_by)');
    $pdo->beginTransaction();                                                   // feature perms + report grants commit together
    $changed = 0;
    foreach ($effs as $code => $e) {
        if (!isset($allCodes[$code])) continue;
        $e = in_array($e, ['grant','deny','inherit'], true) ? $e : 'inherit';
        // Normalise: a "grant" that the role already gives, or a "deny" the role already lacks, is just inherit.
        if (($e === 'grant' && isset($roleCodes[$code])) || ($e === 'deny' && !isset($roleCodes[$code]))) $e = 'inherit';
        if ($e === 'inherit') $del->execute([$tUid, $code]);
        else                  $up->execute([$tUid, $code, $e, $uid]);
        $changed++;
    }
    // ── Per-report access grants (ra[<report_id>][view|download|upload|delete|replace]) — EXACT set from the toggles.
    $raIn  = (array) ($_POST['ra'] ?? []);
    $reps  = $pdo->query("SELECT id FROM reports WHERE deleted_at IS NULL")->fetchAll(PDO::FETCH_COLUMN);
    $raDel = $pdo->prepare('DELETE FROM report_access WHERE user_id=? AND report_id=?');
    $raIns = $pdo->prepare('INSERT INTO report_access (report_id,user_id,can_view,can_upload,can_download,can_delete,can_replace,granted_by,granted_at,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,NOW(),NOW(),NOW())');
    $raN = 0;
    foreach ($reps as $rid) {
        $rid = (int) $rid; $f = (array) ($raIn[$rid] ?? []);
        $v = !empty($f['view'])?1:0; $dl = !empty($f['download'])?1:0; $up2 = !empty($f['upload'])?1:0; $de = !empty($f['delete'])?1:0; $rp = !empty($f['replace'])?1:0;
        if ($dl || $up2 || $de || $rp) $v = 1;                                  // any sub-action implies View
        $raDel->execute([$tUid, $rid]);
        if ($v) { $raIns->execute([$rid, $tUid, $v, $up2, $dl, $de, $rp, $uid]); $raN++; }
    }
    $pdo->commit();
    audit_log('user.permissions.set_user', 'user', $tUid, null, ['codes'=>$changed, 'reports'=>$raN]);
    flash('success', 'Access updated for this user.');
    $redir(['u'=>$tUid]);
}

/* ───────────────────────────────── GET: shared filters + user list ───────────────────────────────── */
$fRole = (string) ($_GET['role'] ?? '');
$fSup  = (int) ($_GET['supervisor_id'] ?? 0);

// supervisor + role filter options (scoped to the team set)
$supOpts = $pdo->query("SELECT DISTINCT sup.id, sup.name FROM users u JOIN users sup ON sup.id=u.reports_to
    WHERE u.user_type='internal' AND u.status='active' $teamInUser ORDER BY sup.name")->fetchAll();
$roleOpts = $pdo->query("SELECT DISTINCT r.code, r.name FROM roles r JOIN user_roles ur ON ur.role_id=r.id
    JOIN users u ON u.id=ur.user_id WHERE u.user_type='internal' AND u.status='active' $teamInUser ORDER BY r.name")->fetchAll();

// the filtered user list (shared by both tabs)
$uWhere = "u.user_type='internal' AND u.status='active'" . $teamInUser;
$uParams = [];
if ($fRole !== '') { $uWhere .= " AND u.id IN (SELECT ur2.user_id FROM user_roles ur2 JOIN roles r2 ON r2.id=ur2.role_id WHERE r2.code=?)"; $uParams[] = $fRole; }
if ($fSup)         { $uWhere .= " AND u.reports_to=?"; $uParams[] = $fSup; }
$uSql = "SELECT u.id, u.name, u.email, u.vertical, u.reports_to,
                COALESCE(GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', '),'') roles,
                MAX(r.code='sysadmin') is_sysadmin, sup.name supervisor
         FROM users u
         LEFT JOIN user_roles ur ON ur.user_id=u.id LEFT JOIN roles r ON r.id=ur.role_id
         LEFT JOIN users sup ON sup.id=u.reports_to
         WHERE $uWhere GROUP BY u.id ORDER BY u.name";
$st = $pdo->prepare($uSql); $st->execute($uParams); $users = $st->fetchAll();
$userIds = array_map(fn($u)=>(int)$u['id'], $users);

// Permission catalog — grouped by VERTICAL / area. The group falls back to the permission CODE PREFIX
// (coding.work → 'coding') when group_name was never set, so EVERY permission appears under the right area
// (old-build prod / newly-added perms). Verticals (Coding / AR / Billing) are listed first, then shared areas.
// REPORT ACCESS is its own standalone category (the reports platform = AR's collection dashboards). It sits right
// after the verticals: select a user → vertical perms first, then Report Access. The per-report GRANTS live on the
// By-user tab (a report_access matrix); this bulk tab shows the report-platform feature perms.
$GROUP_LABELS = ['coding'=>'Coding — vertical', 'ar'=>'AR (Accounts Receivable) — vertical', 'billing'=>'Billing — vertical',
    'reports'=>'Report Access — Reports platform (builder · browser · dashboards)', 'access'=>'Report Access — Grant management',
    'master'=>'Master Data — shared across verticals', 'ops'=>'Settings & Operations', 'users'=>'User management'];
$GROUP_ICONS  = ['coding'=>'stethoscope', 'ar'=>'cash', 'billing'=>'file-invoice', 'reports'=>'report-analytics', 'access'=>'key',
    'master'=>'database', 'ops'=>'settings', 'users'=>'users'];
$GROUP_ORDER  = ['coding', 'ar', 'billing', 'reports', 'access', 'master', 'ops', 'users'];
$VERTICAL_GROUPS = ['coding'=>1, 'ar'=>1, 'billing'=>1];
$permsByGroup = [];
foreach ($pdo->query("SELECT code, label, COALESCE(NULLIF(group_name,''), SUBSTRING_INDEX(code,'.',1)) g FROM permissions ORDER BY label") as $p)
    $permsByGroup[$p['g']][] = $p;
uksort($permsByGroup, function ($a, $b) use ($GROUP_ORDER) {
    $ia = array_search($a, $GROUP_ORDER, true); $ib = array_search($b, $GROUP_ORDER, true);
    return (($ia === false ? 99 : $ia) <=> ($ib === false ? 99 : $ib)) ?: strcmp($a, $b);
});

/* By-user tab: load the chosen user + role-default codes + per-user overrides. */
$selUserId = (int) ($_GET['u'] ?? 0);
$selUser = null; $uRoleCodes = []; $uOverrides = []; $uScopeUnrestricted = false; $uAlignments = [];
if ($tab === 'user' && $selUserId) {
    $q = $pdo->prepare("SELECT u.id,u.name,u.email,u.vertical,
            COALESCE(GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', '),'') roles,
            MAX(r.code='sysadmin') is_sysadmin, sup.name supervisor
        FROM users u LEFT JOIN user_roles ur ON ur.user_id=u.id LEFT JOIN roles r ON r.id=ur.role_id
        LEFT JOIN users sup ON sup.id=u.reports_to
        WHERE u.id=? AND u.user_type='internal' GROUP BY u.id");
    $q->execute([$selUserId]); $selUser = $q->fetch() ?: null;
    if ($selUser) {
        $rc = $pdo->prepare("SELECT DISTINCT pm.code FROM user_roles ur JOIN role_permissions rp ON rp.role_id=ur.role_id
                             JOIN permissions pm ON pm.id=rp.permission_id WHERE ur.user_id=?");
        $rc->execute([$selUserId]); $uRoleCodes = array_flip($rc->fetchAll(PDO::FETCH_COLUMN));
        $ov = $pdo->prepare("SELECT permission_code, effect FROM user_permissions WHERE user_id=?");
        $ov->execute([$selUserId]); foreach ($ov->fetchAll() as $r) $uOverrides[$r['permission_code']] = $r['effect'];
        $uScopeUnrestricted = coding_scope_unrestricted($selUserId);
        // Report Access — every active report (organised by its vertical » client » process) + THIS user's per-report grants.
        $raReports = $pdo->query("SELECT r.id, r.name, p.name process, c.name client,
                COALESCE(v.name,'— no vertical —') vname, COALESCE(v.code,'') vcode
            FROM reports r JOIN processes p ON p.id=r.process_id JOIN clients c ON c.id=p.client_id
            LEFT JOIN verticals v ON v.id=c.vertical_id
            WHERE r.deleted_at IS NULL AND r.status='active' ORDER BY v.name, c.name, p.name, r.name")->fetchAll();
        $raGrants = [];
        $rg = $pdo->prepare("SELECT report_id, can_view, can_upload, can_download, can_delete, can_replace FROM report_access WHERE user_id=?");
        $rg->execute([$selUserId]); foreach ($rg->fetchAll() as $r) $raGrants[(int) $r['report_id']] = $r;
        // Current data alignment (client » process » facility » provider) — the actual editor lives on user-alignment.php.
        $aq = $pdo->prepare("SELECT v.name vname, c.name cname, p.name pname, f.name fname, pr.name prname
            FROM coding_user_scope s
            LEFT JOIN verticals v ON v.id=s.vertical_id
            JOIN clients c ON c.id=s.client_id
            LEFT JOIN processes p ON p.id=s.process_id
            LEFT JOIN coding_facilities f ON f.id=s.facility_id
            LEFT JOIN coding_providers pr ON pr.id=s.provider_id
            WHERE s.user_id=? ORDER BY v.name, c.name, p.name, f.name, pr.name");
        $aq->execute([$selUserId]); $uAlignments = $aq->fetchAll();
    }
}

$pageTitle = 'Permissions';
require __DIR__ . '/../includes/header.php';

$qBase = fn($over=[]) => url('/admin/access-control.php?' . http_build_query(array_merge(['tab'=>$tab,'role'=>$fRole,'supervisor_id'=>$fSup], $over)));
?>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/css/tom-select.bootstrap5.min.css">
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
<style>
  .ac-userpick{ max-height:300px; overflow:auto; border:1px solid var(--tblr-border-color); border-radius:8px; }
  .ac-userpick table{ margin:0; font-size:.82rem; }
  .ac-userpick thead th{ position:sticky; top:0; background:var(--tblr-bg-surface); z-index:1; }
  .ac-perm-grid{ columns: 2; column-gap: 1.5rem; }
  @media (max-width: 900px){ .ac-perm-grid{ columns:1; } }
  .ac-perm-grid .form-check{ break-inside:avoid; }
  .ts-wrapper{ min-width:0; }
</style>

<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-lock-cog me-2 text-primary"></i>Permissions</h2>
        <div class="text-secondary">Global feature permissions (what each user can <em>do</em>) — for one user or many at once.
            Client » process » facility » provider <strong>data access</strong> is mapped on the <a href="<?= e(url('/admin/user-alignment.php')) ?>">User alignment</a> page (one flow for every vertical).</div>
    </div>
    <div class="col-auto d-flex gap-2">
        <a href="<?= e(url('/admin/roles.php')) ?>" class="btn btn-outline-secondary" title="Edit the default features each role grants (role_permissions)"><i class="ti ti-shield-cog me-1"></i>Roles &amp; features</a>
        <a href="<?= e(url('/admin/users.php')) ?>" class="btn btn-outline-secondary"><i class="ti ti-user-cog me-1"></i>Users &amp; roles</a>
    </div>
</div></div></div>

<div class="page-body"><div class="container-xl">
    <ul class="nav nav-tabs mb-3">
        <li class="nav-item"><a class="nav-link <?= $tab==='user'?'active':'' ?>" href="<?= e($qBase(['tab'=>'user'])) ?>"><i class="ti ti-user-shield me-1"></i>By user <span class="badge bg-green-lt ms-1">one user</span></a></li>
        <li class="nav-item"><a class="nav-link <?= $tab==='common'?'active':'' ?>" href="<?= e($qBase(['tab'=>'common'])) ?>"><i class="ti ti-key me-1"></i>Bulk permissions <span class="badge bg-purple-lt ms-1">many users</span></a></li>
    </ul>

    <!-- shared filter bar -->
    <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="tab" value="<?= e($tab) ?>">
        <div class="col-6 col-md-4"><label class="form-label mb-1">Role</label>
            <select name="role" class="form-select form-select-sm" onchange="this.form.submit()">
                <option value="">All roles</option>
                <?php foreach ($roleOpts as $r): ?><option value="<?= e($r['code']) ?>" <?= $fRole===$r['code']?'selected':'' ?>><?= e($r['name']) ?></option><?php endforeach; ?>
            </select></div>
        <div class="col-6 col-md-4"><label class="form-label mb-1">Supervisor (team)</label>
            <select name="supervisor_id" class="form-select form-select-sm" onchange="this.form.submit()">
                <option value="0">All supervisors</option>
                <?php foreach ($supOpts as $s): ?><option value="<?= (int)$s['id'] ?>" <?= $fSup===(int)$s['id']?'selected':'' ?>><?= e($s['name']) ?></option><?php endforeach; ?>
            </select></div>
        <div class="col-6 col-md-4"><label class="form-label mb-1">&nbsp;</label><div>
            <button class="btn btn-sm btn-primary"><i class="ti ti-filter me-1"></i>Apply</button>
            <a href="<?= e(url('/admin/access-control.php?tab='.$tab)) ?>" class="btn btn-sm btn-ghost-secondary">Clear</a>
            <span class="text-secondary small ms-2"><?= count($users) ?> user(s)</span>
        </div></div>
    </form></div></div>

<?php if ($tab === 'user'): /* ════════════════ BY USER — one user's complete capability set ════════════════ */ ?>
    <div class="card mb-3"><div class="card-body py-2">
        <label class="form-label mb-1"><i class="ti ti-user-search me-1"></i>Select a user to see &amp; edit everything they can do</label>
        <select class="form-select" id="acUserPick" data-base="<?= e($qBase(['u'=>'__ID__'])) ?>">
            <option value="">— choose a user —</option>
            <?php if ($selUser && !in_array($selUserId, $userIds, true)): ?>
                <option value="<?= (int)$selUser['id'] ?>" selected><?= e($selUser['name']) ?> — <?= e($selUser['email']) ?><?= $selUser['roles']?'  ('.e($selUser['roles']).')':'' ?></option>
            <?php endif; ?>
            <?php foreach ($users as $u): ?>
                <option value="<?= (int)$u['id'] ?>" <?= $selUserId===(int)$u['id']?'selected':'' ?>><?= e($u['name']) ?> — <?= e($u['email']) ?><?= $u['roles']?'  ('.e($u['roles']).')':'' ?></option>
            <?php endforeach; ?>
        </select>
        <div class="form-hint">Narrow the list with the <strong>Role</strong> / <strong>Supervisor</strong> filters above · <?= count($users) ?> user(s).</div>
    </div></div>

    <?php if ($selUser): $isSa = (int)$selUser['is_sysadmin'];
        $nGrant=0; $nDeny=0; $nRole=0;
        foreach ($permsByGroup as $list) foreach ($list as $p) { $c=$p['code']; $ov=$uOverrides[$c]??null;
            if($ov==='deny')$nDeny++; elseif($ov==='grant')$nGrant++; elseif(isset($uRoleCodes[$c]))$nRole++;
        }
        // Master Data pages (Facilities/Providers/Hierarchy) are now gated by the DISCRETE 'master.data.manage' permission.
        $mdmOv = $uOverrides['master.data.manage'] ?? null;
        $uCanMasterData = (bool)$isSa || $mdmOv==='grant' || ($mdmOv!=='deny' && isset($uRoleCodes['master.data.manage']));

        // The user's own vertical(s) (codes among coding/ar/billing) — shown FIRST; the rest go behind "Additional".
        $userVGroups = [];
        foreach (app_user_vertical_ids((int)$selUser['id']) as $vid) { $vv = app_verticals()[$vid] ?? null; if ($vv) $userVGroups[app_vertical_group($vv['code'])] = 1; }

        // ───────── PAGE-CENTRIC catalog: every permission shown under the actual PAGE it controls, grouped by vertical.
        // 'gate' = perms that grant page ACCESS (mirrors admin_route_permissions(); __sysadmin__ = SysAdmin-only,
        // master.data.manage = the shared master-data gate). 'also' = extra capabilities exercised ON that page.
        // Master-data pages are DUPLICATED under every vertical (per the per-vertical-specificity request).
        $permLabels = []; foreach ($permsByGroup as $list) foreach ($list as $p) $permLabels[$p['code']] = $p['label'];
        // Page catalog — the shared single source of truth (includes/auth.php medmecs_page_catalog); also used by
        // admin/roles.php so a new page appears on BOTH and is never skipped. Master pages are duplicated per vertical.
        $__cat = medmecs_page_catalog();
        $PAGES = $__cat['verticals']; $MASTER_PAGES = $__cat['master']; $REPORT_PAGES = $__cat['reports']; $PLATFORM_GROUPS = $__cat['platform'];

        // ── effective-state helpers + renderers ──
        $placedCodes = [];
        $hasPerm = function (string $code) use ($uOverrides, $uRoleCodes, $isSa): bool {
            if ($isSa) return true;
            $ov = $uOverrides[$code] ?? null;
            if ($ov === 'grant') return true; if ($ov === 'deny') return false;
            return isset($uRoleCodes[$code]);
        };
        $RID = 0;
        // one permission's Inherit/Grant/Deny control (display-only radios; sync into the hidden eff[] via JS).
        $renderPerm = function (string $code) use (&$RID, &$placedCodes, $permLabels, $uRoleCodes, $uOverrides) {
            if ($code === '__sysadmin__' || $code === '__masterdata__') return;
            $placedCodes[$code] = 1;
            $label = $permLabels[$code] ?? $code;
            $roleHas = isset($uRoleCodes[$code]); $ov = $uOverrides[$code] ?? null; $cur = $ov ?? 'inherit';
            if ($ov==='deny'){$bl='Blocked';$bc='red';} elseif($ov==='grant'){$bl='Granted';$bc='green';} elseif($roleHas){$bl='Via role';$bc='blue';} else {$bl='No access';$bc='secondary';}
            $nm = 'disp_' . (++$RID);
            ?>
            <tr>
                <td class="ps-4"><?= e($label) ?> <code class="text-secondary" style="font-size:.66rem"><?= e($code) ?></code>
                    <span class="d-block text-secondary" style="font-size:.66rem">role default: <?= $roleHas?'<span class="text-green">allowed</span>':'not allowed' ?></span></td>
                <td><span class="badge bg-<?= $bc ?>-lt"><?= $bl ?></span></td>
                <td class="text-center"><input type="radio" class="form-check-input permradio" aria-label="Inherit — <?= e($label) ?>" data-code="<?= e($code) ?>" name="<?= $nm ?>" value="inherit" <?= $cur==='inherit'?'checked':'' ?>></td>
                <td class="text-center"><input type="radio" class="form-check-input permradio" aria-label="Grant — <?= e($label) ?>" data-code="<?= e($code) ?>" name="<?= $nm ?>" value="grant" <?= $cur==='grant'?'checked':'' ?>></td>
                <td class="text-center"><input type="radio" class="form-check-input permradio" aria-label="Deny — <?= e($label) ?>" data-code="<?= e($code) ?>" name="<?= $nm ?>" value="deny" <?= $cur==='deny'?'checked':'' ?>></td>
            </tr>
            <?php
        };
        // one page: a header row (icon/label/file + access badge) then its permission rows.
        $renderPageRows = function (array $pg) use ($renderPerm, $hasPerm, $isSa) {
            $gate = $pg['gate'] ?? []; $also = $pg['also'] ?? [];
            $sysOnly = in_array('__sysadmin__', $gate, true);
            $gateReal = array_values(array_filter($gate, fn($c) => $c !== '__sysadmin__' && $c !== '__masterdata__'));
            // Each grantable page has its OWN access code (page.<file>) rendered FIRST — grant just that to give ONLY
            // this page to any user, regardless of role. The capability codes below it still work (any-of).
            $pageCode = (!$sysOnly && !empty($pg['file']) && isset(medmecs_grantable_pages()[$pg['file']])) ? page_perm_code($pg['file']) : null;
            $accessCodes = $pageCode ? array_merge([$pageCode], $gateReal) : $gateReal;
            if ($sysOnly)            $access = 'sys';
            elseif (!$accessCodes)   $access = null;                                   // capability-only, no page gate
            else                     $access = ($isSa || (bool) array_filter($accessCodes, $hasPerm));
            $codes = array_values(array_unique(array_merge($pageCode ? [$pageCode] : [], $gateReal, $also)));
            ?>
            <tr class="pageblock" style="background:var(--tblr-bg-surface-secondary,#f1f5f9)" data-gates="<?= e(implode(',', $gateReal)) ?>" data-sysadmin="<?= $sysOnly?1:0 ?>">
                <td colspan="5" class="py-1">
                    <i class="ti ti-<?= e($pg['icon'] ?? 'file') ?> me-1 text-secondary"></i><span class="fw-bold"><?= $pg['label'] /* may contain entities */ ?></span>
                    <?php if (!empty($pg['file'])): ?> <code class="text-secondary" style="font-size:.64rem"><?= e($pg['file']) ?></code><?php endif; ?>
                    <?php if (!empty($pg['note'])): ?> <span class="text-secondary" style="font-size:.7rem">— <?= $pg['note'] ?></span><?php endif; ?>
                    <?php if ($sysOnly): ?><span class="badge bg-purple-lt ms-2">SysAdmin only</span>
                    <?php elseif ($access !== null): ?><span class="access-badge badge ms-2 bg-<?= $access?'green':'secondary' ?>-lt"><?= $access?'Has access':'No access' ?></span><?php endif; ?>
                    <?php if (!empty($pg['shared'])): ?><span class="badge bg-azure-lt ms-1" title="Shared master data — the same permission under every vertical; changing it here changes it everywhere"><i class="ti ti-arrows-left-right" style="font-size:.7rem"></i> shared</span><?php endif; ?>
                </td>
            </tr>
            <?php foreach ($codes as $c) $renderPerm($c); ?>
            <?php
        };
        $VICON = ['coding'=>'stethoscope','ar'=>'cash','billing'=>'file-invoice'];
        // one vertical card: its own pages + the duplicated Master-data block.
        $renderVerticalCard = function (string $vcode, string $vlabel) use ($PAGES, $renderPageRows, $VICON) { ?>
            <div class="card mb-3">
                <div class="card-header py-2 bg-purple-lt d-flex align-items-center">
                    <i class="ti ti-<?= e($VICON[$vcode] ?? 'building') ?> me-2 text-purple"></i>
                    <h3 class="card-title mb-0 text-purple text-uppercase" style="letter-spacing:.04em"><?= e($vlabel) ?> vertical</h3>
                    <span class="badge bg-purple ms-2">VERTICAL</span>
                </div>
                <div class="card-body p-0"><table class="table table-sm table-vcenter mb-0">
                    <thead><tr><th class="ps-2">Page / capability</th><th class="w-1 text-nowrap">Current</th><th class="w-1 text-center" title="Use role default">Inherit</th><th class="w-1 text-center" title="Force-allow">Grant</th><th class="w-1 text-center" title="Block">Deny</th></tr></thead>
                    <tbody>
                        <?php foreach ($PAGES as $pg) if ($pg['sec'] === $vcode) $renderPageRows($pg); ?>
                    </tbody>
                </table></div>
            </div>
        <?php };
        // master-data card — SHARED across all verticals; shown ONCE (previously repeated inside every vertical card).
        $renderMasterData = function () use ($MASTER_PAGES, $renderPageRows) { ?>
            <div class="card mb-3 border-azure">
                <div class="card-header py-2 bg-azure-lt d-flex align-items-center">
                    <i class="ti ti-database me-2 text-azure"></i>
                    <h3 class="card-title mb-0 text-azure text-uppercase" style="letter-spacing:.04em">Master data</h3>
                    <span class="text-secondary ms-2 small">— shared across ALL verticals · one set of pages, an edit here applies everywhere</span>
                </div>
                <div class="card-body p-0"><table class="table table-sm table-vcenter mb-0">
                    <thead><tr><th class="ps-2">Page / capability</th><th class="w-1 text-nowrap">Current</th><th class="w-1 text-center" title="Use role default">Inherit</th><th class="w-1 text-center" title="Force-allow">Grant</th><th class="w-1 text-center" title="Block">Deny</th></tr></thead>
                    <tbody><?php foreach ($MASTER_PAGES as $pg) $renderPageRows($pg); ?></tbody>
                </table></div>
            </div>
        <?php };
        // platform administration card (cross-vertical, shown once).
        $renderPlatform = function () use ($PLATFORM_GROUPS, $renderPageRows) { ?>
            <div class="card mb-3 border-secondary">
                <div class="card-header py-2 d-flex align-items-center">
                    <i class="ti ti-server-cog me-2 text-secondary"></i>
                    <h3 class="card-title mb-0 text-uppercase" style="letter-spacing:.04em">Platform administration</h3>
                    <span class="text-secondary ms-2 small">— applies across ALL verticals</span>
                </div>
                <div class="card-body p-0"><table class="table table-sm table-vcenter mb-0">
                    <thead><tr><th class="ps-2">Page / capability</th><th class="w-1 text-nowrap">Current</th><th class="w-1 text-center">Inherit</th><th class="w-1 text-center">Grant</th><th class="w-1 text-center">Deny</th></tr></thead>
                    <tbody>
                        <?php foreach ($PLATFORM_GROUPS as $grp): ?>
                            <tr class="table-light"><td colspan="5" class="text-uppercase small fw-bold text-secondary py-1"><i class="ti ti-<?= e($grp['icon']) ?> me-1"></i><?= $grp['title'] ?></td></tr>
                            <?php foreach ($grp['pages'] as $pg) $renderPageRows($pg); ?>
                        <?php endforeach; ?>
                    </tbody>
                </table></div>
            </div>
        <?php };
        // REPORT ACCESS card — standalone: report-platform feature perms + the per-report grant matrix for this user.
        $renderReportAccess = function () use ($REPORT_PAGES, $renderPageRows, $raReports, $raGrants) { ?>
            <div class="card mb-3 border-azure">
                <div class="card-header py-2 bg-azure-lt d-flex align-items-center">
                    <i class="ti ti-report-analytics me-2 text-azure"></i>
                    <h3 class="card-title mb-0 text-azure text-uppercase" style="letter-spacing:.04em">Report Access</h3>
                    <span class="text-secondary ms-2 small">— the reports platform &amp; which reports this user may see</span>
                </div>
                <div class="card-body p-0">
                    <div class="text-uppercase small fw-bold text-secondary px-2 pt-2"><i class="ti ti-tool me-1"></i>Platform tools — can they use the report system</div>
                    <table class="table table-sm table-vcenter mb-0">
                        <thead><tr><th class="ps-2">Page / capability</th><th class="w-1 text-nowrap">Current</th><th class="w-1 text-center">Inherit</th><th class="w-1 text-center">Grant</th><th class="w-1 text-center">Deny</th></tr></thead>
                        <tbody><?php foreach ($REPORT_PAGES as $pg) $renderPageRows($pg); ?></tbody>
                    </table>
                    <div class="text-uppercase small fw-bold text-secondary px-2 pt-3 border-top"><i class="ti ti-eye-check me-1"></i>Per-report access — which specific reports they can see &amp; use</div>
                    <?php if (!$raReports): ?>
                        <div class="px-3 py-3 text-secondary small">No reports defined yet. Reports created in <strong>Report builder</strong> appear here automatically.</div>
                    <?php else: ?>
                        <table class="table table-sm table-vcenter mb-0">
                            <thead><tr><th class="ps-2">Report</th>
                                <th class="w-1 text-center" title="See the report">View</th><th class="w-1 text-center" title="Download data">Download</th>
                                <th class="w-1 text-center" title="Upload data">Upload</th><th class="w-1 text-center" title="Delete data">Delete</th>
                                <th class="w-1 text-center" title="Replace all data">Replace</th></tr></thead>
                            <tbody>
                            <?php $cv=null; $cc=null; foreach ($raReports as $r): $rid=(int)$r['id']; $g=$raGrants[$rid] ?? [];
                                if ($r['vname']!==$cv){ $cv=$r['vname']; $cc=null; ?><tr class="table-active"><td colspan="6" class="text-uppercase small fw-bold text-azure py-1"><i class="ti ti-folder me-1"></i><?= e($r['vname']) ?> reports</td></tr><?php }
                                if ($r['client']!==$cc){ $cc=$r['client']; ?><tr class="table-light"><td colspan="6" class="small fw-bold text-secondary ps-3"><?= e($r['client']) ?></td></tr><?php } ?>
                                <tr>
                                    <td class="ps-4"><?= e($r['name']) ?> <span class="text-secondary" style="font-size:.72rem">· <?= e($r['process']) ?></span></td>
                                    <?php foreach (['view'=>'can_view','download'=>'can_download','upload'=>'can_upload','delete'=>'can_delete','replace'=>'can_replace'] as $k=>$col): ?>
                                        <td class="text-center"><input type="checkbox" class="form-check-input ra-chk<?= $k==='view'?' ra-view':'' ?>" data-rid="<?= $rid ?>" name="ra[<?= $rid ?>][<?= $k ?>]" value="1" <?= !empty($g[$col])?'checked':'' ?>></td>
                                    <?php endforeach; ?>
                                </tr>
                            <?php endforeach; ?>
                            </tbody>
                        </table>
                        <div class="text-secondary px-3 py-2" style="font-size:.72rem"><i class="ti ti-info-circle me-1"></i><strong>View</strong> is the gate — ticking any other action turns View on; SysAdmins always see every report.</div>
                    <?php endif; ?>
                </div>
            </div>
        <?php }; ?>
        <div class="card mb-3"><div class="card-body d-flex flex-wrap gap-4 align-items-center">
            <div><div class="h3 mb-0"><?= e($selUser['name']) ?> <?= $isSa?'<span class="badge bg-secondary-lt ms-1">SysAdmin</span>':'' ?></div><div class="text-secondary small"><?= e($selUser['email']) ?></div></div>
            <div><div class="text-secondary small">Roles</div><?= e($selUser['roles'] ?: 'no role') ?><?= $selUser['supervisor']?'<div class="text-secondary small">↳ '.e($selUser['supervisor']).'</div>':'' ?></div>
            <div><div class="text-secondary small">Capabilities</div>
                <span class="badge bg-blue-lt"><?= $nRole ?> via role</span> <span class="badge bg-green-lt"><?= $nGrant ?> granted</span> <span class="badge bg-red-lt"><?= $nDeny ?> blocked</span></div>
            <div><div class="text-secondary small">Data access</div>
                <?php if($uScopeUnrestricted): ?><span class="badge bg-green-lt">All (supervisor/manager)</span>
                <?php else: ?><span class="badge bg-secondary-lt">per client » process » facility</span><?php endif; ?>
                <a href="<?= e(url('/admin/user-alignment.php?user_id='.(int)$selUser['id'])) ?>" class="small ms-1">manage…</a></div>
            <div><div class="text-secondary small" title="Controlled by the 'master.data.manage' permission below">Master Data pages <i class="ti ti-info-circle"></i></div>
                <?= $uCanMasterData ? '<span class="badge bg-green-lt">Yes</span>' : '<span class="badge bg-secondary-lt">No</span>' ?>
                <span class="text-secondary d-block" style="font-size:.72rem;max-width:230px">Facilities · Providers · Hierarchy — toggle <code>master.data.manage</code> in <strong>Master Data</strong> below.</span></div>
        </div></div>

        <?php // ───────── DATA ALIGNMENT — the client»process»facility»provider this user works (managed on User alignment). ?>
        <div class="card mb-3 border-azure">
            <div class="card-header py-2 bg-azure-lt d-flex align-items-center flex-wrap gap-2">
                <i class="ti ti-user-shield me-2 text-azure"></i>
                <h3 class="card-title mb-0 text-azure">Data alignment <span class="text-secondary small fw-normal">— client » process » facility » provider this user works</span></h3>
                <a href="<?= e(url('/admin/user-alignment.php?user_id='.(int)$selUser['id'])) ?>" class="btn btn-sm btn-azure ms-auto"><i class="ti ti-adjustments-alt me-1"></i>Manage alignment</a>
            </div>
            <div class="card-body p-0">
                <?php if ($uScopeUnrestricted): ?>
                    <div class="px-3 pt-2 small text-secondary"><span class="badge bg-green-lt me-1">All clients</span>Supervisor / manager — sees their whole team's data across their vertical(s). Any specific alignments below further focus their reports.</div>
                <?php endif; ?>
                <?php if (!$uAlignments): ?>
                    <div class="px-3 py-3 text-secondary small"><i class="ti ti-info-circle me-1"></i>
                        <?= $uScopeUnrestricted ? 'No specific alignment — works every client in their vertical(s).' : 'No alignment yet — this user cannot see any data until aligned. Click <strong>Manage alignment</strong>.' ?></div>
                <?php else: ?>
                    <table class="table table-sm table-vcenter mb-0">
                        <thead><tr><th class="ps-2">Vertical</th><th>Client</th><th>Process</th><th>Facility</th><th>Provider</th></tr></thead>
                        <tbody>
                        <?php foreach ($uAlignments as $a): ?>
                            <tr><td class="ps-2"><span class="badge bg-purple-lt"><?= e($a['vname'] ?? '—') ?></span></td>
                                <td><strong><?= e($a['cname']) ?></strong></td>
                                <td class="small"><?= e($a['pname'] ?? '— all —') ?></td>
                                <td class="small"><?= e($a['fname'] ?? '— all —') ?></td>
                                <td class="small"><?= e($a['prname'] ?? '— all —') ?></td></tr>
                        <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php endif; ?>
            </div>
        </div>

        <?php if ($isSa): ?>
            <div class="alert alert-secondary"><i class="ti ti-shield-lock me-1"></i><strong>SysAdmin</strong> — has full access to everything; individual permissions are not editable.</div>
        <?php else: ?>
        <form method="POST" action="<?= e(url('/admin/access-control.php')) ?>">
            <?= csrf_field() ?>
            <input type="hidden" name="tab" value="user"><input type="hidden" name="action" value="set_user_perms"><input type="hidden" name="u" value="<?= (int)$selUser['id'] ?>">
            <?php
                // hidden source-of-truth inputs (ALL codes). Display radios sync into these; Save reads eff[<code>].
                foreach ($permLabels as $code => $lbl) { $ov = $uOverrides[$code] ?? 'inherit'; $rh = isset($uRoleCodes[$code]) ? 1 : 0;
                    echo '<input type="hidden" class="permhidden" name="eff['.e($code).']" data-code="'.e($code).'" data-role="'.$rh.'" value="'.e($ov).'">'; }
                $allV   = ['coding'=>'Coding', 'ar'=>'AR', 'billing'=>'Billing'];
                $userV  = array_values(array_filter(array_keys($allV), fn($v) => isset($userVGroups[$v])));
                $otherV = array_values(array_filter(array_keys($allV), fn($v) => !isset($userVGroups[$v])));
            ?>
            <div class="d-flex flex-wrap align-items-center gap-2 mb-2">
                <div class="text-secondary small me-auto">Every row is a <strong>page</strong> and the permission(s) that unlock it. <span class="badge bg-blue-lt">Inherit</span> = role default · <span class="badge bg-green-lt">Grant</span> = force-allow · <span class="badge bg-red-lt">Deny</span> = block. Shared permissions are kept in sync wherever they repeat.</div>
                <button class="btn btn-primary"><i class="ti ti-device-floppy me-1"></i>Save access for <?= e($selUser['name']) ?></button>
            </div>

            <?php if ($userV): ?>
                <div class="text-uppercase small fw-bold text-purple mb-2"><i class="ti ti-user-check me-1"></i><?= e($selUser['name']) ?>'s vertical<?= count($userV) > 1 ? 's' : '' ?></div>
                <?php foreach ($userV as $v) $renderVerticalCard($v, $allV[$v]); ?>

                <?php // Report Access comes right after the user's own vertical permissions. ?>
                <?php $renderReportAccess(); ?>

                <?php // Master data — shared across every vertical; shown ONCE here (no longer repeated per vertical). ?>
                <?php $renderMasterData(); ?>

                <?php // Platform administration (Users & roles, Logs, Settings) is ALWAYS visible so a SysAdmin can
                      // grant high-value capabilities — e.g. "Manage internal users" (create staff) — without hunting
                      // under a collapse. Only the OTHER verticals stay behind the "Additional permissions" button. ?>
                <?php $renderPlatform(); ?>

                <button type="button" class="btn btn-outline-secondary btn-sm mb-3" id="ac-more-btn"
                    onclick="document.getElementById('ac-more').style.display='';this.style.display='none';">
                    <i class="ti ti-plus me-1"></i>Additional permissions
                    <span class="text-secondary fw-normal ms-1" style="font-size:.72rem">(other verticals)</span>
                </button>
                <div id="ac-more" style="display:none">
                    <?php foreach ($otherV as $v) $renderVerticalCard($v, $allV[$v]); ?>
                </div>
            <?php else: ?>
                <div class="alert alert-secondary py-2 px-3 small"><i class="ti ti-info-circle me-1"></i><?= e($selUser['name']) ?> has no vertical-bearing role yet — every vertical &amp; platform area is shown. Align them on <a href="<?= e(url('/admin/user-alignment.php?user_id='.(int)$selUser['id'])) ?>">User alignment</a> to focus this view.</div>
                <?php foreach (array_keys($allV) as $v) $renderVerticalCard($v, $allV[$v]); ?>
                <?php $renderReportAccess(); ?>
                <?php $renderMasterData(); ?>
                <?php $renderPlatform(); ?>
            <?php endif; ?>

            <?php // completeness guard — surface ANY permission not yet mapped to a page (future-proofing). ?>
            <?php $leftover = array_values(array_diff(array_keys($permLabels), array_keys($placedCodes)));
                if ($leftover): ?>
                <div class="card mb-3 border-warning"><div class="card-header py-2"><h3 class="card-title mb-0 text-warning"><i class="ti ti-alert-triangle me-1"></i>Other capabilities (not yet mapped to a page)</h3></div>
                    <div class="card-body p-0"><table class="table table-sm table-vcenter mb-0">
                        <thead><tr><th class="ps-2">Capability</th><th class="w-1">Current</th><th class="w-1 text-center">Inherit</th><th class="w-1 text-center">Grant</th><th class="w-1 text-center">Deny</th></tr></thead>
                        <tbody><?php foreach ($leftover as $c) $renderPerm($c); ?></tbody>
                    </table></div></div>
            <?php endif; ?>

            <div class="text-end"><button class="btn btn-primary"><i class="ti ti-device-floppy me-1"></i>Save access for <?= e($selUser['name']) ?></button></div>
        </form>
        <?php endif; ?>
    <?php else: ?>
        <div class="card"><div class="card-body text-center text-secondary py-5"><i class="ti ti-user-search" style="font-size:2.2rem"></i><div class="mt-2">Pick a user above to see every capability they have and turn any of them on or off.</div></div></div>
    <?php endif; ?>

<?php else: /* ════════════════ COMMON: BULK PERMISSIONS ════════════════ */ ?>
    <div class="alert alert-info py-2"><i class="ti ti-info-circle me-1"></i>These are <strong>global</strong> capabilities — Master Data &amp; Settings are shared across every vertical. Changes store <strong>deviations</strong> from each role's defaults; SysAdmins are never affected.</div>
    <form method="POST" action="<?= e(url('/admin/access-control.php')) ?>" id="permForm">
        <?= csrf_field() ?>
        <input type="hidden" name="tab" value="common">
        <input type="hidden" name="action" value="bulk_perms">
        <div class="row g-3">
            <div class="col-lg-5">
                <div class="card h-100"><div class="card-header py-2"><h3 class="card-title mb-0"><span class="badge bg-primary me-1">1</span>Users <span class="text-secondary fw-normal small">— tick one or many</span></h3>
                    <div class="ms-auto"><a href="#" id="acSelAll" class="small">Select all</a> · <a href="#" id="acSelNone" class="small">none</a></div></div>
                <div class="ac-userpick"><table class="table table-vcenter table-hover">
                    <thead><tr><th class="w-1"></th><th>User</th><th>Role · supervisor</th></tr></thead>
                    <tbody>
                    <?php foreach ($users as $u): $sid=(int)$u['id']; ?>
                        <tr>
                            <td><input class="form-check-input m-0 acUser" type="checkbox" name="user_ids[]" value="<?= $sid ?>" <?= (int)$u['is_sysadmin']?'disabled':'' ?>></td>
                            <td><div class="fw-medium"><?= e($u['name']) ?> <?= (int)$u['is_sysadmin']?'<span class="badge bg-secondary-lt ms-1">sysadmin</span>':'' ?></div><div class="text-secondary small"><?= e($u['email']) ?></div></td>
                            <td class="small text-secondary"><?= e($u['roles'] ?: 'no role') ?><?= $u['supervisor']?'<br>↳ '.e($u['supervisor']):'' ?></td>
                        </tr>
                    <?php endforeach; ?>
                    <?php if(!$users): ?><tr><td colspan="3" class="text-center text-secondary py-4">No users match these filters.</td></tr><?php endif; ?>
                    </tbody></table></div>
                </div>
            </div>
            <div class="col-lg-7"><div class="card h-100"><div class="card-body">
                <h3 class="card-title"><span class="badge bg-primary me-1">2</span>Permissions to change</h3>
                <?php foreach ($permsByGroup as $g => $list): ?>
                <div class="mb-2">
                    <div class="text-uppercase small fw-bold text-secondary mb-1"><i class="ti ti-<?= e($GROUP_ICONS[$g]??'point') ?> me-1"></i><?= e($GROUP_LABELS[$g] ?? $g) ?>
                        <a href="#" class="ms-1 small fw-normal acGroupAll" data-g="<?= e($g) ?>">all</a></div>
                    <div class="ac-perm-grid">
                        <?php foreach ($list as $p): ?>
                        <label class="form-check"><input type="checkbox" class="form-check-input acPerm acPerm-<?= e($g) ?>" name="codes[]" value="<?= e($p['code']) ?>">
                            <span class="form-check-label"><?= e($p['label']) ?> <code class="text-secondary" style="font-size:.7rem"><?= e($p['code']) ?></code></span></label>
                        <?php endforeach; ?>
                    </div>
                </div>
                <?php endforeach; ?>
                <hr class="my-2">
                <div class="d-flex align-items-center flex-wrap gap-3">
                    <div><label class="form-label mb-1 small">Action to apply</label>
                        <select name="effect" class="form-select form-select-sm" style="width:auto" required>
                            <option value="">— choose —</option>
                            <option value="grant">Grant (force-allow)</option>
                            <option value="deny">Deny (force-block)</option>
                            <option value="inherit">Inherit (clear override → role default)</option>
                        </select></div>
                    <span id="acPermPreview" class="text-secondary small"></span>
                    <button type="submit" class="btn btn-primary ms-auto"><i class="ti ti-device-floppy me-1"></i>Apply to selected</button>
                </div>
            </div></div></div>
        </div>
    </form>
<?php endif; ?>
</div></div>

<script>
(function(){
    // user select-all / none
    var all=document.getElementById('acSelAll'), none=document.getElementById('acSelNone');
    function setAll(v){ document.querySelectorAll('.acUser:not(:disabled)').forEach(function(c){ c.checked=v; }); updatePermPreview(); }
    if(all) all.addEventListener('click', function(e){ e.preventDefault(); setAll(true); });
    if(none) none.addEventListener('click', function(e){ e.preventDefault(); setAll(false); });

    // By-user picker: searchable, navigates to the chosen user on change.
    var up=document.getElementById('acUserPick');
    if(up){
        if (typeof TomSelect !== 'undefined') new TomSelect(up, {maxOptions:5000, persist:false});
        up.addEventListener('change', function(){ if(this.value) location.href=this.dataset.base.replace('__ID__', encodeURIComponent(this.value)); });
    }

    // group "all" toggles (permissions)
    document.querySelectorAll('.acGroupAll').forEach(function(a){
        a.addEventListener('click', function(e){ e.preventDefault();
            var boxes=document.querySelectorAll('.acPerm-'+this.dataset.g);
            var anyOff=Array.prototype.some.call(boxes,function(b){return !b.checked;});
            boxes.forEach(function(b){ b.checked=anyOff; }); updatePermPreview();
        });
    });

    // By-user: keep every duplicated permission radio in sync with one hidden eff[] (the value Save reads),
    // and recompute each page's Has-access / No-access badge live as overrides change.
    var hid={}; document.querySelectorAll('input.permhidden').forEach(function(h){ hid[h.dataset.code]=h; });
    var radiosByCode={}; document.querySelectorAll('.permradio').forEach(function(r){ (radiosByCode[r.dataset.code]=radiosByCode[r.dataset.code]||[]).push(r); });
    function permEff(code){ var h=hid[code]; if(!h) return false; if(h.value==='grant') return true; if(h.value==='deny') return false; return h.dataset.role==='1'; }
    function refreshBadges(){ document.querySelectorAll('.pageblock').forEach(function(b){
        if(b.dataset.sysadmin==='1') return;
        var gates=(b.dataset.gates||'').split(',').filter(Boolean); if(!gates.length) return;
        var has=gates.some(permEff); var badge=b.querySelector('.access-badge'); if(!badge) return;
        badge.className='access-badge badge ms-2 '+(has?'bg-green-lt':'bg-secondary-lt'); badge.textContent=has?'Has access':'No access';
    }); }
    document.querySelectorAll('.permradio').forEach(function(r){ r.addEventListener('change', function(){
        var code=r.dataset.code, val=r.value;
        if(hid[code]) hid[code].value=val;
        (radiosByCode[code]||[]).forEach(function(o){ o.checked=(o.value===val); });
        refreshBadges();
    }); });

    // Report Access: View is the gate. Ticking any sub-action turns View on; turning View off clears the row.
    function raRow(rid){ return document.querySelectorAll('.ra-chk[data-rid="'+rid+'"]'); }
    document.querySelectorAll('.ra-chk').forEach(function(c){ c.addEventListener('change', function(){
        var rid=c.dataset.rid, isView=c.classList.contains('ra-view');
        if(isView && !c.checked){ raRow(rid).forEach(function(o){ o.checked=false; }); }      // View off → clear the row
        else if(!isView && c.checked){ raRow(rid).forEach(function(o){ if(o.classList.contains('ra-view')) o.checked=true; }); }  // sub-action on → View on
    }); });

    function nUsers(){ return document.querySelectorAll('.acUser:checked').length; }
    function updatePermPreview(){ var el=document.getElementById('acPermPreview'); if(!el) return;
        var u=nUsers(), p=document.querySelectorAll('.acPerm:checked').length;
        el.textContent=(u&&p)? (p+' permission(s) → '+u+' user(s)') : 'Tick users + permissions, choose an action.'; }
    document.querySelectorAll('.acUser').forEach(function(c){ c.addEventListener('change', updatePermPreview); });
    document.querySelectorAll('.acPerm').forEach(function(c){ c.addEventListener('change', updatePermPreview); });
    updatePermPreview();
})();
</script>
<?php require __DIR__ . '/../includes/footer.php'; ?>
