<?php

namespace App\Livewire\Traits;

use App\Models\Products\Product;
use App\Support\PeticionesDeContenido;
use App\Support\EstadoDePublicacion;
use App\Support\TiposDeContenido;
use App\Support\RepartoDeVersiones;
use Illuminate\Database\Eloquent\Model;
use Livewire\WithPagination;

trait HasVersionIndex
{
    use WithPagination;
    use BorraVersionesDeContenido;

    public Product $product;
    public string $search = '';
    public ?string $versionFilter = null;

    protected $queryString = ['search', 'versionFilter'];

    /**
     * Obtiene el modelo de versión a usar (ej: JsVersion::class)
     */
    abstract protected function getVersionModel(): string;

    /**
     * Obtiene el nombre de la relación para contar (ej: 'files', 'resources', 'features')
     */
    abstract protected function getCountRelation(): string;

    /**
     * Obtiene el mensaje de éxito al crear una nueva versión
     */
    abstract protected function getCreateSuccessMessage(string $version): string;

    /**
     * Obtiene la ruta para mostrar la versión creada
     */
    abstract protected function getVersionShowRoute(Product $product, Model $version): string;

    /**
     * Obtiene el nombre del parámetro para el método duplicate (ej: 'jsVersionId', 'scssVersionId')
     */
    abstract protected function getDuplicateParameterName(): string;

    /**
     * Permiso que hace falta para crear o duplicar una versión de este tipo de
     * contenido (ej: `admin.js.version.create`).
     *
     * Es abstracto a propósito: el trait no puede saber de qué contenido se trata, y
     * poner un permiso genérico aquí sería peor que no ponerlo —autorizaría seis
     * pantallas con un permiso que no le corresponde a ninguna—. Lo declara cada
     * componente, con el mismo permiso que su ruta.
     *
     * Ver known-issues MGR-005.
     */
    abstract protected function getVersionWritePermission(): string;

    /**
     * Quién es esta pantalla: los textos y las rutas que la distinguen de las otras seis.
     *
     * **Es lo que permite que haya una sola vista.** Había siete copias del mismo listado
     * —145 líneas idénticas cada una, palabra por palabra salvo el título, el nombre de la
     * variable y la palabra «archivo»—, así que cualquier arreglo había que hacerlo siete
     * veces y dejaba de cuadrar en la primera que se olvidara.
     *
     * `explicacion` no es decorativa: **cinco de los siete tipos están vacíos** en la base
     * real, así que el estado normal de estas pantallas es no tener nada, y lo que hace
     * falta ahí es saber qué se pone y qué hace con ello el plugin del cliente.
     *
     * @return array{
     *     clave: string,
     *     elemento: string,
     *     elementos: string,
     *     explicacion: string,
     *     rutaVer: string,
     *     rutaEditar: string
     * }
     */
    abstract protected function ficha(): array;

    /**
     * Qué versión está recibiendo cada cliente de este producto.
     *
     * **Es la pregunta que el listado no respondía.** Enseñaba «versión 2026080702 · 1
     * archivo · creada por Antonio» y con eso no se sabe si le ha llegado a alguien: la
     * API no sirve «la última», sirve la más alta que sea ≤ la versión del plugin
     * instalado en cada sitio. Ver `RepartoDeVersiones`.
     *
     * Se calcula sobre **todas** las versiones del producto y no sobre la página que se
     * está viendo: el reparto depende de las que existen, no de las que caben en pantalla.
     */
    protected function reparto(): array
    {
        $modelo = $this->getVersionModel();

        return 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')
        );
    }

    /**
     * El render de las seis pantallas.
     *
     * Vive en el trait porque era idéntico en las seis salvo el nombre de la variable que
     * se le pasaba a la vista —`$scssVersions`, `$jsVersions`…—, y ese nombre era justo lo
     * que obligaba a tener seis vistas.
     */
    /**
     * La ficha completa: lo que declara el componente **más** la identidad del tipo.
     *
     * El nombre, la inicial, el color, la acción de la API y el código de error salen de
     * `TiposDeContenido::TIPOS`, que es el único sitio donde viven: el selector de arriba
     * los necesita para los siete a la vez, y tenerlos también aquí los dejaría
     * divergiendo en el primer cambio.
     *
     * @return array<string, mixed>
     */
    protected function fichaCompleta(): array
    {
        $ficha = $this->ficha();
        $tipo = TiposDeContenido::TIPOS[$ficha['clave']] ?? [];
        [$fondo, $tinta] = TiposDeContenido::tonoDe($ficha['clave']);

        return array_merge($tipo, $ficha, [
            'fondo' => $fondo,
            'tinta' => $tinta,
        ]);
    }

    /**
     * Abre la modal del cambio de estado.
     *
     * **Publicar no pasa por aquí**: añade una opción y nadie pierde contenido, así que se
     * aplica directo. Sacar de circulación sí, porque le cambia lo que recibe a quien la
     * tuviera y eso no puede pasar de un clic sin ver a cuántos y a qué pasan.
     */
    public function pedirCambioDeEstado(int $versionId, string $estado): void
    {
        $this->authorize($this->getVersionWritePermission());

        $this->dispatch('openModal',
            component: \App\Livewire\Products\CambioDeEstadoModal::class,
            arguments: [
                'tipo' => $this->ficha()['clave'],
                'productId' => $this->product->id,
                'versionId' => $versionId,
                'estado' => $estado,
            ]
        );
    }

    /**
     * La modal avisa al aplicar, y con eso el listado se repinta con el estado nuevo.
     *
     * El cuerpo está vacío a propósito: el estado se lee de la base en cada `render()`, y
     * recibir el evento ya provoca ese render. No se llama a `resetPage()` porque sacaría
     * al usuario de la página que estaba mirando.
     */
    #[\Livewire\Attributes\On('versionCambioDeEstado')]
    public function repintarTrasElCambio(): void
    {
        //
    }


    public function render()
    {
        $ficha = $this->fichaCompleta();
        $modelo = $this->getVersionModel();

        // Lo que ha pasado de verdad, no lo que debería pasar: quién ha llamado, con qué
        // versión y si le fue bien. Ver `PeticionesDeContenido`.
        $peticiones = PeticionesDeContenido::de($this->product, $ficha['accion']);

        return view('livewire.products.contenido.versiones', [
            'product' => $this->product,
            'ficha' => $ficha,
            'versiones' => $this->getVersionsQuery()->paginate(20),
            'reparto' => $this->reparto(),
            'peticiones' => $peticiones,
            // **Una fila por cliente**: qué plugin tiene, qué le corresponde y qué ha
            // pedido. Es la tabla que junta el cálculo con el registro de la API.
            'porEntorno' => RepartoDeVersiones::porEntorno(
                $this->product,
                // Solo las servibles, por lo mismo que en `reparto()`.
                $modelo::where('product_id', $this->product->id)->active()->pluck('version'),
                $peticiones,
                // El mapa número => id: sin él la tabla enseña el número de la versión que
                // le corresponde a cada entorno y hay que buscarla a mano en el listado.
                $modelo::where('product_id', $this->product->id)->pluck('id', 'version')->all()
            ),
            // El selector de los siete tipos: antes cada uno era una URL suelta y para ir
            // de los SCSS a las Funcionalidades había que volver a la ficha del producto.
            'tipos' => TiposDeContenido::paraElSelector($this->product, $ficha['clave']),
            'puedeEscribir' => auth()->user()?->can($this->getVersionWritePermission()) ?? false,
            'elementos' => $this->getCountRelation() . '_count',
        ])->layout('layouts.app');
    }

    public function updatingSearch()
    {
        $this->resetPage();
    }

    public function updatingVersionFilter()
    {
        $this->resetPage();
    }

    public function mount(Product $product)
    {
        $this->product = $product;
    }

    /**
     * Abre la modal de duplicar.
     *
     * **El número de la versión nueva se decide ahí.** Se generaba solo sumando uno y no se
     * veía hasta después de crearla, y el número es justo lo que determina a qué sitios les
     * llegará: la API sirve la más alta que no supere el plugin instalado en cada uno.
     */
    public function pedirDuplicar(int $versionId): void
    {
        $this->authorize($this->getVersionWritePermission());

        $this->dispatch('openModal',
            component: \App\Livewire\Products\DuplicarVersionModal::class,
            arguments: [
                'tipo' => $this->ficha()['clave'],
                'productId' => $this->product->id,
                'versionId' => $versionId,
            ]
        );
    }

    /** La modal avisa al crear, y con eso el listado se repinta con la versión nueva. */
    #[\Livewire\Attributes\On('versionDuplicada')]
    public function repintarTrasDuplicar(): void
    {
        $this->resetPage();
    }

    public function duplicate($versionId)
    {
        $this->authorize($this->getVersionWritePermission());

        try {
            $modelClass = $this->getVersionModel();
            $relation = $this->getCountRelation();

            // Acotado al producto de la pantalla. Con `findOrFail($versionId)` a secas
            // —como estaba— el id llegaba del navegador sin comprobar de quién era:
            // se podía duplicar la versión de contenido de OTRO producto, y el
            // duplicado se creaba dentro de ese otro producto. Es el mismo IDOR que
            // MGR-006 en Setups.
            $version = $modelClass::with($relation)
                ->where('product_id', $this->product->id)
                ->where('id', $versionId)
                ->firstOrFail();

            $newVersion = $version->duplicate();
            
            session()->flash('success', "Versión duplicada correctamente. Nueva versión: {$newVersion->version}");
        } catch (\Exception $e) {
            session()->flash('error', 'Error al duplicar la versión: ' . $e->getMessage());
        }
    }

    public function createNewVersion()
    {
        $this->authorize($this->getVersionWritePermission());

        try {
            $modelClass = $this->getVersionModel();
            
            // Buscar la última versión del producto
            $lastVersion = $modelClass::where('product_id', $this->product->id)
                ->orderByDesc('version')
                ->first();

            // Generar el número de versión
            if ($lastVersion) {
                // Si existe una versión previa, generar la siguiente
                $newVersionNumber = $lastVersion->generateNextVersion();
            } else {
                // Si no hay versiones, crear la primera con la fecha actual
                $newVersionNumber = date('Ymd') . '00';
                
                // Verificar que no exista ya esta versión
                $exists = $modelClass::where('product_id', $this->product->id)
                    ->where('version', $newVersionNumber)
                    ->exists();
                
                if ($exists) {
                    // Si existe, incrementar
                    $newVersionNumber = str_pad((string) ((int) $newVersionNumber + 1), 10, '0', STR_PAD_LEFT);
                }
            }

            // Crear la nueva versión, **en borrador** (MGR-023).
            //
            // Antes no había estado y la versión se servía en cuanto se creaba: preparar la
            // documentación de un producto lleva días y varias manos, así que la única forma
            // de no publicar a medias era no crear la versión, o crearla con un número más
            // alto que ninguna instalación tuviera aún. Un truco, no un mecanismo.
            //
            // Nace en borrador y no publicada porque de los dos errores posibles este es el
            // que se ve: la pantalla pone «Borrador — no se sirve» y ofrece publicar.
            // Publicar a medias no se ve desde aquí — lo descubre el cliente.
            $newVersion = $modelClass::create([
                'product_id' => $this->product->id,
                'version' => $newVersionNumber,
                'status' => EstadoDePublicacion::BORRADOR,
                'description' => null,
                'created_by' => auth()->id(),
                'updated_by' => auth()->id(),
            ]);

            session()->flash('success', $this->getCreateSuccessMessage($newVersion->version)
                . ' Nace como **borrador**: no se sirve a nadie hasta que la publiques.');
            
            // Redirigir a la página de la versión
            return $this->redirect($this->getVersionShowRoute($this->product, $newVersion), navigate: true);
        } catch (\Exception $e) {
            session()->flash('error', 'Error al crear la versión: ' . $e->getMessage());
        }
    }

    protected function getVersionsQuery()
    {
        $modelClass = $this->getVersionModel();
        $relation = $this->getCountRelation();
        
        return $modelClass::query()
            ->with(['product', 'creator', 'updater'])
            ->withCount($relation)
            ->where('product_id', $this->product->id)
            ->when($this->search, function ($query) {
                $query->where(function ($q) {
                    $q->where('version', 'like', "%{$this->search}%")
                      ->orWhere('description', 'like', "%{$this->search}%");
                });
            })
            ->when($this->versionFilter, function ($query) {
                $query->where('version', $this->versionFilter);
            })
            ->orderByDesc('version');
    }
}

