<?php

namespace App\Livewire\Products;

use App\Models\Products\Product;
use App\Support\EstadoDePublicacion;
use App\Support\ImpactoDeBorrarVersion;
use App\Support\ImpactoDePublicarVersion;
use App\Support\TiposDeContenido;
use Illuminate\Database\Eloquent\Model;
use LivewireUI\Modal\ModalComponent;

/**
 * Sacar de circulación una versión de contenido, o volver a publicarla.
 *
 * **Por qué esto no es un «¿seguro?».** Una versión publicada **se está sirviendo a los
 * Moodles de los clientes**: dejar de servirla cambia qué recibe cada uno en su siguiente
 * petición, sin que él haya tocado nada y sin que salte ninguna alarma. Y el caso peor no es
 * el obvio: si no queda ninguna versión que su plugin alcance, la API responde «sin
 * contenido» —que `norma-cero-errores.md` clasifica como **respuesta correcta**—, así que ni
 * el Dashboard ni el visor ni los avisos dicen nada. **El sitio se queda sin contenido en
 * silencio.**
 *
 * De ahí que esta modal no pregunte, sino que **enseñe a qué versión cae cada entorno
 * afectado** y marque en rojo los que se quedan sin ninguna. Ese es el dato con el que se
 * decide, y no lo tiene nadie en la cabeza.
 *
 * ## Una modal para los siete tipos
 *
 * El impacto se calcula con {@see ImpactoDeBorrarVersion}, que ya resolvía **exactamente esta
 * pregunta** para el borrado: quién la recibe ahora y a qué pasaría sin ella. Dejar de
 * servirla y borrarla tienen el mismo efecto sobre lo que reciben los clientes, así que el
 * cálculo es el mismo y no se duplica.
 *
 * Y el modelo y el permiso salen de {@see TiposDeContenido::TIPOS}, que es el único sitio
 * donde viven los siete: un `match` de siete ramas aquí sería la octava copia de esa tabla.
 *
 * ## Antes esto era un `confirm()` del navegador
 *
 * Con el texto entero metido en un atributo `wire:confirm`. Dos problemas: la alerta del
 * navegador **no se parece al resto del panel** —y quien la ve dos veces deja de leerla—, y
 * un texto con condicionales dentro de un atributo obliga a escribir Blade ahí; un `@if`
 * pegado a texto **no lo reconoce el compilador**, se queda literal y su `@endif` cierra el
 * bloque de fuera. Eso llegó a verse en pantalla.
 */
class CambioDeEstadoModal extends ModalComponent
{
    /** La clave del tipo: `setups`, `scss`, `js`, `scss-cdn`, `features`… */
    public string $tipo;

    public Product $product;

    public int $versionId;

    /** A qué estado se va. Los tres de {@see EstadoDePublicacion}. */
    public string $estado;

    public function mount(string $tipo, int $productId, int $versionId, string $estado): void
    {
        abort_unless(isset(TiposDeContenido::TIPOS[$tipo]), 404, 'Tipo de contenido desconocido.');
        abort_unless(EstadoDePublicacion::existe($estado), 404, 'Estado desconocido.');

        // El permiso de la ruta no se reaplica en /livewire/update (MGR-005), y esto cambia
        // qué reciben los Moodles de los clientes.
        $this->authorize(TiposDeContenido::TIPOS[$tipo]['permisoEscritura']);

        $this->tipo = $tipo;
        $this->estado = $estado;
        $this->product = Product::findOrFail($productId);

        // Acotado al producto **en la consulta** y no comparando después: si la versión no es
        // de este producto, es un 404 (MGR-006).
        $this->versionId = $this->modelo()::where('product_id', $this->product->id)
            ->findOrFail($versionId)
            ->id;
    }

    public static function modalMaxWidth(): string
    {
        return '2xl';
    }

    /** @return class-string<Model> */
    private function modelo(): string
    {
        return TiposDeContenido::TIPOS[$this->tipo]['modelo'];
    }

    private function version(): Model
    {
        return $this->modelo()::findOrFail($this->versionId);
    }

    /**
     * Qué pasa si esta versión deja de servirse: quién la recibe y a qué cae.
     *
     * @return array<string, mixed>|null
     */
    public function impacto(): ?array
    {
        if ($this->estado === EstadoDePublicacion::PUBLICADA) {
            return null;
        }

        return ImpactoDeBorrarVersion::de(
            $this->product,
            $this->version(),
            $this->modelo()
        );
    }

    /**
     * Qué pasa si esta versión se publica: quién pasa a recibirla y desde qué versión.
     *
     * **Publicar también es un cambio en producción.** Durante un tiempo esta modal no
     * calculaba nada al publicar, con el argumento de que nadie pierde contenido. Es verdad
     * y es irrelevante: a los entornos que venían recibiendo la versión anterior **les
     * cambia el contenido igual**, en su siguiente petición y sin pedirlo. Y el caso que no
     * se ve en ninguna otra pantalla es el contrario —publicar una versión que **no alcanza
     * a nadie**, que se queda ahí sin llegar y sin que nada falle—.
     *
     * @return array<string, mixed>|null
     */
    public function impactoDePublicar(): ?array
    {
        if ($this->estado !== EstadoDePublicacion::PUBLICADA) {
            return null;
        }

        return ImpactoDePublicarVersion::de(
            $this->product,
            $this->version(),
            $this->modelo()
        );
    }

    /** Cómo se llama esto en la pantalla. */
    public function comoSeLlama(): string
    {
        return TiposDeContenido::TIPOS[$this->tipo]['nombre'];
    }

    /** El código con el que responde la API cuando no queda contenido que servir. */
    public function codigoDeError(): string
    {
        return (string) TiposDeContenido::TIPOS[$this->tipo]['error'];
    }

    public function cambiar(): void
    {
        $this->authorize(TiposDeContenido::TIPOS[$this->tipo]['permisoEscritura']);

        $version = $this->version();

        if ($version->status === $this->estado) {
            $this->closeModal();

            return;
        }

        $cambio = 'Estado cambiado a ' . $this->estado;

        $version->update(array_merge(
            ['status' => $this->estado, 'updated_by' => auth()->id()],
            // El setup guarda en la propia fila el último cambio; los otros seis no
            // tienen la columna. Se mira en los atributos de la fila —que vienen de la
            // base— y no con un `hasColumn`, que sería una consulta al esquema por guardado.
            array_key_exists('change_log', $version->getAttributes()) ? ['change_log' => $cambio] : []
        ));

        // **El histórico del setup no se pierde al pasar por aquí.** Es el único de los
        // siete que guarda una fila con el YAML de cada cambio, y el `toggleStatus` que
        // esta modal sustituye la escribía: si el modelo sabe hacer histórico, se hace.
        if (method_exists($version, 'createHistoryEntry')) {
            $version->createHistoryEntry($cambio);
        }

        session()->flash('success', 'La versión ' . $version->version . ' de '
            . $this->comoSeLlama() . ' pasa a «' . EstadoDePublicacion::etiqueta($this->estado)
            . '». ' . EstadoDePublicacion::consecuencia($this->estado));

        // Clave numérica: el paquete desestructura `[$evento, $params]` cuando el valor es
        // un array, así que `['evento' => []]` revienta (MGR-042).
        $this->closeModalWithEvents(['versionCambioDeEstado']);
    }

    public function render()
    {
        return view('livewire.products.cambio-de-estado-modal', [
            'version' => $this->version(),
            'impacto' => $this->impacto(),
            'llegada' => $this->impactoDePublicar(),
        ]);
    }
}
