<?php

namespace Tests\Feature\Environments;

use App\Livewire\Environments\Plugins\Index;
use App\Models\Auth\User;
use App\Models\Clients\Client;
use App\Models\Environments\Environment;
use App\Models\Environments\Plugin;
use App\Models\Products\Product;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Livewire\Livewire;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;

/**
 * Inventario de plugins de un entorno, desde `Plugins.dc.html`.
 *
 * Lo que se fija: que las pocas filas que requieren algo no se pierdan entre 464, que el
 * desfase de versión diga **cuál de los dos casos es** —porque el arreglo es distinto—, y
 * que la pantalla no afirme más de lo que sabe.
 */
class PluginsInventarioTest extends TestCase
{
    use RefreshDatabase;

    private function admin(): User
    {
        $rol = Role::firstOrCreate(['name' => 'Admin', 'guard_name' => 'web']);

        foreach (['admin.plugins.environment', 'admin.environments.index',
                  'admin.datas.environment', 'admin.clients.index',
                  'admin.products.index', 'admin.products.create',
                  'admin.products.show', 'admin.environments.refresh'] as $nombre) {
            $rol->givePermissionTo(Permission::firstOrCreate(
                ['name' => $nombre, 'guard_name' => 'web'],
                ['model' => 'plugins', 'description' => $nombre]
            ));
        }

        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);

        Cache::flush();

        return $user;
    }

    private function entorno(): Environment
    {
        $cliente = Client::firstOrCreate(
            ['shortname' => 'plg'],
            ['name' => 'Cliente Plugins', 'actived' => true]
        );

        return Environment::create([
            'name' => 'Campus Plugins',
            'domain' => 'https://campus.plg.test',
            'env' => 'pro',
            'version' => '5.1.1',
            'client_id' => $cliente->id,
            'active' => true,
            'refresh_at' => now()->subHour(),
        ]);
    }

    private function plugin(Environment $entorno, array $atributos): Plugin
    {
        return Plugin::create(array_merge([
            'environment_id' => $entorno->id,
            'name' => 'Un plugin',
            'component' => 'mod_algo',
            'type' => 'mod',
            'versiondisk' => '2026010100',
            'versiondb' => '2026010100',
            'release' => '1.0.0',
            // NOT NULL en la tabla: es la versión mínima de Moodle que pide el plugin.
            'versionrequires' => '2025041400',
            'has_updates' => false,
            'has_dependencies' => false,
        ], $atributos));
    }

    /* ===================== Los dos desfases, que no son lo mismo ===================== */

    public function test_los_ficheros_mas_nuevos_que_la_base_de_datos_piden_ejecutar_el_upgrade(): void
    {
        // `versiondisk` > `versiondb`: el plugin se ha subido y el upgrade no ha corrido.
        // Se arregla entrando al sitio o pasando el cron.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'local_tresipunt',
            'name' => '3iPunt',
            'versiondisk' => '2026090200',
            'versiondb' => '2026090100',
        ]);

        $atencion = Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion');

        $this->assertCount(1, $atencion);
        $this->assertSame('Falta el upgrade', $atencion[0]['etiqueta']);
        $this->assertSame('crit', $atencion[0]['nivel']);
        $this->assertStringContainsString('Moodle lo lanza solo', $atencion[0]['texto']);
    }

    public function test_los_ficheros_mas_viejos_que_la_base_de_datos_son_otro_caso_y_otro_arreglo(): void
    {
        // **Este test existe porque los estaba juntando.** `versiondisk` < `versiondb` es
        // una copia restaurada a medias: ficheros viejos sobre una base ya migrada. Moodle
        // **no lanza ningún upgrade** —no sabe bajar—, así que decir «falta el upgrade»
        // manda a pasar el cron, que no arregla nada.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'report_fundae',
            'name' => 'Informe Fundae',
            'versiondisk' => '2026042000',
            'versiondb' => '2026080600',
        ]);

        $atencion = Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion');

        $this->assertCount(1, $atencion);
        $this->assertSame('Ficheros retrocedidos', $atencion[0]['etiqueta']);
        $this->assertSame('crit', $atencion[0]['nivel']);
        $this->assertStringContainsString('el cron no lo arregla', $atencion[0]['texto']);
        // Y no se dice lo contrario.
        $this->assertStringNotContainsString('Moodle lo lanza solo', $atencion[0]['texto']);
    }

    public function test_las_versiones_iguales_no_requieren_nada(): void
    {
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['versiondisk' => '2026010100', 'versiondb' => '2026010100']);

        $componente = Livewire::test(Index::class, ['environment' => $entorno]);

        $this->assertSame([], $componente->viewData('atencion'));
        $componente->assertSee('Ningún plugin requiere nada');
    }

    /* ===================== Lo que la pantalla no afirma ===================== */

    public function test_todo_en_orden_dice_de_donde_sale_esa_tranquilidad(): void
    {
        // «Sin novedades» sale de `has_updates`, que depende de que ese Moodle tenga el
        // comprobador activo y salida a internet. Significa «no nos consta nada nuevo», no
        // «está al día», y con 459 filas en verde la diferencia importa.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'mod_uno']);

        Livewire::test(Index::class, ['environment' => $entorno])
            // Fragmentos de una línea: el HTML mete saltos donde el blade los tiene, y
            // un `assertSee` de una frase que ocupa dos líneas nunca casa.
            ->assertSee('el comprobador de este Moodle no reporta')
            ->assertSee('que no le consta nada nuevo, no que esté al día');
    }

    public function test_la_madurez_que_no_esta_en_la_escala_de_moodle_no_se_traduce(): void
    {
        // Moodle documenta 50/100/150/200. En los datos aparece **300**, que no está en esa
        // lista: inventarle una palabra sería peor que no decir nada, porque de esto
        // depende si alguien instala algo en producción.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'mod_dos',
            'has_updates' => true,
            'availableupdates' => [
                ['release' => '2.0.0', 'maturity' => 200],
                ['release' => '3.0.0', 'maturity' => 300],
            ],
        ]);

        $candidatas = Livewire::test(Index::class, ['environment' => $entorno])
            ->viewData('atencion')[0]['candidatas'];

        $this->assertCount(2, $candidatas);
        $this->assertSame('estable', $candidatas[0]['madurez']);
        $this->assertStringContainsString('sin confirmar', $candidatas[1]['madurez']);
    }

    public function test_una_actualizacion_sin_madurez_no_inventa_ninguna(): void
    {
        // `availableupdates` no tiene forma garantizada: hay entradas sin `maturity`.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'has_updates' => true,
            'availableupdates' => [['release' => '1.6.0']],
        ]);

        $candidatas = Livewire::test(Index::class, ['environment' => $entorno])
            ->viewData('atencion')[0]['candidatas'];

        $this->assertSame('1.6.0', $candidatas[0]['release']);
        $this->assertNull($candidatas[0]['madurez']);
    }

    /* ===================== Las incompatibilidades, cruzadas ===================== */

    public function test_una_incompatibilidad_con_algo_que_no_esta_instalado_no_alarma(): void
    {
        // Pintarla igual que una que sí afecta haría ignorar las dos.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'local_tools',
            'pluginincompatible' => 'theme_bootstrapbase',
        ]);

        $atencion = Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion');

        $this->assertSame('info', $atencion[0]['nivel']);
        $this->assertStringContainsString('no está instalado en este entorno: no afecta', $atencion[0]['texto']);
    }

    public function test_una_incompatibilidad_con_algo_instalado_si_alarma(): void
    {
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'theme_viejo', 'type' => 'theme']);
        $this->plugin($entorno, [
            'component' => 'local_tools',
            'type' => 'local',
            'pluginincompatible' => 'theme_viejo',
        ]);

        $atencion = collect(Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion'));
        $caso = $atencion->firstWhere('etiqueta', 'Incompatibilidad presente');

        $this->assertNotNull($caso);
        $this->assertSame('crit', $caso['nivel']);
        $this->assertStringContainsString('está instalado en este entorno', $caso['texto']);
    }

    public function test_las_dependencias_dicen_si_estan_instaladas_aqui(): void
    {
        // **Es lo que convierte una lista de texto en una respuesta.** Antes se listaban
        // sin comprobar nada, y lo único que interesa saber de una dependencia es si está.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'mod_forum', 'type' => 'mod']);
        $conDeps = $this->plugin($entorno, [
            'component' => 'mod_algo',
            'type' => 'mod',
            'has_dependencies' => true,
            'dependencies' => ['mod_forum' => 2022041900, 'mod_ausente' => 2022041900],
        ]);

        $componente = Livewire::test(Index::class, ['environment' => $entorno])
            ->call('alternarTipo', 'mod')
            ->call('alternarPlugin', $conDeps->id);

        $grupos = collect($componente->viewData('grupos'));
        $fila = collect($grupos->firstWhere('tipo', 'mod')['plugins'])
            ->firstWhere('plugin.component', 'mod_algo');

        $this->assertNotNull($fila);

        $deps = collect($fila['dependencias']);

        $this->assertTrue($deps->firstWhere('nombre', 'mod_forum')['instalada']);
        $this->assertFalse($deps->firstWhere('nombre', 'mod_ausente')['instalada']);
    }

    /* ===================== Los nuestros ===================== */

    public function test_el_catalogo_de_productos_es_la_unica_fuente_de_que_es_nuestro(): void
    {
        // **Lo decide el Manager, no el nombre del plugin.** Un plugin es nuestro si su
        // `component` está en el catálogo de Productos: detrás hay ficha, licencias y
        // contenido publicado. Sin producto, el Manager no sabe nada de ese plugin y no
        // tiene por qué afirmar que es suyo.
        $this->admin();
        $entorno = $this->entorno();

        // En el catálogo y **sin ninguna marca de Tresipunt en el nombre**: la regla
        // anterior no lo reconocía, y es un producto nuestro.
        Product::create([
            'slug' => 'local_courseai',
            'name' => 'Local CourseAI',
            'tech' => 'moodle',
            'actived' => true,
            'enabled' => true,
        ]);
        $this->plugin($entorno, ['component' => 'local_courseai', 'name' => 'CourseAI', 'type' => 'local']);

        // Con marca en el nombre pero **fuera del catálogo**: ya no cuenta como nuestro.
        $this->plugin($entorno, ['component' => 'local_tresipunt', 'name' => '3iPunt', 'type' => 'local']);

        // Y uno de terceros.
        $this->plugin($entorno, ['component' => 'mod_customcert', 'type' => 'mod']);

        $nuestros = collect(Livewire::test(Index::class, ['environment' => $entorno])->viewData('nuestros'));

        $this->assertSame(
            ['local_courseai'],
            $nuestros->pluck('plugin.component')->all(),
            'La identificación no sale solo del catálogo'
        );
        $this->assertSame('Local CourseAI', $nuestros->first()['producto']->name);
    }

    public function test_un_plugin_que_parece_nuestro_y_no_esta_en_el_catalogo_se_sugiere_dar_de_alta(): void
    {
        // **Es una sugerencia, no una identificación**, y va en su propio bloque. Sin
        // esto, pasar la identificación al catálogo habría hecho desaparecer de la
        // pantalla los cinco plugins que hoy están fuera, y nadie se habría enterado de
        // que faltan.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'local_tresipuntmarket', 'name' => 'Marketplace', 'type' => 'local']);
        $this->plugin($entorno, ['component' => 'mod_customcert', 'type' => 'mod']);

        $componente = Livewire::test(Index::class, ['environment' => $entorno]);

        $candidatos = collect($componente->viewData('candidatos'));

        $this->assertSame(['local_tresipuntmarket'], $candidatos->pluck('component')->all());

        // Y no se afirma que sea nuestro: se dice que el Manager no lo sabe.
        $componente->assertSee('¿Falta darlos de alta?')
            ->assertSee('el Manager no lo sabe')
            // Con el identificador ya puesto en el enlace de alta: copiarlo a mano es
            // donde se cometen los dedazos, y un slug mal escrito no empareja con nada.
            ->assertSee('slug=local_tresipuntmarket', false);

        // No aparece entre los nuestros.
        $this->assertSame([], collect($componente->viewData('nuestros'))->pluck('plugin.component')->all());
    }

    public function test_un_plugin_del_catalogo_no_se_sugiere_dar_de_alta(): void
    {
        $this->admin();
        $entorno = $this->entorno();

        Product::create(['slug' => 'theme_fresk', 'name' => 'Theme Fresk', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);
        $this->plugin($entorno, ['component' => 'theme_fresk', 'type' => 'theme']);

        $componente = Livewire::test(Index::class, ['environment' => $entorno]);

        $this->assertSame([], $componente->viewData('candidatos'));
        $componente->assertDontSee('¿Falta darlos de alta?');
    }

    public function test_el_bloque_se_llama_plugins_tresipunt_y_dice_de_donde_sale(): void
    {
        // La pregunta que se hace mirando la pantalla es «¿cómo sabe que este es nuestro?»,
        // y la respuesta tiene que estar ahí: no hay ninguna otra pantalla donde marcarlos.
        $this->admin();
        $entorno = $this->entorno();

        Product::create(['slug' => 'theme_fresk', 'name' => 'Theme Fresk', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);
        $this->plugin($entorno, ['component' => 'theme_fresk', 'type' => 'theme']);

        Livewire::test(Index::class, ['environment' => $entorno])
            ->assertSee('Plugins Tresipunt')
            // Se declara a mano en uno de los dos catálogos: el nombre no cuenta.
            ->assertSee('Se declara a')
            ->assertSee('el nombre del plugin no cuenta');
    }

    public function test_no_se_dice_si_nuestro_plugin_esta_en_la_ultima_version(): void
    {
        // **Porque no se puede saber**: el Manager no registra la versión vigente de cada
        // producto. Es una decisión pendiente, y la pantalla lo dice en vez de callarse o
        // de inventarse un «al día».
        $this->admin();
        $entorno = $this->entorno();

        Product::create(['slug' => 'local_tresipunt', 'name' => '3iPunt', 'tech' => 'moodle', 'actived' => true, 'enabled' => true]);
        $this->plugin($entorno, ['component' => 'local_tresipunt', 'type' => 'local']);

        Livewire::test(Index::class, ['environment' => $entorno])
            ->assertSee('aún no se puede decir')
            ->assertSee('no registra la versión');
    }

    /* ===================== Los filtros y el volumen ===================== */

    public function test_el_buscador_busca_en_el_nombre_y_no_solo_en_el_component(): void
    {
        // Antes solo buscaba en `component`. El nombre es lo que se recuerda de un plugin;
        // el `component` es lo que se copia.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'mod_customcert', 'name' => 'Certificado personalizado', 'type' => 'mod']);
        $this->plugin($entorno, ['component' => 'mod_quiz', 'name' => 'Cuestionario', 'type' => 'mod']);

        $porNombre = collect(
            Livewire::test(Index::class, ['environment' => $entorno])
                ->set('busca', 'certificado')
                ->viewData('grupos')
        );

        $encontrados = collect($porNombre->firstWhere('tipo', 'mod')['plugins']);

        $this->assertCount(1, $encontrados);
        $this->assertSame('mod_customcert', $encontrados->first()['plugin']->component);
    }

    public function test_el_filtro_de_atencion_deja_solo_los_que_piden_algo(): void
    {
        // Son 3 de 464: sin este filtro hay que rebuscar entre las que no piden nada.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'mod_uno', 'type' => 'mod']);
        $this->plugin($entorno, ['component' => 'mod_dos', 'type' => 'mod']);
        $this->plugin($entorno, [
            'component' => 'mod_tres',
            'type' => 'mod',
            'versiondisk' => '2026090200',
            'versiondb' => '2026090100',
        ]);

        $grupos = collect(
            Livewire::test(Index::class, ['environment' => $entorno])
                ->set('soloAtencion', true)
                ->viewData('grupos')
        );

        $plugins = collect($grupos->firstWhere('tipo', 'mod')['plugins']);

        $this->assertCount(1, $plugins);
        $this->assertSame('mod_tres', $plugins->first()['plugin']->component);
    }

    public function test_con_filtro_puesto_los_grupos_se_despliegan_solos(): void
    {
        // Si no, hay que abrir a mano el grupo donde está lo que el buscador acaba de
        // encontrar.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'block_algo', 'type' => 'block']);

        $grupos = collect(
            Livewire::test(Index::class, ['environment' => $entorno])
                ->set('busca', 'block')
                ->viewData('grupos')
        );

        $this->assertTrue($grupos->firstWhere('tipo', 'block')['abierto']);
    }

    public function test_el_recuento_no_cuenta_dos_veces_al_mismo_plugin(): void
    {
        // Un plugin puede tener a la vez el desfase de versión y una actualización
        // disponible. Sumando los grupos saldría dos veces, y **un recuento que no cuadra
        // con las filas de abajo es un número que no se puede explicar**.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'mod_ambos',
            'type' => 'mod',
            'versiondisk' => '2026090200',
            'versiondb' => '2026090100',
            'has_updates' => true,
            'availableupdates' => [['release' => '2.0.0']],
        ]);

        $resumen = Livewire::test(Index::class, ['environment' => $entorno])->viewData('resumen');

        $this->assertStringContainsString('Requiere algo 1', $resumen);
        $this->assertStringNotContainsString('Requieren algo 2', $resumen);
    }

    public function test_el_inventario_completo_no_se_lee_una_vez_por_bloque(): void
    {
        // `render()` necesita el inventario cinco veces —atención, los nuestros, los
        // grupos, el resumen y la frescura— y sin memoizar eran cinco consultas de 470
        // filas en cada interacción de Livewire.
        $this->admin();
        $entorno = $this->entorno();

        for ($i = 1; $i <= 40; $i++) {
            $this->plugin($entorno, ['component' => 'mod_p' . $i, 'type' => 'mod']);
        }

        Cache::flush();

        $consultas = 0;
        DB::listen(function ($consulta) use (&$consultas) {
            // **Solo la tabla del inventario.** `str_contains($sql, 'plugins')` también
            // pillaba `own_plugins` —el catálogo de plugins propios— y contaba consultas
            // que no son el inventario.
            if (str_contains($consulta->sql, 'from "plugins"') || str_contains($consulta->sql, 'from `plugins`')) {
                $consultas++;
            }
        });

        Livewire::test(Index::class, ['environment' => $entorno]);

        $this->assertLessThanOrEqual(
            2,
            $consultas,
            'El inventario se está leyendo ' . $consultas . ' veces por render'
        );
    }

    /* ===================== Los estados ===================== */

    public function test_un_entorno_sin_inventario_lo_explica_y_no_parece_un_error(): void
    {
        // Es el estado de 18 de los 21 entornos. El inventario llega con la
        // sincronización, y hace falta una versión del plugin que lo envíe.
        $this->admin();
        $entorno = $this->entorno();

        Livewire::test(Index::class, ['environment' => $entorno])
            ->assertSee('todavía no ha enviado su inventario')
            ->assertSee('No es un error')
            ->assertDontSee('Requiere atención');
    }

    public function test_un_inventario_viejo_se_avisa(): void
    {
        // Se reescribe entero en cada sincronización: una lista de hace tres semanas
        // presentada como actual es la trampa de esta pantalla.
        $this->admin();
        $entorno = $this->entorno();

        $plugin = $this->plugin($entorno, ['component' => 'mod_uno', 'type' => 'mod']);
        // `created_at` es la fecha del último sync, no la de instalación.
        $plugin->forceFill(['created_at' => now()->subDays(21)])->saveQuietly();

        $componente = Livewire::test(Index::class, ['environment' => $entorno]);

        $this->assertTrue($componente->viewData('frescura')['viejo']);
        $componente->assertSee('hace 21 días')
            ->assertSee('puede no ser lo que hay instalado ahora');
    }

    /* ===================== El plugin que no está en el disco ===================== */

    public function test_un_plugin_registrado_sin_ficheros_se_dice_ausente_del_disco(): void
    {
        // **Este es el caso que se leía como «Sin novedades».** Moodle lo marca en su
        // pantalla de plugins como «Missing from disk»: sigue registrado en la base de
        // datos y sus ficheros no están. `versiondisk` no llega como `null` —la columna es
        // NOT NULL— sino como el `'0'` que ponen las acciones de la API.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'local_moodlecheck',
            'name' => 'Moodle code checker',
            'versiondisk' => '0',
            'versiondb' => '2024032700',
        ]);

        $atencion = Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion');

        $this->assertCount(1, $atencion);
        $this->assertSame('Ausente del disco', $atencion[0]['etiqueta']);
        $this->assertSame('crit', $atencion[0]['nivel']);
        $this->assertStringContainsString('2024032700', $atencion[0]['texto']);

        // Y **no se manda a actualizar ni a pasar el cron**, que es lo que dicen los otros
        // dos desfases y aquí no arregla nada: no hay ficheros que actualizar.
        $this->assertStringNotContainsString('Moodle lo lanza solo', $atencion[0]['texto']);
        $this->assertStringNotContainsString('subir los ficheros de la versión que toca', $atencion[0]['texto']);
    }

    public function test_sin_ninguna_de_las_dos_versiones_no_se_afirma_nada(): void
    {
        // Un plugin del que no sabemos ni disco ni base de datos no es una avería: es un
        // payload incompleto. Decir «ausente del disco» ahí sería inventarse el
        // diagnóstico.
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, [
            'component' => 'mod_sinnada',
            'versiondisk' => '0',
            'versiondb' => '0',
        ]);

        $this->assertSame([], Livewire::test(Index::class, ['environment' => $entorno])->viewData('atencion'));
    }

    public function test_el_resumen_cuenta_los_ausentes_aparte(): void
    {
        $this->admin();
        $entorno = $this->entorno();

        $this->plugin($entorno, ['component' => 'local_uno', 'versiondisk' => '0', 'versiondb' => '2024032700']);
        $this->plugin($entorno, ['component' => 'local_dos', 'versiondisk' => '0', 'versiondb' => '2025010100']);

        $resumen = Livewire::test(Index::class, ['environment' => $entorno])->viewData('resumen');

        $this->assertStringContainsString('2 ausentes del disco', $resumen);
    }
    public function test_hace_falta_el_permiso_de_plugins_del_entorno(): void
    {
        $rol = Role::firstOrCreate(['name' => 'Mirón', 'guard_name' => 'web']);
        $rol->givePermissionTo(Permission::firstOrCreate(
            ['name' => 'admin.environments.index', 'guard_name' => 'web'],
            ['model' => 'environments', 'description' => 'ver']
        ));

        $user = User::factory()->create();
        $user->assignRole($rol);
        $this->actingAs($user);
        Cache::flush();

        $entorno = $this->entorno();

        $this->get(route('environments.plugins.index', $entorno))->assertForbidden();
    }
}
