<?php

namespace Mtc\Plugins\BlockManager\Tests\Unit;

use Mtc\Modules\BlockRegistry\Models\BlockRegistryField;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

class ElementManifestSchemaTest extends TestCase
{
    private const ELEMENTS_DIR = __DIR__ . '/../../elements';

    /**
     * Phase 2 ships 3 starter elements. Each must satisfy the manifest schema
     * declared in plugins/BlockManager/claude.md §"Element manifest schema".
     */
    public static function provideElements(): array
    {
        return [
            'heading'   => ['heading'],
            'paragraph' => ['paragraph'],
            'button'    => ['button'],
            'link'      => ['link'],
            'details'   => ['details'],
            'table'     => ['table'],
            'hr'        => ['hr'],
            'image'     => ['image'],
        ];
    }

    #[DataProvider('provideElements')]
    public function test_element_directory_exists(string $type): void
    {
        $this->assertDirectoryExists(
            self::ELEMENTS_DIR . '/' . $type,
            "Element directory plugins/BlockManager/elements/{$type}/ is missing."
        );
    }

    #[DataProvider('provideElements')]
    public function test_manifest_file_exists_and_parses(string $type): void
    {
        $path = self::ELEMENTS_DIR . "/{$type}/element.json";
        $this->assertFileExists($path, "Manifest {$path} is missing.");

        $json = json_decode(file_get_contents($path), true);
        $this->assertIsArray($json, "Manifest {$path} did not decode to an array.");
        $this->assertSame(JSON_ERROR_NONE, json_last_error(), 'Manifest JSON is invalid: ' . json_last_error_msg());
    }

    #[DataProvider('provideElements')]
    public function test_manifest_has_required_top_level_keys(string $type): void
    {
        $manifest = $this->loadManifest($type);

        foreach (['type', 'label', 'category', 'icon', 'inputs', 'variants', 'default_variant'] as $key) {
            $this->assertArrayHasKey($key, $manifest, "Manifest for '{$type}' is missing top-level key '{$key}'.");
        }
    }

    #[DataProvider('provideElements')]
    public function test_manifest_type_matches_directory_name(string $type): void
    {
        $manifest = $this->loadManifest($type);
        $this->assertSame($type, $manifest['type'], "Manifest type does not match directory name '{$type}'.");
    }

    #[DataProvider('provideElements')]
    public function test_inputs_are_non_empty_array(string $type): void
    {
        $manifest = $this->loadManifest($type);
        $this->assertIsArray($manifest['inputs']);
        $this->assertNotEmpty($manifest['inputs'], "Element '{$type}' must declare at least one input.");
    }

    #[DataProvider('provideElements')]
    public function test_inputs_have_valid_block_registry_field_types(string $type): void
    {
        $manifest = $this->loadManifest($type);
        $validTypes = array_keys(BlockRegistryField::FIELD_TYPES);

        foreach ($manifest['inputs'] as $i => $input) {
            $this->assertArrayHasKey('name', $input, "Element '{$type}' input #{$i} is missing 'name'.");
            $this->assertArrayHasKey('label', $input, "Element '{$type}' input #{$i} is missing 'label'.");
            $this->assertArrayHasKey('type', $input, "Element '{$type}' input #{$i} is missing 'type'.");
            $this->assertContains(
                $input['type'],
                $validTypes,
                "Element '{$type}' input '{$input['name']}' has type '{$input['type']}' which is not in BlockRegistryField::FIELD_TYPES."
            );
        }
    }

    #[DataProvider('provideElements')]
    public function test_element_has_at_least_two_variants(string $type): void
    {
        $manifest = $this->loadManifest($type);
        $this->assertGreaterThanOrEqual(
            2,
            count($manifest['variants']),
            "Element '{$type}' must ship at least 2 variants for Phase 2."
        );
    }

    #[DataProvider('provideElements')]
    public function test_variants_have_id_and_label(string $type): void
    {
        $manifest = $this->loadManifest($type);

        foreach ($manifest['variants'] as $i => $variant) {
            $this->assertArrayHasKey('id', $variant, "Element '{$type}' variant #{$i} is missing 'id'.");
            $this->assertArrayHasKey('label', $variant, "Element '{$type}' variant #{$i} is missing 'label'.");
        }
    }

    #[DataProvider('provideElements')]
    public function test_default_variant_matches_a_declared_variant(string $type): void
    {
        $manifest = $this->loadManifest($type);
        $variantIds = array_column($manifest['variants'], 'id');

        $this->assertContains(
            $manifest['default_variant'],
            $variantIds,
            "Element '{$type}' default_variant '{$manifest['default_variant']}' does not match any declared variant id."
        );
    }

    #[DataProvider('provideElements')]
    public function test_each_variant_has_twig_partial_on_disk(string $type): void
    {
        $manifest = $this->loadManifest($type);

        foreach ($manifest['variants'] as $variant) {
            $path = self::ELEMENTS_DIR . "/{$type}/variants/{$variant['id']}.twig";
            $this->assertFileExists(
                $path,
                "Variant Twig file missing for '{$type}.{$variant['id']}'. Expected: plugins/BlockManager/elements/{$type}/variants/{$variant['id']}.twig"
            );
        }
    }

    #[DataProvider('provideElements')]
    public function test_each_variant_has_less_stylesheet_on_disk(string $type): void
    {
        $manifest = $this->loadManifest($type);

        foreach ($manifest['variants'] as $variant) {
            $path = self::ELEMENTS_DIR . "/{$type}/styles/{$variant['id']}.less";
            $this->assertFileExists(
                $path,
                "Variant LESS file missing for '{$type}.{$variant['id']}'. Expected: plugins/BlockManager/elements/{$type}/styles/{$variant['id']}.less"
            );
        }
    }

    private function loadManifest(string $type): array
    {
        $path = self::ELEMENTS_DIR . "/{$type}/element.json";
        if (!file_exists($path)) {
            $this->markTestSkipped("Manifest {$path} not present — covered by test_manifest_file_exists_and_parses.");
        }
        return json_decode(file_get_contents($path), true) ?? [];
    }
}
