<?php

namespace App\Livewire\Traits;

use App\Models\Products\Product;
use App\Support\RepartoDeVersiones;
use Illuminate\Support\Collection;

/**
 * Avisa de lo que pasa al cambiar el número de una versión ya publicada.
 *
 * **Es la acción con la consecuencia más grande y más invisible de estas pantallas.** Los
 * formularios de editar una versión dejan cambiar su número, y ese número es lo único que
 * decide a quién le llega el contenido: la API sirve la versión más alta que no supere el
 * plugin instalado en cada sitio.
 *
 * Así que bajar `2026080702` a `2020010100` puede:
 *
 * - **dejar de servirla** a quien la estaba recibiendo, porque otra versión más alta pasa a
 *   taparla;
 * - **dársela a quien no le tocaba**, si su plugin ahora la alcanza;
 * - o **dejar a un cliente sin nada**, si era la única que alcanzaba y ahora ninguna lo
 *   hace: entonces la API le devuelve un error.
 *
 * Nada de eso falla, nada avisa, y desde el formulario no se veía. Lo único que se pedía
 * era el formato y que no estuviera repetida.
 */
trait AvisaDelCambioDeVersion
{
    /**
     * Qué cambiaría si se guarda el número nuevo, o null si no hay nada que decir.
     *
     * Se calcula **con el número que hay escrito ahora mismo** en el campo, no al guardar:
     * el aviso sirve para no equivocarse, y después de guardar ya no sirve de nada.
     *
     * @param  class-string  $modelo  el modelo de versión de este tipo de contenido
     * @param  string  $original  el número que tiene guardado
     * @param  string  $nueva  el que hay escrito en el formulario
     * @return array{clientes:int, pierden:int, ganan:int, sinNada:int, grave:bool}|null
     */
    protected function consecuenciaDeCambiarLaVersion(
        Product $producto,
        string $modelo,
        string $original,
        string $nueva
    ): ?array {
        $nueva = trim($nueva);

        // Sin cambio, o con un número a medio escribir, no hay nada que calcular: avisar
        // de algo distinto en cada tecla es ruido.
        if ($nueva === '' || $nueva === $original || ! preg_match('/^\d{10}$/', $nueva)) {
            return null;
        }

        // **Solo las que se sirven**: el aviso cuenta a cuántos clientes les cambia el
        // contenido, y una versión en borrador o retirada no le llega a ninguno (MGR-023).
        $todas = $modelo::where('product_id', $producto->id)->active()->pluck('version');

        // El reparto de ahora y el que habría con el número nuevo, sobre los mismos
        // clientes: la diferencia entre los dos es lo que hay que contar.
        $antes = $this->quienRecibeQue($producto, $todas);
        $despues = $this->quienRecibeQue(
            $producto,
            $todas->map(fn ($v) => (string) $v === $original ? $nueva : (string) $v)
        );

        $pierden = 0;
        $ganan = 0;
        $sinNada = 0;

        foreach ($antes as $entornoId => $recibiaAntes) {
            $recibeDespues = $despues[$entornoId] ?? null;

            // **La comparación es por identidad, no por número.** Al renombrar la versión
            // su número cambia, así que un cliente que antes recibía `2026080702` y después
            // recibe `2020010100` puede estar recibiendo **exactamente el mismo
            // contenido**: es la misma versión con otro nombre. Comparar los números a
            // secas contaba eso como una pérdida, y el aviso mentía en el caso más común
            // —corregir el número de una versión sin querer cambiar de manos—.
            $recibiaLaEditada = $recibiaAntes === $original;
            $recibeLaEditada = $recibeDespues === $nueva;

            if ($recibiaLaEditada === $recibeLaEditada && ($recibiaLaEditada || $recibiaAntes === $recibeDespues)) {
                continue;
            }

            if ($recibeDespues === null) {
                // Se queda sin ninguna versión compatible: la API le devolverá un error.
                $sinNada++;

                continue;
            }

            if ($recibiaLaEditada) {
                // La recibía y ahora le llega otra: otra versión ha pasado a taparla.
                $pierden++;
            } elseif ($recibeLaEditada) {
                // No le tocaba y ahora sí: pasa a recibir contenido que antes no veía.
                $ganan++;
            }
        }

        if ($pierden === 0 && $ganan === 0 && $sinNada === 0) {
            return null;
        }

        return [
            'entornos' => count($antes),
            'pierden' => $pierden,
            'ganan' => $ganan,
            'sinNada' => $sinNada,
            // Grave es dejar a alguien sin contenido o quitárselo a quien lo tenía: dar
            // contenido nuevo a alguien no rompe nada.
            'grave' => $sinNada > 0 || $pierden > 0,
        ];
    }

    /**
     * Qué versión recibe cada entorno, indexado por su id.
     *
     * @param  Collection<int, string>  $versiones
     * @return array<int, ?string>
     */
    private function quienRecibeQue(Product $producto, Collection $versiones): array
    {
        $reparto = [];

        foreach (RepartoDeVersiones::entornosDe($producto) as $cliente) {
            // Los que no tienen el plugin no piden nada: no entran en la cuenta, o el
            // aviso diría que cambia algo para 19 clientes a los que no les llega nada.
            if ($cliente['instalada'] === null || $cliente['instalada'] === '') {
                continue;
            }

            $reparto[$cliente['entorno']->id] = RepartoDeVersiones::queRecibe(
                $versiones,
                $cliente['instalada']
            );
        }

        return $reparto;
    }
}
