<?php

namespace App\Support;

use App\Models\Products\Product;
use Illuminate\Database\Eloquent\Model;

/**
 * Qué pasa si se publica una versión de contenido.
 *
 * **Publicar no es «guardar»: es un despliegue.** En cuanto una versión pasa a publicada, la
 * API empieza a servirla, y se la sirve a todos los entornos cuyo plugin la alcance —sin que
 * ellos pidan nada ni se enteren de que ha cambiado—. Los que venían recibiendo una versión
 * anterior **dejan de recibirla en su siguiente petición**.
 *
 * Así que la pregunta antes de publicar no es «¿seguro?», sino **a quién le va a cambiar el
 * contenido y desde qué versión**. Es el espejo exacto de {@see ImpactoDeBorrarVersion}: allí
 * se calcula quién deja de recibirla y a qué cae; aquí, quién pasa a recibirla y de dónde
 * viene.
 *
 * **Y el caso que no se ve de otra forma: que no la alcance nadie.** Si el número de versión
 * es más alto que el plugin instalado en todos los sitios, se publica y no le llega a ninguno.
 * No falla nada, no avisa nada, y la pantalla la enseña publicada igual que a las demás. El
 * contenido se queda ahí sin que nadie lo reciba hasta que los clientes actualicen su plugin,
 * que puede ser nunca. Esto es lo único que lo dice.
 */
class ImpactoDePublicarVersion
{
    /**
     * @param  Model  $version  la versión que se quiere publicar
     * @param  class-string<Model>  $modelo  su clase, para buscar las hermanas
     * @return array{
     *     afectados: array<int, array{entorno:mixed, instalada:string, veniaDe:?string}>,
     *     noLaRecibeNadie: bool,
     *     sinContenidoAntes: int,
     *     entornosConPlugin: int,
     *     eraLaPrimera: bool
     * }
     */
    public static function de(Product $producto, Model $version, string $modelo): array
    {
        $estaVersion = (string) $version->version;

        // Lo que se sirve **ahora**: solo las publicadas, que son las que la API entrega.
        $antes = $modelo::where('product_id', $producto->id)
            ->active()
            ->pluck('version')
            ->map(fn ($v) => (string) $v)
            ->filter()
            ->reject(fn (string $v) => $v === $estaVersion)
            ->values();

        // Y lo que se serviría con esta dentro. Con `merge` y no con `push`: `push` muta la
        // colección, así que `$antes` se quedaba con la versión metida y todo el cálculo
        // comparaba el estado nuevo consigo mismo.
        $despues = $antes->merge([$estaVersion])->unique()->values();

        $afectados = [];
        $sinContenidoAntes = 0;
        $entornosConPlugin = 0;

        foreach (RepartoDeVersiones::entornosDe($producto) as $cliente) {
            $instalada = $cliente['instalada'];

            // Sin el plugin instalado no pide contenido de este producto: publicar no le
            // cambia nada y meterlo en la lista solo la llenaría de ruido.
            if ($instalada === null || $instalada === '') {
                continue;
            }

            $entornosConPlugin++;

            // ¿Le tocaría **esta**? Si su plugin no la alcanza, o hay otra publicada más
            // alta que también alcanza, seguirá recibiendo lo mismo que hoy.
            if (RepartoDeVersiones::queRecibe($despues, (string) $instalada) !== $estaVersion) {
                continue;
            }

            $veniaDe = RepartoDeVersiones::queRecibe($antes, (string) $instalada);

            if ($veniaDe === null) {
                $sinContenidoAntes++;
            }

            $afectados[] = [
                'entorno' => $cliente['entorno'],
                'instalada' => (string) $instalada,
                // `null` aquí es la buena noticia: no recibía nada de este tipo y pasa a
                // recibir algo.
                'veniaDe' => $veniaDe,
            ];
        }

        // Primero los que no tenían nada: publicar les arregla algo, y es lo que se quiere
        // ver arriba. Después, por nombre.
        usort($afectados, function (array $a, array $b) {
            return [$a['veniaDe'] === null ? 0 : 1, $a['entorno']->name]
                <=> [$b['veniaDe'] === null ? 0 : 1, $b['entorno']->name];
        });

        return [
            'afectados' => $afectados,
            // **El aviso que no da ninguna otra pantalla**: publicada y sin destinatarios.
            'noLaRecibeNadie' => $afectados === [],
            'sinContenidoAntes' => $sinContenidoAntes,
            'entornosConPlugin' => $entornosConPlugin,
            // Si no había ninguna publicada, hasta ahora la API respondía «sin contenido».
            'eraLaPrimera' => $antes->isEmpty(),
        ];
    }
}
