<?php

namespace App\Livewire\Products;

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

/**
 * Duplicar una versión de contenido para preparar la siguiente.
 *
 * **Antes era un `confirm()` del navegador** que decía «se creará una versión nueva con una
 * copia de todo el contenido. ¿Seguir?». Con eso no se puede decidir nada, porque lo callaba
 * justo lo que decide:
 *
 * 1. **Qué número va a llevar.** El número no es una etiqueta: la API sirve la versión más
 *    alta que no supere el plugin instalado en cada sitio, así que el número es *lo que
 *    determina a quién le llegará*. Se generaba solo, sumando uno, y no se veía hasta
 *    después. Aquí se ve y **se puede cambiar**.
 * 2. **A quién le llegaría con ese número.** Se calcula en cuanto se escribe: si el número
 *    está por encima del plugin de todos los sitios, la versión no le llegará a nadie cuando
 *    se publique. Eso no falla ni avisa en ninguna pantalla; aquí se dice antes.
 * 3. **Que nace sin publicar.** Desde MGR-023 el duplicado es un borrador —se duplica para
 *    preparar la siguiente, no para soltarla a medias—, así que copiar no le cambia nada a
 *    ningún cliente. Es una tranquilidad que hay que decir, porque «duplicar en producción»
 *    asusta con razón cuando no se sabe.
 */
class DuplicarVersionModal extends ModalComponent
{
    /** La clave del tipo: `setups`, `scss`, `js`, `scss-cdn`, `features`… */
    public string $tipo;

    public Product $product;

    public int $versionId;

    /** El número de la versión nueva. Se propone el siguiente y se puede cambiar. */
    public string $numero = '';

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

        // El permiso de la ruta no se reaplica en /livewire/update (MGR-005), y esto crea
        // una fila nueva.
        $this->authorize(TiposDeContenido::TIPOS[$tipo]['permisoEscritura']);

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

        // Acotado al producto **en la consulta** y no comparando después: sin esto se podía
        // duplicar la versión de otro producto, y el duplicado se creaba dentro de ese otro
        // producto (MGR-006).
        $this->versionId = $this->modelo()::where('product_id', $this->product->id)
            ->findOrFail($versionId)
            ->id;

        $this->numero = $this->version()->generateNextVersion();
    }

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

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

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

    public function comoSeLlama(): string
    {
        return TiposDeContenido::TIPOS[$this->tipo]['nombre'];
    }

    /**
     * Qué se va a copiar, en palabras.
     *
     * Los setups no tienen elementos dentro: su contenido es el propio YAML de la fila.
     */
    public function queSeCopia(): string
    {
        $relacion = TiposDeContenido::TIPOS[$this->tipo]['relacion'];
        [$singular, $plural] = TiposDeContenido::TIPOS[$this->tipo]['contenido'];

        if ($relacion === null) {
            return $singular . ' entero';
        }

        $cuantos = $this->version()->{$relacion}()->count();

        return $cuantos === 0
            ? 'nada: la versión de origen está vacía'
            : $cuantos . ' ' . ($cuantos === 1 ? $singular : $plural);
    }

    /**
     * Con el número escrito, a cuántos entornos les llegaría **cuando se publique**.
     *
     * **Es el dato que justifica que el número se vea.** No se sirve «la última»: se sirve la
     * más alta que no supere el plugin instalado en cada sitio. Un número por encima del
     * plugin de toda la flota se publica sin llegarle a nadie, y eso no lo dice ninguna otra
     * pantalla porque no es un error.
     *
     * @return array{valido: bool, existe: bool, alcanzan: int, conPlugin: int}
     */
    public function alcance(): array
    {
        $valido = (bool) preg_match('/^\d{10}$/', $this->numero);

        $existe = $valido && $this->modelo()::where('product_id', $this->product->id)
            ->where('version', $this->numero)
            ->exists();

        $alcanzan = 0;
        $conPlugin = 0;

        foreach (RepartoDeVersiones::entornosDe($this->product) as $cliente) {
            $instalada = $cliente['instalada'];

            if ($instalada === null || $instalada === '') {
                continue;
            }

            $conPlugin++;

            if ($valido && ! $existe && (string) $instalada >= $this->numero) {
                $alcanzan++;
            }
        }

        return [
            'valido' => $valido,
            'existe' => $existe,
            'alcanzan' => $alcanzan,
            'conPlugin' => $conPlugin,
        ];
    }

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

        $this->validate(
            ['numero' => 'required|regex:/^\d{10}$/'],
            ['numero.regex' => 'El número de versión son diez dígitos: AAAAMMDDNN.']
        );

        $version = $this->version();

        try {
            $nueva = $version->duplicate($this->numero);
        } catch (\RuntimeException $e) {
            // `duplicate()` rechaza un número que ya exista. El mensaje suyo es el bueno.
            $this->addError('numero', $e->getMessage());

            return;
        }

        session()->flash('success', 'Creada la versión ' . $nueva->version . ' de '
            . $this->comoSeLlama() . ' con una copia de la ' . $version->version . '. '
            . 'Está en «' . EstadoDePublicacion::etiqueta(EstadoDePublicacion::BORRADOR)
            . '»: no se sirve a nadie hasta que se publique.');

        $this->closeModalWithEvents(['versionDuplicada']);
    }

    public function render()
    {
        return view('livewire.products.duplicar-version-modal', [
            'version' => $this->version(),
            'alcance' => $this->alcance(),
        ]);
    }
}
