<?php

declare(strict_types=1);

use Builder\Core\Foundation\Assets;

beforeEach(function (): void {
    $this->testPublicPath = sys_get_temp_dir().'/builder-assets-test-'.uniqid();
    mkdir($this->testPublicPath, 0755, true);
    $this->app->usePublicPath($this->testPublicPath);
});

afterEach(function (): void {
    $path = $this->testPublicPath;
    $rm = function (string $dir) use (&$rm): void {
        if (! is_dir($dir)) {
            return;
        }
        foreach (array_diff((array) scandir($dir), ['.', '..']) as $item) {
            $full = "$dir/$item";
            is_dir($full) ? $rm($full) : unlink($full);
        }
        rmdir($dir);
    };
    $rm($path);
});

// --- tags() ---

it('returns vite dev server tags when hot file is present', function (): void {
    $vendorDir = $this->testPublicPath.'/vendor/builder/core';
    mkdir($vendorDir, 0755, true);
    file_put_contents($vendorDir.'/hot', 'http://localhost:5173');

    $result = Assets::tags();

    expect($result)->toBe(
        '<script type="module" src="http://localhost:5173/@vite/client"></script>'."\n".
        '<script type="module" src="http://localhost:5173/resources/js/app.ts"></script>'
    );
});

it('tags() accepts optional packageKey argument', function (): void {
    $vendorDir = $this->testPublicPath.'/vendor/builder/core';
    mkdir($vendorDir, 0755, true);
    file_put_contents($vendorDir.'/hot', 'http://localhost:5173');

    $result = Assets::tags('builder');

    expect($result)->toContain('http://localhost:5173');
});

it('throws RuntimeException when manifest is missing', function (): void {
    Assets::tags();
})->throws(RuntimeException::class, 'Builder manifest not found');

it('throws RuntimeException when entry is not in manifest', function (): void {
    $viteDir = $this->testPublicPath.'/vendor/builder/core/.vite';
    mkdir($viteDir, 0755, true);
    file_put_contents($viteDir.'/manifest.json', json_encode([
        'other/entry.js' => ['file' => 'assets/other.abc.js'],
    ]));

    Assets::tags();
})->throws(RuntimeException::class, "Builder entry 'resources/js/app.ts' not in manifest");

it('returns link and script tags from manifest with css and imports', function (): void {
    $viteDir = $this->testPublicPath.'/vendor/builder/core/.vite';
    mkdir($viteDir, 0755, true);
    file_put_contents($viteDir.'/manifest.json', json_encode([
        'resources/js/app.ts' => [
            'file' => 'assets/app.abc.js',
            'css' => ['assets/app.abc.css'],
            'imports' => ['_chunk.def.js'],
        ],
        '_chunk.def.js' => [
            'file' => 'assets/chunk.def.js',
            'css' => ['assets/chunk.def.css'],
        ],
    ]));

    $result = Assets::tags();

    expect($result)
        ->toContain('<link rel="stylesheet" href="')
        ->toContain('assets/chunk.def.css')
        ->toContain('assets/app.abc.css')
        ->toContain('<script type="module" src="')
        ->toContain('assets/app.abc.js');
});

it('throws RuntimeException when manifest entry has no file key', function (): void {
    $viteDir = $this->testPublicPath.'/vendor/builder/core/.vite';
    mkdir($viteDir, 0755, true);
    file_put_contents($viteDir.'/manifest.json', json_encode([
        'resources/js/app.ts' => [
            'css' => ['assets/app.abc.css'],
        ],
    ]));

    Assets::tags();
})->throws(RuntimeException::class, "Builder manifest entry 'resources/js/app.ts' has no 'file'");

// --- entryUrl() ---

it('entryUrl returns dev server url when hot file exists', function (): void {
    $coreDir = $this->testPublicPath.'/vendor/builder/core';
    mkdir($coreDir, 0755, true);
    file_put_contents($coreDir.'/hot', 'http://localhost:5173');

    $dir = $this->testPublicPath.'/vendor/builder/media';
    mkdir($dir, 0755, true);
    file_put_contents($dir.'/hot', 'http://localhost:5174');

    $result = Assets::entryUrl('media', 'resources/js/module.ts');

    expect($result)->toBe('http://localhost:5174/resources/js/module.ts');
});

it('entryUrl throws when no manifest exists', function (): void {
    Assets::entryUrl('media', 'index.js');
})->throws(RuntimeException::class, "Manifest not found for 'media'");

it('entryUrl throws when entry not in manifest', function (): void {
    $dir = $this->testPublicPath.'/vendor/builder/media/.vite';
    mkdir($dir, 0755, true);
    file_put_contents($dir.'/manifest.json', json_encode([
        'other.js' => ['file' => 'other.abc.js'],
    ]));

    Assets::entryUrl('media', 'index.js');
})->throws(RuntimeException::class, "Entry 'index.js' not in manifest for 'media'");

it('entryUrl returns asset url from manifest', function (): void {
    $dir = $this->testPublicPath.'/vendor/builder/media/.vite';
    mkdir($dir, 0755, true);
    file_put_contents($dir.'/manifest.json', json_encode([
        'resources/js/module.ts' => ['file' => 'assets/module.abc.js'],
    ]));

    $result = Assets::entryUrl('media', 'resources/js/module.ts');

    expect($result)->toContain('assets/module.abc.js');
});

// --- importMap() ---

it('importMap generates json with resolved imports', function (): void {
    $dir = $this->testPublicPath.'/vendor/builder/media/.vite';
    mkdir($dir, 0755, true);
    file_put_contents($dir.'/manifest.json', json_encode([
        'resources/js/module.ts' => ['file' => 'assets/module.abc.js'],
    ]));

    $result = Assets::importMap([
        'builder-media' => ['package' => 'media', 'entry' => 'resources/js/module.ts'],
    ]);

    $decoded = json_decode($result, true);

    expect($decoded)->toHaveKey('imports')
        ->and($decoded['imports'])->toHaveKey('builder-media')
        ->and($decoded['imports']['builder-media'])->toContain('assets/module.abc.js');
});

it('importMap silently skips modules with missing manifests', function (): void {
    $result = Assets::importMap([
        'builder-media' => ['package' => 'media', 'entry' => 'resources/js/index.ts'],
    ]);

    expect($result)->toContain('"imports":{}')
        ->and(json_decode($result))->toHaveProperty('imports');
});

it('importMap includes vue and @builder/ui from core manifest in production mode', function (): void {
    // Set up core manifest with vue and ui entries
    $coreViteDir = $this->testPublicPath.'/vendor/builder/core/.vite';
    mkdir($coreViteDir, 0755, true);
    file_put_contents($coreViteDir.'/manifest.json', json_encode([
        'resources/js/vendor/vue.ts' => ['file' => 'assets/vendor/vue-abc.js'],
        'resources/js/ui/index.ts' => ['file' => 'assets/ui-abc.js'],
    ]));

    $result = Assets::importMap([]);
    $decoded = json_decode($result, true);

    expect($decoded['imports'])->toHaveKey('vue')
        ->and($decoded['imports']['vue'])->toContain('assets/vendor/vue-abc.js')
        ->and($decoded['imports'])->toHaveKey('@builder/ui')
        ->and($decoded['imports']['@builder/ui'])->toContain('assets/ui-abc.js');
});

it('importMap includes @inertiajs/vue3 when inertia.ts is in core manifest', function (): void {
    $coreViteDir = $this->testPublicPath.'/vendor/builder/core/.vite';
    mkdir($coreViteDir, 0755, true);
    file_put_contents($coreViteDir.'/manifest.json', json_encode([
        'resources/js/vendor/vue.ts' => ['file' => 'assets/vendor/vue-abc.js'],
        'resources/js/ui/index.ts' => ['file' => 'assets/ui-abc.js'],
        'resources/js/vendor/inertia.ts' => ['file' => 'assets/vendor/inertia-abc.js'],
    ]));

    $result = Assets::importMap([]);
    $decoded = json_decode($result, true);

    expect($decoded['imports'])->toHaveKey('@inertiajs/vue3')
        ->and($decoded['imports']['@inertiajs/vue3'])->toContain('assets/vendor/inertia-abc.js');
});

it('importMap includes vue and @builder/ui from dev server in dev mode so production-built modules can resolve them', function (): void {
    $hotDir = $this->testPublicPath.'/vendor/builder/core';
    mkdir($hotDir, 0755, true);
    file_put_contents($hotDir.'/hot', 'http://localhost:5173');

    $result = Assets::importMap([]);
    $decoded = json_decode($result, true);

    expect($decoded['imports'])->toHaveKey('vue')
        ->and($decoded['imports']['vue'])->toBe('http://localhost:5173/resources/js/vendor/vue.ts')
        ->and($decoded['imports'])->toHaveKey('@builder/ui')
        ->and($decoded['imports']['@builder/ui'])->toBe('http://localhost:5173/resources/js/ui/index.ts');
});
