<?php

namespace App\Livewire\Plugins;

use App\Models\Environments\Plugin;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\Url;
use Livewire\WithPagination;

/**
 * El inventario completo del parque: todo lo que hay instalado, nuestro o de terceros.
 *
 * **Es la lista larga**, y por eso es la única paginada de la sección. Responde a «¿qué hay
 * suelto por ahí?», que es una pregunta distinta de «¿qué versión de lo nuestro tiene cada
 * cliente?» —y durante mucho tiempo las dos se contestaban con la misma tabla de 470 filas.
 *
 * Aquí viven los filtros que solo tienen sentido sobre esa lista: el tipo de plugin y el
 * foco por estado. El buscador está en la base porque «Versiones de Moodle» también lo usa.
 */
class Inventario extends PantallaDelParque
{
    use WithPagination;

    /** Tipo de plugin: `mod`, `local`, `theme`… Las opciones salen de lo que hay. */
    #[Url(as: 'tipo', except: '')]
    public string $tipo = '';

    /** Estado: con actualización, divergentes, solo nuestros… */
    #[Url(as: 'ver', except: '')]
    public string $foco = '';

    public function updatedBusqueda(): void
    {
        $this->resetPage();
    }

    public function updatedTipo(): void
    {
        $this->resetPage();
    }


    public function updatedFoco(): void
    {
        $this->resetPage();
    }

    /* ======================================================================
       Sobre qué se está calculando
       ====================================================================== */

    /**
     * La cobertura: cuántos entornos envían inventario de los que hay.
     *
     * **Va primero en la pantalla porque cambia cómo se leen todas las demás cifras.** Un
     * «0 con actualización» no significa «el parque está al día» si 18 de 21 sitios no han
     * dicho nada.
     *
     * @return array{envian:int, total:int, sin:int, parcial:bool, vacio:bool}
     */

    public function tipos(): array
    {
        return $this->agregado()
            ->groupBy('type')
            ->map(fn (Collection $filas, string $tipo) => [
                'tipo' => $tipo,
                'cuantos' => $filas->count(),
            ])
            ->sortByDesc('cuantos')
            ->values()
            ->all();
    }

    /**
     * Sugerencias para el buscador.
     *
     * Con 470 componentes, un campo de texto libre es escribir a ciegas: o aciertas el
     * identificador exacto o no encuentras nada. En el listado de Entornos esto ya se
     * resolvió con sugerencias; aquí no las había.
     *
     * @return array<int, array<string, mixed>>
     */
    public function sugerencias(): array
    {
        $termino = trim($this->busqueda);

        if (mb_strlen($termino) < 2) {
            return [];
        }

        $encaja = $this->agregado()->filter(
            fn (object $f) => str_contains(mb_strtolower($f->component), mb_strtolower($termino))
                || str_contains(mb_strtolower((string) $f->name), mb_strtolower($termino))
        );

        // Si el filtro ya deja pocos, la lista de abajo ya es la respuesta y las
        // sugerencias solo estorbarían.
        if ($encaja->count() <= 1 || $encaja->count() > 40) {
            return [];
        }

        return $encaja
            // **Fuera la que ya está elegida.** Pulsarla no hace nada, porque `busqueda` ya
            // vale eso, y un botón que no puede hacer nada se lee como un botón roto.
            ->reject(fn (object $f) => mb_strtolower($f->component) === mb_strtolower($termino))
            ->sortBy('component')
            ->take(8)
            ->map(fn (object $f) => [
                'component' => $f->component,
                'name' => $f->name ?: $f->component,
                'entornos' => (int) $f->entornos,
            ])
            ->values()
            ->all();
    }

    /**
     * El inventario filtrado y paginado.
     *
     * Ordenado por lo que importa y no alfabéticamente: los que conviven con versiones
     * distintas primero, luego los que tienen actualización, y después por presencia en el
     * parque. Con 470 filas y 5 interesantes, el orden alfabético escondía justo lo que se
     * venía a buscar.
     */

    public function filas(): Collection
    {
        $termino = mb_strtolower(trim($this->busqueda));
        $nuestros = $this->nuestrosComponents();
        $nombres = $this->nombresDeCatalogo();

        return $this->agregado()
            ->when($termino !== '', fn (Collection $c) => $c->filter(
                fn (object $f) => str_contains(mb_strtolower($f->component), $termino)
                    || str_contains(mb_strtolower((string) $f->name), $termino)
            ))
            ->when($this->tipo !== '', fn (Collection $c) => $c->where('type', $this->tipo))
            ->when($this->foco === 'nuestros', fn (Collection $c) => $c->whereIn('component', $nuestros))
            ->when($this->foco === 'actualizables', fn (Collection $c) => $c->where('actualizables', '>', 0))
            ->when($this->foco === 'divergentes', fn (Collection $c) => $c->where('versiones', '>', 1))
            // Para los nuestros, el nombre del catálogo: si no, el mismo plugin saldría
            // con un nombre en el bloque de arriba y con otro aquí.
            ->map(fn (object $f) => [
                'component' => $f->component,
                'name' => $nombres[$f->component] ?? ($f->name ?: $f->component),
                'type' => $f->type,
                'entornos' => (int) $f->entornos,
                'versiones' => (int) $f->versiones,
                'actualizables' => (int) $f->actualizables,
                'nuestro' => in_array($f->component, $nuestros, true),
            ])
            // El orden pone delante lo accionable, y **la divergencia solo es accionable en
            // los nuestros**: un plugin que viene con Moodle —`mod_forum`, `mod_quiz`—
            // tiene por definición versiones distintas si los sitios tienen versiones de
            // Moodle distintas, porque su versión es la del core. Ordenar por divergencia
            // a secas llenaría la cabecera de plugins de core y escondería justo lo que se
            // viene a buscar.
            ->sortBy(fn (array $f) => [
                $f['nuestro'] && $f['versiones'] > 1 ? 0 : 1,
                $f['actualizables'] > 0 ? 0 : 1,
                $f['versiones'] > 1 ? 0 : 1,
                -$f['entornos'],
                $f['name'],
            ])
            ->values();
    }

    /**
     * Despliega un componente para ver en qué entornos está y con qué versión.
     *
     * **Con `authorize()`.** El permiso de la ruta no se vuelve a aplicar en
     * `/livewire/update` (MGR-005), y este método lee el inventario de un `component` que
     * llega del cliente: sin la comprobación, cualquiera con sesión podría consultarlo.
     * Antes eran dos mutadores —`openEnvironmentsModal` y `closeEnvironmentsModal`— y
     * ninguno la tenía.
     */

    public function limpiarFiltros(): void
    {
        parent::limpiarFiltros();

        $this->tipo = '';
        $this->foco = '';
        $this->abierto = null;
        $this->resetPage();
    }

    public function render()
    {
        $cobertura = $this->cobertura();
        $deLaTabla = [];

        if (! $cobertura['vacio']) {
            $filas = $this->filas();
            $porPagina = 30;
            $pagina = max(1, (int) $this->getPage());

            $deLaTabla = [
                'filas' => $filas->forPage($pagina, $porPagina),
                'total' => $filas->count(),
                'pagina' => $pagina,
                'paginas' => (int) ceil($filas->count() / $porPagina),
                'tipos' => $this->tipos(),
                'sugerencias' => $this->sugerencias(),
            ];
        }

        return view('livewire.plugins.inventario', array_merge($this->comunes(), [
            'resumen' => $this->resumen(),
            'detalle' => $this->detalleAbierto(),
            'hayFiltro' => $this->busqueda !== '' || $this->tipo !== '' || $this->foco !== ''
                || $this->incluirApagados,
        ], $deLaTabla))->layout('layouts.app');
    }
}
