<?php

declare(strict_types=1);

namespace Builder\Core\Foundation;

use RuntimeException;

final class Assets
{
    private const string APP_ENTRY = 'resources/js/app.ts';

    /** Base path for core assets (symlinked to packages/core/public) */
    private const string CORE_BASE = 'vendor/builder/core';

    /** Base path for module assets — module key is appended (e.g. vendor/builder/media) */
    private const string MODULE_BASE = 'vendor/builder';

    public static function tags(string $packageKey = 'builder'): string
    {
        unset($packageKey);

        $base = public_path(self::CORE_BASE);
        $hotPath = $base.'/hot';

        if (is_file($hotPath)) {
            $origin = trim((string) file_get_contents($hotPath));

            return implode("\n", [
                '<script type="module" src="'.rtrim($origin, '/').'/@vite/client"></script>',
                '<script type="module" src="'.rtrim($origin, '/').'/'.self::APP_ENTRY.'"></script>',
            ]);
        }

        $manifestPath = $base.'/.vite/manifest.json';
        if (! is_file($manifestPath)) {
            throw new RuntimeException('Builder manifest not found at '.$manifestPath);
        }

        /** @var array<string, array{file?: string, css?: list<string>, imports?: list<string>}> $manifest */
        $manifest = json_decode((string) file_get_contents($manifestPath), true) ?? [];

        if (! isset($manifest[self::APP_ENTRY])) {
            $keys = implode(', ', array_keys($manifest));
            throw new RuntimeException("Builder entry '".self::APP_ENTRY."' not in manifest. Available: {$keys}");
        }

        $item = $manifest[self::APP_ENTRY];
        $baseUrl = asset(self::CORE_BASE);
        $tags = [];

        foreach ($item['imports'] ?? [] as $importKey) {
            if (isset($manifest[$importKey])) {
                foreach ($manifest[$importKey]['css'] ?? [] as $css) {
                    $tags[] = '<link rel="stylesheet" href="'.$baseUrl.'/'.ltrim($css, '/').'">';
                }
            }
        }

        foreach ($item['css'] ?? [] as $css) {
            $tags[] = '<link rel="stylesheet" href="'.$baseUrl.'/'.ltrim($css, '/').'">';
        }

        if (! isset($item['file'])) {
            throw new RuntimeException("Builder manifest entry '".self::APP_ENTRY."' has no 'file'");
        }

        $tags[] = '<script type="module" src="'.$baseUrl.'/'.ltrim($item['file'], '/').'"></script>';

        return implode("\n", array_values(array_unique($tags)));
    }

    public static function entryUrl(string $packageKey, string $entry): string
    {
        $base = public_path(self::MODULE_BASE.'/'.$packageKey);
        $hotPath = $base.'/hot';
        $coreHotPath = public_path(self::CORE_BASE.'/hot');

        if (is_file($hotPath) && is_file($coreHotPath)) {
            $origin = trim((string) file_get_contents($hotPath));

            return rtrim($origin, '/').'/'.ltrim($entry, '/');
        }

        // Vite 5 places the manifest at .vite/manifest.json
        $manifestPath = $base.'/.vite/manifest.json';
        if (! is_file($manifestPath)) {
            throw new RuntimeException("Manifest not found for '{$packageKey}' at {$manifestPath}");
        }

        /** @var array<string, array{file?: string}> $manifest */
        $manifest = json_decode((string) file_get_contents($manifestPath), true) ?? [];

        if (! isset($manifest[$entry]['file'])) {
            $keys = implode(', ', array_keys($manifest));
            throw new RuntimeException("Entry '{$entry}' not in manifest for '{$packageKey}'. Available: {$keys}");
        }

        return asset(self::MODULE_BASE.'/'.$packageKey.'/'.ltrim($manifest[$entry]['file'], '/'));
    }

    /**
     * @param  array<string, array{package: string, entry: string}>  $imports
     */
    public static function importMap(array $imports): string
    {
        $map = ['imports' => []];

        // Shared deps required by all modules (vue, @builder/ui).
        // Skipped in dev mode — the Vite dev server handles resolution server-side.
        foreach (self::sharedImports() as $specifier => $url) {
            $map['imports'][$specifier] = $url;
        }

        foreach ($imports as $specifier => $target) {
            try {
                $map['imports'][$specifier] = self::entryUrl($target['package'], $target['entry']);
            } catch (RuntimeException) {
                // Module assets not built/linked yet — skip silently.
            }
        }

        // Cast to object so empty imports serialize as {} not []
        return json_encode(['imports' => (object) $map['imports']], JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
    }

    /**
     * Returns import-map entries for shared deps (vue, @builder/ui) that all modules need.
     * Returns an empty array in dev mode (hot file present) because the Vite dev server
     * handles dep resolution itself.
     *
     * @return array<string, string>
     */
    private static function sharedImports(): array
    {
        $coreBase = public_path(self::CORE_BASE);

        // Dev mode: production-built modules still need `vue` and `@builder/ui` in the
        // import map to resolve bare specifiers. Point to the Vite dev server's own
        // source files — Vite transforms and serves them on demand.
        if (is_file($coreBase.'/hot')) {
            $origin = rtrim(trim((string) file_get_contents($coreBase.'/hot')), '/');

            return [
                'vue' => $origin.'/resources/js/vendor/vue.ts',
                '@builder/ui' => $origin.'/resources/js/ui/index.ts',
                '@inertiajs/vue3' => $origin.'/resources/js/vendor/inertia.ts',
            ];
        }

        $manifestPath = $coreBase.'/.vite/manifest.json';
        if (! is_file($manifestPath)) {
            return [];
        }

        /** @var array<string, array{file?: string}> $manifest */
        $manifest = json_decode((string) file_get_contents($manifestPath), true) ?? [];
        $baseUrl = asset(self::CORE_BASE);
        $shared = [];

        if (isset($manifest['resources/js/vendor/vue.ts']['file'])) {
            $shared['vue'] = $baseUrl.'/'.ltrim($manifest['resources/js/vendor/vue.ts']['file'], '/');
        }

        if (isset($manifest['resources/js/ui/index.ts']['file'])) {
            $shared['@builder/ui'] = $baseUrl.'/'.ltrim($manifest['resources/js/ui/index.ts']['file'], '/');
        }

        if (isset($manifest['resources/js/vendor/inertia.ts']['file'])) {
            $shared['@inertiajs/vue3'] = $baseUrl.'/'.ltrim($manifest['resources/js/vendor/inertia.ts']['file'], '/');
        }

        return $shared;
    }
}
