<?php

namespace App\Livewire\Traits;

use App\Support\PeticionesDeContenido;
use App\Support\RepartoDeVersiones;
use App\Support\TiposDeContenido;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

/**
 * La pantalla de una versión con elementos ordenables: Funcionalidades, Recursos y
 * Tutoriales.
 *
 * Los tres componentes tenían **los mismos seis métodos** —`mount`, `delete`, `moveUp`,
 * `moveDown`, `duplicateVersion`, `render`— y las tres vistas la misma estructura; lo único
 * que cambia son las columnas propias de cada elemento, que van en su propio parcial.
 *
 * **Y el orden estaba roto.** `moveUp`/`moveDown` intercambiaban el `sort_order` de dos
 * elementos, y con los valores a `NULL` —que es como están los reales: la única
 * funcionalidad que existe tiene `sort_order` a null— intercambiar null por null no hace
 * nada. La pantalla decía «Orden actualizado correctamente» y la lista se quedaba igual.
 *
 * Aquí el orden **se normaliza**: se reasigna 1..N en la posición nueva. Así funciona con
 * nulls, con huecos y con empates, y de paso deja el orden explícito —que es el que la API
 * usa para servir los elementos al Moodle del cliente—.
 */
trait MuestraVersionDeElementos
{
    use BorraVersionesDeContenido;

    /**
     * Quién es esta versión y con qué se trabaja.
     *
     * @return array<string, mixed>
     */
    abstract protected function fichaDeLaVersion(): array;

    /** La consulta de los elementos de esta versión. */
    abstract protected function elementosDeLaVersion();

    /**
     * Los elementos en el orden en que los sirve la API.
     *
     * Se usa el scope `ordered()` del modelo, que es **el mismo que usan las Actions de la
     * API**: `sort_order` ascendente con los nulos al final y, entre los que empatan, el
     * más nuevo primero. Ese scope estaba mal —ponía los nulos primero— y se arregló al
     * hacer esta pantalla; ver su docblock.
     *
     * @return Collection<int, Model>
     */
    protected function elementos(): Collection
    {
        return $this->elementosDeLaVersion()
            ->with('creator:id,name')
            // **El mismo scope que usa la API**, no una copia: si la pantalla ordenara por
            // su cuenta, enseñaría un orden y el Moodle del cliente recibiría otro. El
            // scope está en el modelo y lo usan las tres Actions.
            ->ordered()
            ->get();
    }

    /**
     * Mueve un elemento una posición arriba o abajo.
     *
     * **Reasigna el orden completo**, no intercambia dos valores: ver el docblock de la
     * clase. Devuelve si algo cambió, para no decir «orden actualizado» cuando el elemento
     * ya estaba en el extremo.
     */
    protected function moverElemento(int $elementoId, int $direccion): bool
    {
        $ficha = $this->fichaDeLaVersion();
        $ordenados = $this->elementos();

        $posicion = $ordenados->search(fn (Model $e) => $e->id === $elementoId);

        if ($posicion === false) {
            return false;
        }

        $destino = $posicion + $direccion;

        // Ya está arriba del todo o abajo del todo: no hay nada que hacer, y decir que sí
        // sería mentir.
        if ($destino < 0 || $destino >= $ordenados->count()) {
            return false;
        }

        $lista = $ordenados->values()->all();
        [$lista[$posicion], $lista[$destino]] = [$lista[$destino], $lista[$posicion]];

        // El orden se escribe entero y en una transacción: a medias dejaría la lista con
        // dos elementos en la misma posición, y de ahí no se sale sola.
        DB::transaction(function () use ($lista, $ficha) {
            foreach ($lista as $indice => $elemento) {
                $elemento->timestamps = false;
                $elemento->update(['sort_order' => $indice + 1]);
            }
        });

        return true;
    }

    /**
     * El mensaje del movimiento, solo si movió algo.
     *
     * **Antes decía «Orden actualizado correctamente» siempre**, incluso cuando el
     * elemento ya estaba en el extremo o cuando el intercambio de nulls no hacía nada. Un
     * mensaje de éxito que aparece sin que pase nada enseña a no leer los mensajes.
     */
    protected function avisarDelOrden(bool $cambio): void
    {
        if (! $cambio) {
            return;
        }

        session()->flash('success', 'Orden actualizado. Es el orden en que los verá quien use la plataforma.');
    }

    /** A qué entornos les llega esta versión. Ver `MuestraVersionDeArchivos::aQuienLlega()`. */
    protected function aQuienLlega(): array
    {
        $ficha = $this->fichaDeLaVersion();
        $modelo = $ficha['version']::class;

        $reparto = RepartoDeVersiones::reparto(
            $this->product,
            // **Solo las que se sirven.** Una versión en borrador o retirada no sale por la
            // API, así que contarla aquí diría que le está llegando a alguien: justo lo
            // contrario de lo que significa no publicarla (MGR-023).
            $modelo::where('product_id', $this->product->id)->active()->pluck('version')
        );

        return [
            'laReciben' => $reparto['porVersion'][(string) $ficha['version']->version] ?? 0,
            'entornos' => $reparto['entornos'],
            'conPlugin' => $reparto['entornos'] - $reparto['sinPlugin'],
        ];
    }

    protected function datosDeLaPantalla(): array
    {
        $ficha = $this->fichaDeLaVersion();
        $tipo = TiposDeContenido::TIPOS[$ficha['clave']] ?? [];
        [$fondo, $tinta] = TiposDeContenido::tonoDe($ficha['clave']);

        $elementos = $this->elementos();

        // **Cuántos salen de verdad por la API.** Es la diferencia que no se veía en
        // ninguna de las tres pantallas: solo los `published` se sirven, así que «4
        // funcionalidades» puede ser «2 que llegan», y una versión con todo en borrador se
        // ve igual que una publicada.
        $publicados = $elementos->where('status', 'published');

        return [
            'product' => $this->product,
            'ficha' => array_merge($tipo, $ficha, ['fondo' => $fondo, 'tinta' => $tinta]),
            'elementos' => $elementos,
            'publicados' => $publicados->count(),
            'borradores' => $elementos->where('status', 'draft')->count(),
            'archivados' => $elementos->where('status', 'archived')->count(),
            // Cuántos elementos no tienen orden puesto: van al final sin que nadie lo haya
            // decidido, y es el estado real de los que existen.
            'sinOrden' => $elementos->whereNull('sort_order')->count(),
            'llega' => $this->aQuienLlega(),
            'peticiones' => PeticionesDeContenido::de($this->product, $tipo['accion'] ?? ''),
        ];
    }
}
