<?php
/**
 * @intranet-file common/roles.php
 * @intranet-file-version 2026.05.06.1
 * @intranet-file-date 2026-05-06
 * @intranet-file-owner CSIF Intranet
 */
declare(strict_types=1);

function intranet_role_key_normalize(string $role): string
{
    $role = strtolower(trim($role));
    $role = preg_replace('/\s+/', '_', $role) ?? $role;
    $role = preg_replace('/[^a-z0-9_*.-]/', '', $role) ?? $role;
    return trim($role, '.-');
}

function intranet_role_label_from_key(string $role): string
{
    $role = intranet_role_key_normalize($role);
    if ($role === '') {
        return '';
    }

    $label = str_replace(['app_', '_', '-'], ['', ' ', ' '], $role);
    $label = trim($label);
    return $label !== '' ? ucwords($label) : $role;
}

function intranet_role_key_is_valid(string $role): bool
{
    $role = intranet_role_key_normalize($role);
    if ($role === 'administradores') {
        return true;
    }

    return (bool) preg_match('/^app_[a-z0-9][a-z0-9_.-]{0,95}$/', $role);
}

function intranet_default_roles(): array
{
    return [
        'administradores' => [
            'label' => 'Administradores',
            'description' => 'Acceso completo a administracion y mantenimiento de la intranet.',
            'position' => 10,
            'is_system' => 1,
        ],
    ];
}

function intranet_roles_ensure_schema(PDO $pdo): void
{
    static $done = false;
    if ($done) {
        return;
    }

    $pdo->exec(
        "CREATE TABLE IF NOT EXISTS intranet_roles (
            role_key varchar(100) NOT NULL,
            label varchar(150) NOT NULL,
            description text DEFAULT NULL,
            is_enabled tinyint(1) NOT NULL DEFAULT 1,
            is_system tinyint(1) NOT NULL DEFAULT 0,
            position int NOT NULL DEFAULT 0,
            created_at timestamp NULL DEFAULT current_timestamp(),
            updated_at timestamp NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
            PRIMARY KEY (role_key),
            KEY idx_intranet_roles_enabled_position (is_enabled, position, role_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish_ci"
    );

    $st = $pdo->prepare(
        "INSERT INTO intranet_roles (role_key, label, description, is_enabled, is_system, position)
         VALUES (:role_key, :label, :description, 1, :is_system, :position)
         ON DUPLICATE KEY UPDATE
            label = IF(TRIM(label) = '', VALUES(label), label),
            description = IF(description IS NULL OR TRIM(description) = '', VALUES(description), description),
            is_system = GREATEST(is_system, VALUES(is_system)),
            position = IF(position = 0, VALUES(position), position)"
    );

    foreach (intranet_default_roles() as $role => $meta) {
        $role = intranet_role_key_normalize($role);
        if ($role === '' || !intranet_role_key_is_valid($role)) {
            continue;
        }
        $st->execute([
            'role_key' => $role,
            'label' => (string) ($meta['label'] ?? intranet_role_label_from_key($role)),
            'description' => (string) ($meta['description'] ?? ''),
            'is_system' => (int) ($meta['is_system'] ?? 1),
            'position' => (int) ($meta['position'] ?? 999),
        ]);
    }

    $done = true;
}

function intranet_roles_collect_referenced(PDO $pdo): array
{
    $roles = [];
    foreach (['intranet_apps', 'plugin_builder_apps'] as $table) {
        try {
            $check = $pdo->prepare(
                "SELECT COUNT(*)
                   FROM information_schema.tables
                  WHERE table_schema = DATABASE()
                    AND table_name = :table_name"
            );
            $check->execute(['table_name' => $table]);
            if ((int) $check->fetchColumn() <= 0) {
                continue;
            }

            $rs = $pdo->query("SELECT roles_json FROM {$table} WHERE roles_json IS NOT NULL AND roles_json <> ''");
            if ($rs === false) {
                continue;
            }
            while ($row = $rs->fetch(PDO::FETCH_ASSOC)) {
                foreach (intranet_roles_normalize((string) ($row['roles_json'] ?? '')) as $role) {
                    if (intranet_role_key_is_valid($role)) {
                        $roles[$role] = true;
                    }
                }
            }
        } catch (Throwable $e) {
            continue;
        }
    }

    foreach (is_array($_SESSION['roles'] ?? null) ? $_SESSION['roles'] : [] as $role) {
        foreach (intranet_roles_normalize([$role]) as $normalized) {
            if (intranet_role_key_is_valid($normalized)) {
                $roles[$normalized] = true;
            }
        }
    }

    $out = array_keys($roles);
    sort($out, SORT_NATURAL | SORT_FLAG_CASE);
    return $out;
}

function intranet_roles_fetch(PDO $pdo, bool $onlyEnabled = false): array
{
    intranet_roles_ensure_schema($pdo);

    $sql = "SELECT role_key, label, description, is_enabled, is_system, position, created_at, updated_at
              FROM intranet_roles";
    if ($onlyEnabled) {
        $sql .= " WHERE is_enabled = 1";
    }
    $sql .= " ORDER BY position ASC, role_key ASC";

    $rows = $pdo->query($sql)->fetchAll(PDO::FETCH_ASSOC) ?: [];
    foreach ($rows as &$row) {
        $row['role_key'] = intranet_role_key_normalize((string) ($row['role_key'] ?? ''));
        $row['_is_valid_role_key'] = intranet_role_key_is_valid((string) $row['role_key']) ? 1 : 0;
        $row['label'] = trim((string) ($row['label'] ?? ''));
        if ($row['label'] === '') {
            $row['label'] = intranet_role_label_from_key((string) $row['role_key']);
        }
        $row['is_enabled'] = (int) ($row['is_enabled'] ?? 0);
        $row['is_system'] = (int) ($row['is_system'] ?? 0);
        $row['position'] = (int) ($row['position'] ?? 0);
    }
    unset($row);

    return array_values(array_filter($rows, static fn($row): bool => (string) ($row['role_key'] ?? '') !== ''));
}

function intranet_role_usage_counts(PDO $pdo): array
{
    $counts = [];
    foreach (['intranet_apps', 'plugin_builder_apps'] as $table) {
        try {
            $check = $pdo->prepare(
                "SELECT COUNT(*)
                   FROM information_schema.tables
                  WHERE table_schema = DATABASE()
                    AND table_name = :table_name"
            );
            $check->execute(['table_name' => $table]);
            if ((int) $check->fetchColumn() <= 0) {
                continue;
            }

            $rs = $pdo->query("SELECT roles_json FROM {$table} WHERE roles_json IS NOT NULL AND roles_json <> ''");
            if ($rs === false) {
                continue;
            }
            while ($row = $rs->fetch(PDO::FETCH_ASSOC)) {
                foreach (intranet_roles_normalize((string) ($row['roles_json'] ?? '')) as $role) {
                    if (intranet_role_key_is_valid($role)) {
                        $counts[$role] = ($counts[$role] ?? 0) + 1;
                    }
                }
            }
        } catch (Throwable $e) {
            continue;
        }
    }
    ksort($counts, SORT_NATURAL | SORT_FLAG_CASE);
    return $counts;
}

function intranet_roles_ensure_app_registered(PDO $pdo): void
{
    intranet_roles_ensure_schema($pdo);

    try {
        $check = $pdo->prepare("SELECT id FROM intranet_apps WHERE app_id = ? LIMIT 1");
        $check->execute(['roles']);
        if ($check->fetch(PDO::FETCH_ASSOC)) {
            return;
        }

        $group = 'Administracion';
        if (function_exists('catalogo_ensure_group')) {
            $group = catalogo_ensure_group($pdo, 'apps', 'Administracion', null);
        }

        $pos = 1;
        try {
            $p = $pdo->prepare("SELECT COALESCE(MAX(position), 0) + 1 FROM intranet_apps WHERE group_name = ?");
            $p->execute([$group]);
            $pos = (int) ($p->fetchColumn() ?: 1);
        } catch (Throwable $e) {
            $pos = 1;
        }

        $icon = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/><path d="M12 11l2 2 4-4"/></svg>';
        $roles = json_encode(['administradores'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        $ins = $pdo->prepare(
            "INSERT INTO intranet_apps
                (app_id, name, url, group_name, description, icon, image, new_tab, roles_json, position, is_enabled, created_at, updated_at)
             VALUES
                (?, ?, ?, ?, ?, ?, NULL, 0, ?, ?, 1, NOW(), NOW())"
        );
        $ins->execute([
            'roles',
            'Gestion de roles',
            'base/roles/index.php',
            $group,
            'Gestion centralizada de roles de la intranet',
            $icon,
            $roles,
            $pos,
        ]);
    } catch (Throwable $e) {
        error_log('intranet_roles_ensure_app_registered: ' . $e->getMessage());
    }
}

function intranet_known_roles(): array
{
    $roles = [];
    foreach (array_keys(intranet_default_roles()) as $role) {
        $roles[$role] = true;
    }

    if (function_exists('db')) {
        try {
            $pdo = db();
            foreach (intranet_roles_fetch($pdo, true) as $row) {
                $role = intranet_role_key_normalize((string) ($row['role_key'] ?? ''));
                if ($role !== '' && intranet_role_key_is_valid($role)) {
                    $roles[$role] = true;
                }
            }
            foreach (intranet_roles_collect_referenced($pdo) as $role) {
                if ($role !== '' && intranet_role_key_is_valid($role)) {
                    $roles[$role] = true;
                }
            }
        } catch (Throwable $e) {
        }
    }

    $out = array_keys($roles);
    sort($out, SORT_NATURAL | SORT_FLAG_CASE);
    return $out;
}

function intranet_roles_normalize($value): array
{
    if (is_string($value)) {
        $raw = trim($value);
        if ($raw === '') {
            return [];
        }

        $decoded = json_decode($raw, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
            $value = $decoded;
        } else {
            $value = preg_split('/[\s,;]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
        }
    }

    if (!is_array($value)) {
        return [];
    }

    $roles = [];
    foreach ($value as $role) {
        if (!is_string($role) && !is_numeric($role)) {
            continue;
        }
        $role = intranet_role_key_normalize((string) $role);
        if ($role !== '' && intranet_role_key_is_valid($role)) {
            $roles[] = $role;
        }
    }

    return array_values(array_unique($roles));
}

function intranet_roles_from_post($value, array $fallback = ['administradores']): array
{
    $roles = intranet_roles_normalize($value);
    return $roles ?: intranet_roles_normalize($fallback);
}

function intranet_roles_to_json($value, array $fallback = ['administradores']): string
{
    return json_encode(intranet_roles_from_post($value, $fallback), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function intranet_available_roles(?PDO $pdo = null, array $extra = []): array
{
    $roles = [];
    if ($pdo instanceof PDO) {
        try {
            foreach (intranet_roles_fetch($pdo, true) as $row) {
                $role = intranet_role_key_normalize((string) ($row['role_key'] ?? ''));
                if ($role !== '' && intranet_role_key_is_valid($role)) {
                    $roles[$role] = true;
                }
            }
        } catch (Throwable $e) {
        }
    }

    foreach (intranet_roles_normalize($extra) as $role) {
        $roles[$role] = true;
    }

    if (!$pdo instanceof PDO) {
        foreach (intranet_known_roles() as $role) {
            $roles[$role] = true;
        }
    }

    $out = array_keys($roles);
    sort($out, SORT_NATURAL | SORT_FLAG_CASE);
    return $out;
}
