<?php

declare(strict_types=1);

/**
 * DentalCare v0.9.0.4 authorization and minimum-necessary access helpers.
 *
 * This layer keeps broad historical permissions compatible while requiring
 * explicit granular grants for patient identity, contact, medical, financial,
 * portal, and safety-alert data.
 */

function dental_access_control_schema_ready(): bool
{
    try {
        return schema_table_exists(table('access_decision_logs'))
            && schema_column_exists(table('access_decision_logs'), 'decision')
            && schema_column_exists(table('access_decision_logs'), 'permission_code')
            && schema_column_exists(table('access_decision_logs'), 'route_key');
    } catch (Throwable) {
        return false;
    }
}

function dental_access_record_decision(
    string $decision,
    string $permission = '',
    string $entityType = '',
    int|string|null $entityId = null,
    string $reason = ''
): void {
    static $writing = false;
    if ($writing || !current_user()) return;
    if (!in_array($decision, ['allowed','denied','break_glass'], true)) $decision = 'denied';

    try {
        if (!dental_access_control_schema_ready()) return;
        $writing = true;
        $stmt = db()->prepare(
            'INSERT INTO `' . table('access_decision_logs') . '` '
            . '(user_id,branch_id,actual_role,active_role,decision,permission_code,route_key,entity_type,entity_id,reason,ip_address,user_agent,created_at) '
            . 'VALUES (?,?,?,?,?,?,?,?,?,?,?,?,NOW())'
        );
        $stmt->execute([
            (int)(current_user()['id'] ?? 0) ?: null,
            signed_user_branch_id() ?: null,
            actual_role(),
            active_role(),
            $decision,
            mb_substr(trim($permission), 0, 120),
            mb_substr(function_exists('task_route_key') ? task_route_key() : basename((string)($_SERVER['SCRIPT_NAME'] ?? '')), 0, 120),
            $entityType !== '' ? mb_substr($entityType, 0, 80) : null,
            $entityId !== null ? mb_substr((string)$entityId, 0, 80) : null,
            $reason !== '' ? mb_substr($reason, 0, 500) : null,
            mb_substr((string)($_SERVER['REMOTE_ADDR'] ?? ''), 0, 64),
            mb_substr((string)($_SERVER['HTTP_USER_AGENT'] ?? ''), 0, 500),
        ]);
    } catch (Throwable $exception) {
        error_log('DentalCare access-decision logging failed: ' . $exception->getMessage());
    } finally {
        $writing = false;
    }
}

function dental_role_permission_granted(string $role, string $permission, array|string $fallbackRoles = []): bool
{
    // A focused Superuser workspace must use the selected role's explicit
    // permissions for presentation/data projection, not owner-wide authority.
    if (actual_role() === 'super_admin' && task_role_is_active()) {
        return role_permission_granted_for($role, $permission, $fallbackRoles);
    }
    if (function_exists('superuser_primary_workspace') && superuser_primary_workspace() && $role === 'super_admin') return true;
    return role_permission_granted_for($role, $permission, $fallbackRoles);
}

function dental_patient_access_capabilities(?string $role = null): array
{
    $role ??= active_role();
    $fallbacks = [
        'identity' => ['super_admin','clinic_admin','receptionist','staff','dentist','dental_assistant','cashier'],
        'contact' => ['super_admin','clinic_admin','receptionist','staff'],
        'safety_alert' => ['super_admin','clinic_admin','receptionist','staff','dentist','dental_assistant'],
        'medical_intake' => ['super_admin','receptionist','staff','dental_assistant'],
        'medical' => ['super_admin','clinic_admin','dentist','dental_assistant'],
        'medical_verify' => ['super_admin','dentist'],
        'financial' => ['super_admin','clinic_admin','staff','cashier'],
        'portal' => ['super_admin','clinic_admin','receptionist','staff'],
    ];
    $permissions = [
        'identity' => 'patients.identity.view',
        'contact' => 'patients.contact.view',
        'safety_alert' => 'patients.safety_alert.view',
        'medical_intake' => 'patients.medical.intake',
        'medical' => 'patients.medical.view',
        'medical_verify' => 'patients.medical.verify',
        'financial' => 'patients.financial.view',
        'portal' => 'patients.portal.manage',
    ];

    $result = [];
    foreach ($permissions as $key => $permission) {
        $granted = dental_role_permission_granted($role, $permission, $fallbacks[$key]);
        if (!$granted && function_exists('dental_break_glass_permission_allowed')) {
            $granted = dental_break_glass_permission_allowed($permission, signed_user_branch_id() ?: null);
        }
        $result[$key] = $granted;
    }
    return $result;
}

function dental_patient_capability(string $capability, ?string $role = null): bool
{
    $capabilities = dental_patient_access_capabilities($role);
    return !empty($capabilities[$capability]);
}

function require_patient_capability(string $capability, string $message = 'This task does not permit access to that patient information.'): void
{
    if (dental_patient_capability($capability)) return;
    dental_access_record_decision('denied', 'patients.' . $capability, 'patient', null, $message);
    access_denied($message);
}

function require_independent_actor(array|int $blockedUserIds, string $actionLabel): void
{
    $blocked = array_values(array_unique(array_filter(array_map('intval', (array)$blockedUserIds))));
    $current = (int)(current_user()['id'] ?? 0);
    if ($current < 1 || !in_array($current, $blocked, true)) return;

    $message = 'Independent approval is required. The same person cannot ' . trim($actionLabel) . '.';
    dental_access_record_decision('denied', 'approval.independent', '', null, $message);
    throw new RuntimeException($message);
}

function require_record_branch_scope(?int $recordBranchId, ?int $patientId = null, string $message = 'This record is outside your authorized branch or patient assignment.'): void
{
    if (record_branch_scope_allowed($recordBranchId, $patientId)) return;
    dental_access_record_decision('denied', 'branch.scope', $patientId ? 'patient' : 'record', $patientId, $message);
    access_denied($message);
}

function dental_mask_contact(string $value, string $kind = 'text'): string
{
    $value = trim($value);
    if ($value === '') return '';
    if ($kind === 'email' && str_contains($value, '@')) {
        [$local, $domain] = array_pad(explode('@', $value, 2), 2, '');
        $visible = mb_substr($local, 0, 1);
        return $visible . str_repeat('•', max(3, mb_strlen($local) - 1)) . '@' . $domain;
    }
    $digits = preg_replace('/\D+/', '', $value) ?? '';
    if ($digits !== '') return '••••' . substr($digits, -4);
    return mb_substr($value, 0, 1) . str_repeat('•', max(3, mb_strlen($value) - 1));
}

// Controlled emergency administrative access is loaded after the base
// permission helpers so it can extend denied permissions without weakening
// routine role and Task View isolation.
require_once DENTAL_ROOT . '/app/break_glass.php';
