<?php

declare(strict_types=1);

require DENTAL_ROOT . '/app/bootstrap.php';
require_login();
require_roles(['super_admin','clinic_admin']);
require DENTAL_ROOT . '/app/layout.php';
if (is_file(DENTAL_ROOT . '/app/role_satisfaction.php')) { require_once DENTAL_ROOT . '/app/role_satisfaction.php'; }

$pdo = db();

function permission_matrix_schema_ready(): bool
{
    return schema_table_exists(table('roles')) && schema_table_exists(table('permissions')) && schema_table_exists(table('role_permissions'));
}

function permission_matrix_grants(PDO $pdo): array
{
    if (!permission_matrix_schema_ready()) return [];
    $rows = $pdo->query('SELECT r.code role_code,p.code permission_code,rp.allowed FROM `' . table('role_permissions') . '` rp JOIN `' . table('roles') . '` r ON r.id=rp.role_id JOIN `' . table('permissions') . '` p ON p.id=rp.permission_id WHERE rp.allowed=1')->fetchAll();
    $map = [];
    foreach ($rows as $row) $map[(string)$row['role_code']][(string)$row['permission_code']] = true;
    $map['super_admin']['*'] = true;
    return $map;
}

function permission_matrix_has(array $grants, string $role, array|string $permissions): bool
{
    if (!empty($grants[$role]['*'])) return true;
    foreach ((array)$permissions as $permission) if (!empty($grants[$role][$permission])) return true;
    return false;
}

$roles = [
    'super_admin' => 'Superuser',
    'clinic_admin' => 'Administrator',
    'receptionist' => 'Receptionist',
    'staff' => 'General Staff',
    'dentist' => 'Dentist',
    'dental_assistant' => 'Dental Assistant / In-house',
    'cashier' => 'Cashier',
    'inventory_officer' => 'Inventory Officer',
    'patient' => 'Patient Portal',
];
$modules = [
    ['Patients','patients.view','Open the patient-care module; granular permissions below control the fields exposed.'],
    ['Register/Edit patient','patients.manage','Add or update basic patient information.'],
    ['Patient identity','patients.identity.view','View patient number, name, date of birth, sex, and photograph.'],
    ['Patient contact details','patients.contact.view','View telephone, email, address, and emergency-contact details.'],
    ['Dental safety-alert flag','patients.safety_alert.view','See that a dental treatment safety alert exists without exposing detailed health information.'],
    ['Medical-history intake','patients.medical.intake','Record a patient-reported update for later dentist verification.'],
    ['Health information for dental care','patients.medical.view','View allergies, medicines, conditions, alerts, and history versions relevant to dental care.'],
    ['Verify dental-safety history','patients.medical.verify','Verify health information relevant to dental care and activate structured dental safety alerts.'],
    ['Patient financial record','patients.financial.view','View invoices, balances, payments, adjustments, and insurance details.'],
    ['Patient portal access','patients.portal.manage','Invite, link, review, or revoke patient and guardian portal access.'],
    ['Appointments','appointments.manage','Create, confirm, reschedule, and manage clinic appointment flow.'],
    ['Queue/chair flow','queue.manage','Call, seat, chair-assign, and move patient through front-desk flow.'],
    ['Clinical records','clinical.view','View and work with clinical encounters, charts, plans, and prescriptions.'],
    ['Billing/POS','billing.manage','Create invoices, post payments, print receipts, and manage cashier transactions.'],
    ['Insurance/HMO','insurance.manage','Manage insurance providers, claims, and claim status.'],
    ['Inventory','inventory.manage','Manage stock, lots, movements, and inventory evidence.'],
    ['Sterilization','sterilization.manage','Manage equipment, cycles, instruments, and release records.'],
    ['Reports','reports.view','Open operational reports and summaries.'],
    ['Records printing','records.print','Print patient records, receipts, reports, and controlled documents.'],
    ['Communications','communications.manage','Manage patient communication records and outbound messages.'],
    ['Staff/dentists','staff.manage','Manage staff accounts, dentists, and clinical profiles.'],
    ['Clinic configuration','configuration.manage','Manage services, chairs, hours, branding, templates, and clinic setup.'],
    ['SMTP/email/SMS','smtp.manage','Manage email, templates, SMS readiness, queues, and test messages.'],
    ['Uploads','uploads.manage','Manage protected forms, policies, and uploaded clinic files.'],
    ['Controlled emergency access','security.break_glass.manage','Start or end a scoped, MFA re-authenticated, time-limited emergency-access session.'],
    ['Emergency access review','security.break_glass.review','Independently review or revoke an assigned emergency-access session.'],
    ['System/owner controls','system.manage','Manage updates, backups, QA, demo data, package building, and owner tools.'],
];
$grants = permission_matrix_grants($pdo);
$roleAccessMatrix = function_exists('role_access_matrix') ? role_access_matrix() : [];
$navigationAudit = function_exists('role_navigation_audit_snapshot') ? role_navigation_audit_snapshot() : ['routes_total'=>0,'requirements_total'=>0,'workspace_routes_total'=>0,'issues'=>[],'critical_count'=>0,'warning_count'=>0,'notice_count'=>0];
$staticLimits = [
    ['Cashier clinical separation','cashier','clinical.view',false,'Cashier should not add/edit clinical chart, diagnosis, prescription, or treatment findings.'],
    ['Receptionist payment separation','receptionist','billing.manage',false,'Receptionist should not post cashier payments unless explicitly authorized by the clinic.'],
    ['Dental Assistant supplier separation','dental_assistant','inventory.manage',false,'Dental Assistant must not manage suppliers, stock, or inventory catalogue records while the Dental Assistant role is active.'],
    ['Inventory patient privacy','inventory_officer','patients.identity.view',false,'Inventory users should not browse patient identity or clinical history.'],
    ['Receptionist clinical privacy','receptionist','patients.medical.view',false,'Receptionists may record patient-reported dental-safety intake but must not open detailed health information.'],
    ['Cashier contact privacy','cashier','patients.contact.view',false,'Cashiers may identify the payor and view authorized balances without seeing contact, address, or medical details.'],
    ['Dentist contact minimization','dentist','patients.contact.view',false,'Dentists receive clinical and safety information; routine contact details remain with coordination staff.'],
    ['Dentist cashier separation','dentist','billing.manage',false,'Dentist should hand off completed charges but not close cashier collections.'],
    ['Patient portal staff privacy','patient','staff.manage',false,'Patient accounts should never access staff/dentist management.'],
    ['Owner-only system controls','clinic_admin','system.manage',true,'Clinic admin may have setup access, but licensing/package secrets remain Superuser/owner-controlled by feature lock.'],
];
$issues = [];
foreach ($staticLimits as [$label,$role,$permission,$expected,$note]) {
    $actual = permission_matrix_has($grants,$role,$permission);
    if ($actual !== $expected && !($role === 'clinic_admin' && $permission === 'system.manage')) $issues[] = $label . ': review ' . $role . ' / ' . $permission;
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    try {
        verify_csrf();
        if ((string)($_POST['action'] ?? '') === 'mark_reviewed') {
            setting_set('system','permission_matrix_reviewed_at',date('Y-m-d H:i:s'));
            setting_set('system','permission_matrix_reviewed_by',(string)(current_user()['id'] ?? 0));
            setting_set('system','role_navigation_permission_hardening_v067_reviewed','1');
            audit('permission_matrix_reviewed','system',null,['issues'=>$issues]);
            flash('success','Permission Matrix review was recorded for go-live readiness.');
            header('Location: permission_matrix.php'); exit;
        }
    } catch (Throwable $exception) {
        flash('error',$exception->getMessage());
        header('Location: permission_matrix.php'); exit;
    }
}

render_header('Permission Matrix');
?>
<section class="admin-hero card"><div><span class="eyebrow">Role access control</span><h2>Permission Matrix</h2><p>Review what each role can view, add, edit, print, manage, or administer before giving accounts to clinic staff.</p><div class="status-actions"><?php if(superuser_primary_workspace()):?><a class="btn ghost" href="system_setup.php?tab=permissions">Edit role permissions</a><?php else:?><span class="badge">Read-only review</span><?php endif;?><?php if(superuser_primary_workspace() && function_exists('dental_internal_qa_visible') && dental_internal_qa_visible()): ?><a class="btn ghost" href="go_live_checklist.php#role-flow-qa">Internal role QA gate</a><?php endif; ?></div></div><div class="admin-hero-icon"><?=dental_icon('shield')?></div></section>
<?php if (!permission_matrix_schema_ready()): ?>
<div class="card"><h2>Permission tables are not ready</h2><p>Apply all pending updates before reviewing access.</p><?php if(superuser_primary_workspace()):?><a class="btn primary" href="system_setup.php?tab=updates">Open System Control updates</a><?php else:?><p class="muted">Ask the Superuser to apply the pending update.</p><?php endif;?></div>
<?php else: ?>
<section class="stats mini-stats"><?php stat_card('Roles', count($roles), 'Included in matrix'); stat_card('Access review', setting_get('system','permission_matrix_reviewed_at','')!==''?'Recorded':'Pending', (string)setting_get('system','permission_matrix_reviewed_at','Before go-live')); stat_card('Route audit', (int)($navigationAudit['critical_count'] ?? 0) . ' critical', (int)($navigationAudit['routes_total'] ?? 0) . ' routes scanned'); stat_card('Audit issues', count($issues), count($issues)===0?'No high-risk mismatch detected':'Review flagged role grants'); ?></section>
<?php if($issues): ?><div class="alert warning"><strong>Permission review needed.</strong><ul><?php foreach($issues as $issue): ?><li><?=e($issue)?></li><?php endforeach; ?></ul></div><?php else: ?><div class="alert success"><strong>Baseline role separation looks acceptable.</strong> Still review permissions with the clinic owner before go-live.</div><?php endif; ?>
<?php if(function_exists('dental_internal_qa_visible') && dental_internal_qa_visible()): ?>
<?php if (!empty($navigationAudit['issues'])): ?><section class="card"><div class="card-head"><div><h2>Internal route and navigation audit</h2><p>Every visible link should land on a route that exists and belongs to the active role workspace. Critical items must be fixed before selling.</p></div><span class="badge <?=((int)($navigationAudit['critical_count']??0)===0)?'success':'warning'?>"><?=e((string)($navigationAudit['critical_count']??0))?> critical</span></div><div class="table-wrap"><table><thead><tr><th>Severity</th><th>Route</th><th>Finding</th></tr></thead><tbody><?php foreach(array_slice((array)$navigationAudit['issues'],0,40) as $item): ?><tr><td><span class="badge <?=($item['severity']??'notice')==='critical'?'danger':(($item['severity']??'notice')==='warning'?'warning':'neutral')?>"><?=e((string)($item['severity']??'notice'))?></span></td><td><code><?=e((string)($item['route']??''))?></code></td><td><?=e((string)($item['message']??''))?></td></tr><?php endforeach; ?></tbody></table></div><?php if(count((array)$navigationAudit['issues'])>40): ?><p class="muted">Showing first 40 findings. Resolve critical items first.</p><?php endif; ?></section><?php else: ?><div class="alert success"><strong>Internal route audit passed.</strong> Known protected routes are covered by role workspaces or explicit route requirements.</div><?php endif; ?>
<?php else: ?>
<div class="alert success"><strong>Production access view.</strong> Internal route-audit/testing details are hidden. Use the matrix below to manage real clinic permissions.</div>
<?php endif; ?>
<?php if($roleAccessMatrix): ?><section class="card"><div class="card-head"><div><h2>Role capability lanes</h2><p>Operational language for what each account type is supposed to do and what it must never do.</p></div></div><div class="setup-phase-grid role-capability-grid"><?php foreach($roleAccessMatrix as $code=>$info): ?><article class="setup-phase-card"><header><h3><?=e((string)($info['label']??$code))?></h3><span class="badge neutral"><?=e((string)$code)?></span></header><p><?=e((string)($info['scope']??''))?></p><small><strong>Allowed:</strong> <?=e(implode(', ', array_map('role_access_action_label',(array)($info['allow']??[]))))?></small><small><strong>Blocked:</strong> <?=e(implode(', ', array_map('role_access_action_label',(array)($info['deny']??[]))))?></small></article><?php endforeach; ?></div></section><?php endif; ?>
<section class="card table-wrap permission-review-table"><div class="card-head"><div><h2>Role access table</h2><p>✓ means the role currently has that permission grant. Superuser owner authority applies only in the explicit Superuser workspace; selected Task Views use the active role boundary.</p></div></div><table data-paginate data-pagination-threshold="10"><thead><tr><th>Module / permission</th><?php foreach($roles as $code=>$label): ?><th><?=e($label)?><small><?=e($code)?></small></th><?php endforeach; ?></tr></thead><tbody><?php foreach($modules as [$module,$permission,$description]): ?><tr><td><strong><?=e($module)?></strong><small><?=e($permission)?> · <?=e($description)?></small></td><?php foreach($roles as $code=>$label): $allowed = permission_matrix_has($grants,$code,$permission); ?><td class="center-cell"><?= $allowed ? '<span class="status-ok">✓</span>' : '<span class="muted">—</span>' ?></td><?php endforeach; ?></tr><?php endforeach; ?></tbody></table></section>
<section class="card"><div class="card-head"><div><h2>Role separation rules</h2><p>These are the operational safeguards to verify during orientation.</p></div></div><div class="setup-phase-grid"><?php foreach($staticLimits as [$label,$role,$permission,$expected,$note]): $actual = permission_matrix_has($grants,$role,$permission); $ok = $expected ? $actual : !$actual; ?><article class="setup-phase-card <?= $ok?'ready':'attention' ?>"><header><h3><?=e($label)?></h3><span class="badge <?= $ok?'success':'warning' ?>"><?= $ok?'OK':'Review' ?></span></header><p><?=e($note)?></p><small><?=e($role)?> / <?=e($permission)?>: <?= $actual?'granted':'not granted' ?></small></article><?php endforeach; ?></div><form method="post" class="status-actions"><input type="hidden" name="csrf" value="<?=e(csrf_token())?>"><input type="hidden" name="action" value="mark_reviewed"><button class="btn primary" type="submit">Mark permission review completed</button></form></section>
<?php endif; ?>
<?php render_footer();
