<?php
namespace App\Controllers;

use App\Core\Controller;
use App\Core\Auth;
use App\Core\Database;
use PDO;

class RolesController extends Controller
{
    private const BASE_PERMISSION_CATALOG = [
        ['key_name' => 'dashboard.view', 'label' => 'Voir tableau de bord'],
        ['key_name' => 'incidents.list', 'label' => 'Lister incidents'],
        ['key_name' => 'incidents.list_own', 'label' => 'Lister ses propres incidents'],
        ['key_name' => 'incidents.create', 'label' => 'Créer incident'],
        ['key_name' => 'incidents.view', 'label' => 'Voir incident'],
        ['key_name' => 'incidents.view_own', 'label' => 'Voir ses propres incidents'],
        ['key_name' => 'incidents.edit', 'label' => 'Modifier incident'],
        ['key_name' => 'incidents.delete', 'label' => 'Supprimer incident'],
        ['key_name' => 'users.list', 'label' => 'Lister utilisateurs'],
        ['key_name' => 'users.create', 'label' => 'Créer utilisateur'],
        ['key_name' => 'users.view', 'label' => 'Voir utilisateur'],
        ['key_name' => 'users.edit', 'label' => 'Modifier utilisateur'],
        ['key_name' => 'users.delete', 'label' => 'Supprimer utilisateur'],
        ['key_name' => 'users.toggle_2fa', 'label' => 'Activer/Désactiver 2FA'],
        ['key_name' => 'roles.list', 'label' => 'Lister rôles'],
        ['key_name' => 'roles.create', 'label' => 'Créer rôle'],
        ['key_name' => 'roles.edit', 'label' => 'Modifier rôle'],
        ['key_name' => 'roles.delete', 'label' => 'Supprimer rôle'],
        ['key_name' => 'settings.view', 'label' => 'Voir paramètres'],
        ['key_name' => 'settings.edit', 'label' => 'Modifier paramètres'],
        ['key_name' => 'catalog.view', 'label' => 'Voir catalogue'],
        ['key_name' => 'catalog.create', 'label' => 'Créer catégorie/article'],
        ['key_name' => 'catalog.edit', 'label' => 'Modifier catégorie/article'],
        ['key_name' => 'catalog.delete', 'label' => 'Supprimer catégorie/article'],
        ['key_name' => 'quotes.list', 'label' => 'Lister devis'],
        ['key_name' => 'quotes.create', 'label' => 'Créer devis'],
        ['key_name' => 'quotes.view', 'label' => 'Voir devis'],
        ['key_name' => 'quotes.edit', 'label' => 'Modifier devis'],
        ['key_name' => 'quotes.delete', 'label' => 'Supprimer devis'],
        ['key_name' => 'sla.view', 'label' => 'Voir SLA'],
        ['key_name' => 'sla.create', 'label' => 'Créer SLA'],
        ['key_name' => 'sla.edit', 'label' => 'Modifier SLA'],
        ['key_name' => 'sla.delete', 'label' => 'Supprimer SLA'],
        ['key_name' => 'clients.list', 'label' => 'Lister clients'],
        ['key_name' => 'clients.create', 'label' => 'Créer client'],
        ['key_name' => 'clients.view', 'label' => 'Voir client'],
        ['key_name' => 'clients.edit', 'label' => 'Modifier client'],
        ['key_name' => 'clients.delete', 'label' => 'Supprimer client'],
        ['key_name' => 'prospect_clients.list', 'label' => 'Lister prospects et clients'],
        ['key_name' => 'prospect_clients.create', 'label' => 'Créer un prospect'],
        ['key_name' => 'prospect_clients.view', 'label' => 'Consulter un prospect ou client'],
        ['key_name' => 'prospect_clients.edit', 'label' => 'Modifier un prospect ou client'],
        ['key_name' => 'prospect_clients.receipt_upload', 'label' => 'Uploader le reçu de paiement'],
        ['key_name' => 'prospect_clients.raccordement_create', 'label' => 'Transmettre vers raccordement client'],
        ['key_name' => 'planning.view', 'label' => 'Voir planning'],
        ['key_name' => 'planning.view_own', 'label' => 'Voir son propre planning'],
        ['key_name' => 'planning.create', 'label' => 'Créer tâche planning'],
        ['key_name' => 'planning.edit', 'label' => 'Modifier planning'],
        ['key_name' => 'planning.edit_own', 'label' => 'Modifier son propre planning'],
        ['key_name' => 'planning.delete', 'label' => 'Supprimer tâche planning'],
        ['key_name' => 'maintenance_ftth.list', 'label' => 'Lister maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.list_own', 'label' => 'Lister sa maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.create', 'label' => 'Créer ticket maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.view', 'label' => 'Voir ticket maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.view_own', 'label' => 'Voir ses tickets maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.edit', 'label' => 'Modifier ticket maintenance FTTH B2B'],
        ['key_name' => 'maintenance_ftth.delete', 'label' => 'Supprimer ticket maintenance FTTH B2B'],
        ['key_name' => 'otb_management.list', 'label' => 'Lister les boîtiers OTB'],
        ['key_name' => 'otb_management.box_save', 'label' => 'Créer ou modifier un boîtier OTB'],
        ['key_name' => 'otb_management.box_delete', 'label' => 'Supprimer un boîtier OTB'],
        ['key_name' => 'otb_management.zone_save', 'label' => 'Gérer les paramètres de zone OTB'],
        ['key_name' => 'cartography.view', 'label' => 'Voir cartographie'],
        ['key_name' => 'cartography.supervision', 'label' => 'Accéder à la supervision cartographie'],
        ['key_name' => 'notifications.list', 'label' => 'Lister notifications'],
        ['key_name' => 'notifications.read_all', 'label' => 'Tout marquer comme lu'],
        ['key_name' => 'referentials.import', 'label' => 'Importer référentiels'],
        ['key_name' => 'referentials.export', 'label' => 'Exporter référentiels'],
        ['key_name' => 'locations.list', 'label' => 'Lister localisations'],
        ['key_name' => 'locations.create', 'label' => 'Créer localisation'],
        ['key_name' => 'locations.view', 'label' => 'Voir localisation'],
        ['key_name' => 'locations.edit', 'label' => 'Modifier localisation'],
        ['key_name' => 'locations.delete', 'label' => 'Supprimer localisation'],
        ['key_name' => 'sites.list', 'label' => 'Lister sites'],
        ['key_name' => 'sites.create', 'label' => 'Créer site'],
        ['key_name' => 'sites.edit', 'label' => 'Modifier site'],
        ['key_name' => 'sites.delete', 'label' => 'Supprimer site'],
        ['key_name' => 'liaisons.list', 'label' => 'Lister liaisons'],
        ['key_name' => 'liaisons.create', 'label' => 'Créer liaison'],
        ['key_name' => 'liaisons.view', 'label' => 'Voir liaison'],
        ['key_name' => 'liaisons.edit', 'label' => 'Modifier liaison'],
        ['key_name' => 'liaisons.delete', 'label' => 'Supprimer liaison'],
        ['key_name' => 'equipments.list', 'label' => 'Lister équipements'],
        ['key_name' => 'equipments.create', 'label' => 'Créer équipement'],
        ['key_name' => 'equipments.edit', 'label' => 'Modifier équipement'],
        ['key_name' => 'equipments.delete', 'label' => 'Supprimer équipement'],
        ['key_name' => 'priority_levels.list', 'label' => 'Lister niveaux de priorité'],
        ['key_name' => 'priority_levels.create', 'label' => 'Créer niveau de priorité'],
        ['key_name' => 'priority_levels.edit', 'label' => 'Modifier niveau de priorité'],
        ['key_name' => 'priority_levels.delete', 'label' => 'Supprimer niveau de priorité'],
        ['key_name' => 'supervisor.dashboard', 'label' => 'Voir dashboard superviseur'],
        ['key_name' => 'supervisor.technicians', 'label' => 'Gérer techniciens'],
        ['key_name' => 'supervisor.technician_activity', 'label' => 'Voir activité technicien'],
    ];

    private const MODULE_LABELS = [
        'dashboard' => 'Tableau de bord',
        'incidents' => 'Incidents',
        'clients' => 'Clients',
        'prospect_clients' => 'Prospects & Clients',
        'users' => 'Utilisateurs',
        'roles' => 'Rôles',
        'settings' => 'Paramètres',
        'catalog' => 'Catalogue',
        'quotes' => 'Devis',
        'sla' => 'SLA',
        'referentials' => 'Référentiels métier',
        'locations' => 'Localisations',
        'sites' => 'Sites',
        'liaisons' => 'Liaisons',
        'equipments' => 'Équipements',
        'priority_levels' => 'Niveaux de priorité',
        'planning' => 'Planning',
        'notifications' => 'Notifications',
        'cartography' => 'Cartographie',
        'maintenance_ftth' => 'Maintenance FTTH B2B',
        'otb_management' => 'Gestion des boîtiers OTB',
        'supervisor' => 'Supervision',
    ];

    private const ACTION_LABELS = [
        'list' => 'Lister',
        'list_own' => 'Lister ses propres',
        'view' => 'Voir',
        'view_own' => 'Voir ses propres',
        'create' => 'Créer',
        'edit' => 'Modifier',
        'delete' => 'Supprimer',
        'toggle_2fa' => 'Activer/Désactiver 2FA',
        'assign' => 'Affecter',
        'unassign' => 'Désaffecter',
        'bulk_assign' => 'Affecter en masse',
        'bulk_delete' => 'Supprimer en masse',
        'import' => 'Importer',
        'export' => 'Exporter',
        'template' => 'Télécharger le modèle',
        'search' => 'Rechercher',
        'feed' => 'Consulter le flux',
        'read_all' => 'Tout marquer comme lu',
        'dashboard' => 'Voir le dashboard',
        'technicians' => 'Gérer les techniciens',
        'technician_activity' => 'Voir activité technicien',
        'data' => 'Consulter les données',
        'trajectories' => 'Consulter les trajectoires',
        'technicians_list' => 'Lister les techniciens',
        'update_location' => 'Mettre à jour la position',
        'supervision' => 'Accéder à la supervision',
        'client_hints' => 'Consulter les suggestions client',
        'treatment' => 'Accéder au traitement',
        'treatment_save' => 'Sauvegarder le traitement',
        'treatment_validate' => 'Valider le traitement',
        'treatment_invalidate' => 'Invalider le traitement',
        'final_report_validate' => 'Valider le rapport final',
        'report_autosave' => 'Sauvegarder automatiquement le rapport',
        'report_save' => 'Enregistrer le rapport',
        'report_selection' => 'Consulter la sélection de rapport',
        'report_suggestions' => 'Consulter les suggestions de rapport',
        'validate' => 'Valider',
        'invalidate' => 'Invalider',
        'debug' => 'Accéder au debug',
    ];

    public function index(): void
    {
        if (!Auth::check()) { $this->redirect('/login'); }
        Auth::requireRole(['admin']);
        
        $pdo = Database::pdo();
        $storage = $this->detectPermissionStorage($pdo);
        try { $roles = $pdo->query('SELECT * FROM roles ORDER BY label')->fetchAll(); } catch (\Throwable $e) { $roles = []; }
        $permissions = $this->loadPermissionCatalog($pdo, $roles, $storage);
        $rolePerms = $this->loadRolePermissions($pdo, $roles, $storage);
        $permissionGroups = $this->groupPermissionsByModule($permissions);
        
        $this->view('roles/index', compact('roles', 'permissions', 'rolePerms', 'storage', 'permissionGroups'));
    }

    public function store(): void
    {
        if (!Auth::check()) { $this->redirect('/login'); }
        Auth::requireRole(['admin']);
        
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->redirect('/roles'); }
        
        $key = trim($_POST['key_name'] ?? '');
        $label = trim($_POST['label'] ?? '');
        $permissions = $this->normalizePermissionInput($_POST['permissions'] ?? []);
        
        if ($key && $label) {
            $pdo = Database::pdo();
            $storage = $this->detectPermissionStorage($pdo);
            $pdo->beginTransaction();
            try {
                if ($storage === 'json') {
                    $stmt = $pdo->prepare('INSERT INTO roles (key_name, label, permissions) VALUES (?, ?, ?)');
                    $stmt->execute([$key, $label, json_encode($permissions, JSON_UNESCAPED_UNICODE)]);
                } else {
                    $stmt = $pdo->prepare('INSERT INTO roles (key_name, label) VALUES (?, ?)');
                    $stmt->execute([$key, $label]);
                }
                $roleId = $pdo->lastInsertId();
                
                $this->persistRolePermissions($pdo, (int)$roleId, $permissions, $storage);
                $pdo->commit();
            } catch (\Throwable $e) {
                $pdo->rollBack();
                // Log error in production
            }
        }
        $this->redirect('/roles');
    }

    public function update(): void
    {
        if (!Auth::check()) { $this->redirect('/login'); }
        Auth::requireRole(['admin']);
        
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') { $this->redirect('/roles'); }
        
        $roleId = (int)($_POST['role_id'] ?? 0);
        $label = trim($_POST['label'] ?? '');
        $permissions = $this->normalizePermissionInput($_POST['permissions'] ?? []);
        
        if ($roleId && $label) {
            $pdo = Database::pdo();
            $storage = $this->detectPermissionStorage($pdo);
            $pdo->beginTransaction();
            try {
                if ($storage === 'json') {
                    $stmt = $pdo->prepare('UPDATE roles SET label = ?, permissions = ? WHERE id = ?');
                    $stmt->execute([$label, json_encode($permissions, JSON_UNESCAPED_UNICODE), $roleId]);
                } else {
                    $stmt = $pdo->prepare('UPDATE roles SET label = ? WHERE id = ?');
                    $stmt->execute([$label, $roleId]);
                }

                $this->persistRolePermissions($pdo, $roleId, $permissions, $storage);
                $pdo->commit();
            } catch (\Throwable $e) {
                $pdo->rollBack();
            }
        }
        $this->redirect('/roles');
    }

    private function detectPermissionStorage(PDO $pdo): string
    {
        if ($this->tableExists($pdo, 'permissions') && $this->tableExists($pdo, 'role_permissions')) {
            return 'relational';
        }

        return $this->columnExists($pdo, 'roles', 'permissions') ? 'json' : 'none';
    }

    private function loadPermissionCatalog(PDO $pdo, array $roles, string $storage): array
    {
        $catalog = $this->buildPermissionCatalog($roles);

        if ($storage === 'relational') {
            try {
                $dbPermissions = $pdo->query('SELECT key_name, label FROM permissions ORDER BY label')->fetchAll();
                foreach ($dbPermissions as $permission) {
                    $key = (string)($permission['key_name'] ?? '');
                    if ($key === '') {
                        continue;
                    }

                    $catalog[$key] = [
                        'key_name' => $key,
                        'label' => trim((string)($permission['label'] ?? '')) ?: ($catalog[$key]['label'] ?? $this->humanizePermissionKey($key)),
                    ];
                }

                $this->syncPermissionCatalog($pdo, $catalog);
            } catch (\Throwable $e) {
                return array_values($catalog);
            }
        }

        return array_values($catalog);
    }

    private function buildPermissionCatalog(array $roles): array
    {
        $catalog = [];

        foreach (self::BASE_PERMISSION_CATALOG as $permission) {
            $catalog[$permission['key_name']] = $permission;
        }

        foreach ($this->discoverPermissionsFromRoutes() as $permission) {
            $catalog[$permission['key_name']] = $permission;
        }

        foreach ($roles as $role) {
            foreach ($this->decodePermissionKeys($role['permissions'] ?? null) as $key) {
                if (!isset($catalog[$key])) {
                    $catalog[$key] = [
                        'key_name' => $key,
                        'label' => $this->humanizePermissionKey($key),
                    ];
                }
            }
        }

        uasort($catalog, function (array $left, array $right): int {
            $leftModule = $this->moduleLabelFromPermissionKey((string)($left['key_name'] ?? ''));
            $rightModule = $this->moduleLabelFromPermissionKey((string)($right['key_name'] ?? ''));

            $moduleComparison = strcmp($leftModule, $rightModule);
            if ($moduleComparison !== 0) {
                return $moduleComparison;
            }

            return strcmp((string)($left['label'] ?? ''), (string)($right['label'] ?? ''));
        });

        return $catalog;
    }

    private function discoverPermissionsFromRoutes(): array
    {
        $routesPath = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'routes' . DIRECTORY_SEPARATOR . 'web.php';
        if (!is_file($routesPath) || !is_readable($routesPath)) {
            return [];
        }

        $contents = @file_get_contents($routesPath);
        if ($contents === false || $contents === '') {
            return [];
        }

        preg_match_all('/\\$router->(get|post)\\(\'([^\']+)\'/i', $contents, $matches, PREG_SET_ORDER);
        $catalog = [];

        foreach ($matches as $match) {
            $method = strtolower((string)($match[1] ?? 'get'));
            $routePath = (string)($match[2] ?? '');
            foreach ($this->inferPermissionsFromRoute($method, $routePath) as $key) {
                $catalog[$key] = [
                    'key_name' => $key,
                    'label' => $this->humanizePermissionKey($key),
                ];
            }
        }

        return array_values($catalog);
    }

    private function inferPermissionsFromRoute(string $method, string $routePath): array
    {
        $path = trim($routePath);
        if ($path === '' || $path === '/' || str_starts_with($path, '/api/')) {
            return [];
        }

        $trimmed = trim($path, '/');
        if ($trimmed === '' || in_array($trimmed, ['login', 'logout'], true)) {
            return [];
        }

        $segments = array_values(array_filter(explode('/', $trimmed), static fn(string $segment): bool => $segment !== ''));
        if (empty($segments)) {
            return [];
        }

        if ($segments[0] === 'cartographie') {
            $segments[0] = 'cartography';
        }

        if ($segments[0] === 'cartography-debug') {
            return ['cartography.debug'];
        }

        $module = $this->normalizePermissionToken($segments[0]);
        if ($module === '') {
            return [];
        }

        if ($module === 'dashboard') {
            return ['dashboard.view'];
        }

        $nonPlaceholderSegments = array_values(array_filter($segments, static fn(string $segment): bool => !preg_match('/^\{.+\}$/', $segment)));
        $tail = array_slice($nonPlaceholderSegments, 1);
        $tailKey = $this->normalizePermissionToken(implode('_', $tail));

        if ($module === 'supervisor' && isset($tail[0])) {
            return [$module . '.' . $this->normalizePermissionToken(implode('_', $tail))];
        }

        if (empty($tail)) {
            return [$module . '.' . ($method === 'post' ? 'create' : 'list')];
        }

        if ($tailKey === 'show') {
            return [$module . '.view'];
        }

        if ($tailKey === 'create' || $tailKey === 'store') {
            return [$module . '.create'];
        }

        if ($tailKey === 'edit' || $tailKey === 'update') {
            return [$module . '.edit'];
        }

        if ($tailKey === 'delete' || $tailKey === 'destroy') {
            return [$module . '.delete'];
        }

        if ($tailKey === 'toggle2fa') {
            return [$module . '.toggle_2fa'];
        }

        if ($tailKey === 'read_all') {
            return [$module . '.read_all'];
        }

        if (preg_match('/^\{.+\}$/', $segments[1] ?? '')) {
            if (count($tail) === 1) {
                return [$module . '.view'];
            }

            if (($tail[1] ?? null) === 'edit' || ($tail[1] ?? null) === 'update') {
                return [$module . '.edit'];
            }

            if (($tail[1] ?? null) === 'delete' || ($tail[1] ?? null) === 'destroy') {
                return [$module . '.delete'];
            }
        }

        return [$module . '.' . $tailKey];
    }

    private function normalizePermissionToken(string $value): string
    {
        $value = trim($value);
        if ($value === '') {
            return '';
        }

        $value = strtolower($value);
        $value = preg_replace('/[^a-z0-9]+/', '_', $value) ?? '';

        return trim($value, '_');
    }

    private function groupPermissionsByModule(array $permissions): array
    {
        $groups = [];

        foreach ($permissions as $permission) {
            $key = (string)($permission['key_name'] ?? '');
            if ($key === '') {
                continue;
            }

            $module = strstr($key, '.', true);
            $module = $module === false ? 'general' : $module;

            if (!isset($groups[$module])) {
                $groups[$module] = [
                    'label' => self::MODULE_LABELS[$module] ?? $this->humanizePermissionKey($module),
                    'permissions' => [],
                ];
            }

            $groups[$module]['permissions'][] = $permission;
        }

        uasort($groups, static fn(array $left, array $right): int => strcmp($left['label'], $right['label']));

        foreach ($groups as &$group) {
            usort($group['permissions'], static fn(array $left, array $right): int => strcmp((string)($left['label'] ?? ''), (string)($right['label'] ?? '')));
        }
        unset($group);

        return $groups;
    }

    private function syncPermissionCatalog(PDO $pdo, array $catalog): void
    {
        if (!$this->columnExists($pdo, 'permissions', 'key_name') || !$this->columnExists($pdo, 'permissions', 'label')) {
            return;
        }

        $stmt = $pdo->prepare('INSERT IGNORE INTO permissions (key_name, label) VALUES (?, ?)');
        foreach ($catalog as $permission) {
            $key = (string)($permission['key_name'] ?? '');
            if ($key === '') {
                continue;
            }

            $stmt->execute([$key, (string)($permission['label'] ?? $this->humanizePermissionKey($key))]);
        }
    }

    private function moduleLabelFromPermissionKey(string $key): string
    {
        $module = strstr($key, '.', true);
        $module = $module === false ? $key : $module;

        return self::MODULE_LABELS[$module] ?? $this->humanizePermissionKey($module);
    }

    private function loadRolePermissions(PDO $pdo, array $roles, string $storage): array
    {
        $rolePerms = [];

        foreach ($roles as $role) {
            $roleId = (int)($role['id'] ?? 0);

            if ($storage === 'relational') {
                try {
                    $stmt = $pdo->prepare('
                        SELECT p.key_name
                        FROM role_permissions rp
                        JOIN permissions p ON p.id = rp.permission_id
                        WHERE rp.role_id = ?
                    ');
                    $stmt->execute([$roleId]);
                    $rolePerms[$roleId] = array_values(array_unique($stmt->fetchAll(PDO::FETCH_COLUMN) ?: []));
                    continue;
                } catch (\Throwable $e) {
                    $rolePerms[$roleId] = [];
                    continue;
                }
            }

            $rolePerms[$roleId] = $this->decodePermissionKeys($role['permissions'] ?? null);
        }

        return $rolePerms;
    }

    private function persistRolePermissions(PDO $pdo, int $roleId, array $permissionKeys, string $storage): void
    {
        if ($storage !== 'relational') {
            return;
        }

        $stmt = $pdo->prepare('DELETE FROM role_permissions WHERE role_id = ?');
        $stmt->execute([$roleId]);

        if (empty($permissionKeys)) {
            return;
        }

        $placeholders = implode(',', array_fill(0, count($permissionKeys), '?'));
        $lookup = $pdo->prepare("SELECT key_name, id FROM permissions WHERE key_name IN ($placeholders)");
        $lookup->execute($permissionKeys);
        $permissionIds = $lookup->fetchAll(PDO::FETCH_KEY_PAIR);

        if (empty($permissionIds)) {
            return;
        }

        $stmtPerm = $pdo->prepare('INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)');
        foreach ($permissionKeys as $key) {
            $permissionId = $permissionIds[$key] ?? null;
            if ($permissionId !== null) {
                $stmtPerm->execute([$roleId, (int)$permissionId]);
            }
        }
    }

    private function normalizePermissionInput($permissions): array
    {
        if (!is_array($permissions)) {
            return [];
        }

        $normalized = [];
        foreach ($permissions as $permission) {
            $key = trim((string)$permission);
            if ($key !== '') {
                $normalized[$key] = $key;
            }
        }

        return array_values($normalized);
    }

    private function decodePermissionKeys($rawPermissions): array
    {
        if ($rawPermissions === null || $rawPermissions === '') {
            return [];
        }

        if (is_string($rawPermissions)) {
            $decoded = json_decode($rawPermissions, true);
            if (!is_array($decoded)) {
                return [];
            }
        } elseif (is_array($rawPermissions)) {
            $decoded = $rawPermissions;
        } else {
            return [];
        }

        return $this->normalizePermissionInput($decoded);
    }

    private function tableExists(PDO $pdo, string $table): bool
    {
        $stmt = $pdo->prepare('SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1');
        $stmt->execute([$table]);
        return (bool)$stmt->fetchColumn();
    }

    private function columnExists(PDO $pdo, string $table, string $column): bool
    {
        $stmt = $pdo->prepare('SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1');
        $stmt->execute([$table, $column]);
        return (bool)$stmt->fetchColumn();
    }

    private function humanizePermissionKey(string $key): string
    {
        $parts = explode('.', $key, 2);
        $module = $parts[0] ?? $key;
        $action = $parts[1] ?? '';

        if ($action !== '') {
            $moduleLabel = self::MODULE_LABELS[$module] ?? $this->humanizeText($module);
            $actionLabel = self::ACTION_LABELS[$action] ?? $this->humanizeText($action);
            return trim($actionLabel . ' ' . $moduleLabel);
        }

        return self::MODULE_LABELS[$module] ?? $this->humanizeText($module);
    }

    private function humanizeText(string $value): string
    {
        $parts = preg_split('/[._-]+/', $value) ?: [$value];
        $parts = array_map(static fn(string $part): string => ucfirst($part), $parts);

        return implode(' ', $parts);
    }
}
