<?php

namespace App\Livewire\Configurations\Roles;

use App\Livewire\Configurations\Access;
use App\Livewire\Traits\ProtectsSystemRoles;
use Illuminate\Support\Facades\DB;
use Database\Seeders\RoleSeeder;
use Livewire\Component;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;

/**
 * Editar un rol — desde `Configuración.dc.html`.
 *
 * **Qué se arregla.** Los permisos se elegían en una lista plana de 126 casillas con un
 * buscador: para saber si un rol podía editar licencias había que buscar
 * «license-tokens» y contar. Ahora van **agrupados por módulo**, con «todos / ninguno»
 * por grupo y el recuento de cada uno, que es como se piensa el reparto: «Manager ve
 * licencias enteras y de clientes nada».
 *
 * Y arriba se dice **a cuánta gente afecta** y **cuántos permisos lleva**, con un aviso
 * si el rol se queda a cero: quien lo tenga entra y no ve una sola pantalla, sin ningún
 * error. Era el caso de Support y ninguna pantalla lo decía.
 *
 * **Aquí se editan todos los roles menos Admin.** Hasta el 2026-09-15 estaban bloqueados
 * los seis del seeder, y eso era pasarse: un Admin no podía cambiar los permisos de Soporte
 * —que es justo la clase de decisión que toma un Admin— sin tocar el seeder y desplegar.
 *
 * El único que se queda fuera es **Admin**, y por un motivo concreto y no por precaución:
 * es el único rol desde el que uno puede cerrarse la puerta por dentro. Quitarle
 * `admin.roles.edit` deja el panel sin ninguna forma de volver a editar ningún rol, y la
 * salida sería entrar a la base a mano (MGR-018).
 *
 * **El nombre sí se queda fijo en los seis**, que es otra cosa: el seeder y las migraciones
 * de permisos los buscan por nombre, así que renombrar «Manager» no da error — hace que la
 * siguiente migración cree un Manager nuevo y vacío mientras los usuarios se quedan en el
 * renombrado. Para la etiqueta visible está `fullname`, que sí se cambia.
 */
class Edit extends Component
{
    use ProtectsSystemRoles;

    public Role $role;

    public string $name = '';
    public string $fullname = '';
    public ?string $description = null;

    /** Los nombres de los permisos marcados. */
    public array $permissions = [];

    public function mount(Role $role)
    {
        if ($this->isSystemRole($role)) {
            session()->flash('error', 'Los permisos de «' . $role->name . '» no se cambian desde '
                . 'el panel: es el rol que da acceso a esta misma pantalla, y quitarle un permiso '
                . 'de más dejaría el panel sin forma de volver a editar ningún rol.');

            return $this->redirect(route('configurations.access'), navigate: true);
        }

        $this->role = $role;
        $this->name = $role->name ?? '';
        $this->fullname = $role->fullname ?? '';
        $this->description = $role->description;
        $this->permissions = $role->permissions->pluck('name')->all();
    }

    protected function rules()
    {
        return [
            // Los seis del seeder conservan su nombre pase lo que pase: `in:` con el valor
            // actual, para que un intento de cambiarlo desde el navegador se rechace con un
            // mensaje y no en silencio. Ver `ProtectsSystemRoles::tieneNombreFijo()`.
            'name' => $this->tieneNombreFijo($this->role)
                ? 'required|string|in:' . $this->role->name
                : "required|string|unique:roles,name,{$this->role->id}",
            'fullname' => 'required|string|max:255',
            'description' => 'nullable|string',
            // Propiedad pública: llega del navegador y hay que validarla. Ver MGR-018.
            'permissions' => 'array',
            'permissions.*' => 'string|exists:permissions,name',
        ];
    }

    /** ¿Se le puede cambiar el nombre a este rol? La vista lo usa para bloquear el campo. */
    public function nombreEditable(): bool
    {
        return ! $this->tieneNombreFijo($this->role);
    }

    /** Marca o desmarca un permiso. */
    public function alternar(string $permiso): void
    {
        if (in_array($permiso, $this->permissions, true)) {
            $this->permissions = array_values(array_diff($this->permissions, [$permiso]));

            return;
        }

        $this->permissions[] = $permiso;
    }

    /**
     * Todo un módulo de golpe.
     *
     * Es como se reparte de verdad: «este rol lleva licencias entero» o «de clientes,
     * nada». Marcarlos de cuatro en cuatro a mano invita a dejarse uno.
     */
    public function todoElModulo(string $modulo, bool $marcar): void
    {
        $delModulo = $this->porModulo()[$modulo]['permisos'] ?? [];
        $claves = array_column($delModulo, 'clave');

        $this->permissions = $marcar
            ? array_values(array_unique(array_merge($this->permissions, $claves)))
            : array_values(array_diff($this->permissions, $claves));
    }

    public function update()
    {
        $this->authorize('admin.roles.edit');

        // Se revalida aquí y no solo en mount(): entre el montaje y el guardado hay una
        // petición a /livewire/update, y `$role` viaja en el snapshot. MGR-005.
        if ($this->isSystemRole($this->role)) {
            session()->flash('error', 'Los permisos de ' . $this->role->name . ' no se cambian desde el panel.');

            return;
        }

        $validated = $this->validate();

        $this->role->update([
            'name' => $validated['name'],
            'fullname' => $validated['fullname'],
            'description' => $validated['description'],
        ]);

        $this->role->syncPermissions($this->permissions ?? []);

        // La portada de Configuración cuenta los roles sin permisos y está cacheada: sin
        // esto, un rol que se acaba de quedar a cero no sale marcado hasta cinco minutos
        // después.
        \Illuminate\Support\Facades\Cache::forget('configs:portada:acceso');

        session()->flash('success', $this->permissions === []
            ? 'Guardado. Ojo: el rol se ha quedado sin ningún permiso, así que quien lo tenga '
                . 'entrará y no verá ninguna pantalla.'
            : 'Rol guardado con ' . count($this->permissions) . ' permiso(s). La caché de permisos '
                . 'se ha invalidado: se aplica ya.');
    }

    /**
     * Los permisos agrupados por módulo, con lo que este rol tiene marcado.
     *
     * Una sola consulta: se agrupa en PHP. Con `$permiso->roles` por fila serían más de
     * cien.
     *
     * **Y cada uno dice si todavía no tiene pantalla.** Es la mitad del arreglo de los 27
     * permisos huérfanos: veinte se borraron, pero siete son intención de verdad y siguen
     * ahí. Sin decirlo, quien marca esa casilla cree que está concediendo algo — y no
     * concede nada, porque no hay ninguna pantalla que lo compruebe. La lista viva está en
     * `RoleSeeder::PERMISOS_DE_ROADMAP`, con el motivo de cada uno.
     *
     * @return array<string, array{nombre: string, total: int, marcados: int, permisos: array<int, array{clave: string, desc: string, marcado: bool, sinPantalla: bool, motivo: ?string}>}>
     */
    public function porModulo(): array
    {
        $grupos = [];

        foreach (Permission::orderBy('name')->get() as $permiso) {
            $modulo = $this->moduloDe($permiso->name);
            $marcado = in_array($permiso->name, $this->permissions, true);

            $grupos[$modulo]['nombre'] = $modulo;
            $grupos[$modulo]['permisos'][] = [
                'clave' => $permiso->name,
                'desc' => $permiso->description ?: '—',
                'marcado' => $marcado,
                'sinPantalla' => isset(RoleSeeder::PERMISOS_DE_ROADMAP[$permiso->name]),
                'motivo' => RoleSeeder::PERMISOS_DE_ROADMAP[$permiso->name] ?? null,
            ];
        }

        ksort($grupos);

        foreach ($grupos as $modulo => $grupo) {
            $grupos[$modulo]['total'] = count($grupo['permisos']);
            $grupos[$modulo]['marcados'] = count(array_filter(
                $grupo['permisos'],
                fn (array $p) => $p['marcado']
            ));
        }

        return $grupos;
    }

    /** El módulo de un permiso: `admin.clients.index` → `clients`. La misma regla que Acceso. */
    private function moduloDe(string $permiso): string
    {
        $partes = explode('.', $permiso);

        if (($partes[1] ?? '') === 'configurations' && isset($partes[2])) {
            return 'configurations';
        }

        return $partes[1] ?? $permiso;
    }

    public function render()
    {
        $color = Access::colorDe($this->role->name);

        return view('livewire.configurations.roles.edit', [
            'modulos' => $this->porModulo(),
            'totalPermisos' => Permission::count(),
            // Cuánta gente lo tiene: los cambios les afectan de inmediato, y eso decide
            // si esto se toca ahora o se avisa antes.
            'usuarios' => DB::table('model_has_roles')
                ->where('role_id', $this->role->id)
                ->where('model_type', \App\Models\Auth\User::class)
                ->count(),
            'colorFg' => $color['fg'],
            'colorBg' => $color['bg'],
        ])->layout('layouts.app');
    }
}
