<?php

// phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound -- APD is the established namespace shared with the Pro add-on and retained for backward compatibility.
namespace APD\Support;

defined('ABSPATH') || exit;

final class DesignerAssets
{
    private const BASE_PATH = 'assets/vendor/asuka-designer/';

    private static ?array $manifest = null;

    public static function reset() : void
    {
        self::$manifest = null;
    }

    public static function manifest() : array
    {
        if (self::$manifest !== null) {
            return self::$manifest;
        }

        $combined = [];
        $files = [];
        foreach (self::providers() as $provider) {
            $path = (string) ($provider['manifest'] ?? '');
            if ($path === '' || ! is_readable($path)) {
                continue;
            }

            $decoded = json_decode((string) file_get_contents($path), true);
            if (! is_array($decoded)) {
                continue;
            }

            if ($combined === []) {
                $combined = $decoded;
                $combined['files'] = [];
            } else {
                foreach ((array) ($decoded['entrypoints'] ?? []) as $key => $file) {
                    if (! isset($combined['entrypoints'][$key])) {
                        $combined['entrypoints'][$key] = $file;
                    }
                }
            }

            foreach ((array) ($decoded['files'] ?? []) as $entry) {
                if (! is_array($entry)) {
                    continue;
                }
                $file = self::safeFileName((string) ($entry['name'] ?? ''));
                if ($file === '' || isset($files[$file])) {
                    continue;
                }
                $entry['name'] = $file;
                $entry['_provider'] = (string) $provider['id'];
                $files[$file] = $entry;
            }
        }

        $combined['files'] = array_values($files);
        self::$manifest = $combined;

        return self::$manifest;
    }

    public static function designerVersion() : string
    {
        $version = trim((string) (self::manifest()['version'] ?? ''));
        $fallback = defined('ASUKA_PRODUCT_DESIGNER_JS_VERSION') ? ASUKA_PRODUCT_DESIGNER_JS_VERSION : ASUKA_PRODUCT_DESIGNER_VERSION;
        return $version !== '' ? $version : $fallback;
    }

    public static function entrypoint(string $key, string $fallback = '') : string
    {
        $file = (string) (self::manifest()['entrypoints'][$key] ?? $fallback);
        return self::safeFileName($file);
    }

    public static function coreFile() : string
    {
        return self::entrypoint('module', 'asukadesigner.min.js');
    }

    public static function styleFile() : string
    {
        return self::entrypoint('style', 'asukadesigner.min.css');
    }

    public static function relativePath(string $file) : string
    {
        return self::BASE_PATH . self::safeFileName($file);
    }

    public static function absolutePath(string $file) : string
    {
        $file = self::safeFileName($file);
        $provider = self::providerForFile($file);
        return $file === '' ? '' : (string) $provider['path'] . $file;
    }

    public static function url(string $file, bool $versioned = false) : string
    {
        $file = self::safeFileName($file);
        if ($file === '') {
            return '';
        }

        $provider = self::providerForFile($file);
        $url = (string) $provider['url'] . $file;

        if (! $versioned) {
            return $url;
        }

        return add_query_arg('ver', self::version($file), $url);
    }

    public static function version(string $file) : string
    {
        $path = self::absolutePath($file);
        return $path !== '' && is_file($path) ? (string) filemtime($path) : ASUKA_PRODUCT_DESIGNER_VERSION;
    }

    public static function exists(string $file) : bool
    {
        $file = self::safeFileName($file);
        return $file !== '' && is_file(self::absolutePath($file));
    }

    public static function addons(array $enabledFeatures) : array
    {
        $addons = [];
        foreach ((array) (self::manifest()['files'] ?? []) as $entry) {
            if (! is_array($entry) || ($entry['kind'] ?? '') !== 'premium-addon') {
                continue;
            }

            $file = self::safeFileName((string) ($entry['name'] ?? ''));
            $feature = trim((string) ($entry['feature'] ?? ''));
            $featureKey = sanitize_key($feature);
            if ($file === '' || $featureKey === '' || empty($enabledFeatures[$featureKey]) || ! self::exists($file)) {
                continue;
            }

            $addons[] = [
                'handle' => 'apd-addon-' . sanitize_key((string) preg_replace('/-addon\.js$/', '', $file)),
                'file' => $file,
                'feature' => $feature,
            ];
        }

        return $addons;
    }

    public static function importMapEntries() : array
    {
        $entries = [];
        $files = [];
        foreach (['module', 'fabric', 'babylon'] as $key) {
            $file = self::entrypoint($key);
            if ($file !== '') {
                $files[$file] = true;
            }
        }
        foreach ((array) (self::manifest()['files'] ?? []) as $entry) {
            $file = is_array($entry) ? self::safeFileName((string) ($entry['name'] ?? '')) : '';
            if ($file !== '' && str_ends_with(strtolower($file), '.js')) {
                $files[$file] = true;
            }
        }

        foreach (array_keys($files) as $file) {
            if (! self::exists($file)) {
                continue;
            }
            $target = self::url($file, true);
            $entries[self::url($file)] = $target;

            // Vite emits relative dynamic imports from the core bundle. Map the
            // expected core URL to an external provider when an add-on plugin
            // supplies the chunk from its own directory.
            $coreUrl = ASUKA_PRODUCT_DESIGNER_URL . self::BASE_PATH . $file;
            $entries[$coreUrl] = $target;
        }

        return $entries;
    }

    private static function providers() : array
    {
        // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- The apd_ hook is a documented integration API retained for backward compatibility.
        $providers = apply_filters('apd_designer_asset_providers', [[
            'id' => 'core',
            'path' => ASUKA_PRODUCT_DESIGNER_PATH . self::BASE_PATH,
            'url' => ASUKA_PRODUCT_DESIGNER_URL . self::BASE_PATH,
            'manifest' => ASUKA_PRODUCT_DESIGNER_PATH . self::BASE_PATH . 'manifest.json',
        ]]);

        $normalized = [];
        foreach (is_array($providers) ? $providers : [] as $provider) {
            if (! is_array($provider)) {
                continue;
            }
            $id = sanitize_key((string) ($provider['id'] ?? ''));
            $path = trailingslashit(wp_normalize_path((string) ($provider['path'] ?? '')));
            $url = trailingslashit(esc_url_raw((string) ($provider['url'] ?? '')));
            $manifest = wp_normalize_path((string) ($provider['manifest'] ?? ($path . 'manifest.json')));
            if ($id === '' || $path === '' || $url === '') {
                continue;
            }
            $normalized[$id] = compact('id', 'path', 'url', 'manifest');
        }

        return array_values($normalized);
    }

    private static function providerForFile(string $file) : array
    {
        $providerId = 'core';
        foreach ((array) (self::manifest()['files'] ?? []) as $entry) {
            if (is_array($entry) && (string) ($entry['name'] ?? '') === $file) {
                $providerId = sanitize_key((string) ($entry['_provider'] ?? 'core')) ?: 'core';
                break;
            }
        }

        $providers = self::providers();
        foreach ($providers as $provider) {
            if ((string) $provider['id'] === $providerId) {
                return $provider;
            }
        }

        return $providers[0] ?? [
            'id' => 'core',
            'path' => ASUKA_PRODUCT_DESIGNER_PATH . self::BASE_PATH,
            'url' => ASUKA_PRODUCT_DESIGNER_URL . self::BASE_PATH,
            'manifest' => ASUKA_PRODUCT_DESIGNER_PATH . self::BASE_PATH . 'manifest.json',
        ];
    }

    private static function safeFileName(string $file) : string
    {
        $file = wp_basename(trim($file));
        return preg_match('/^[a-z0-9][a-z0-9._-]*$/i', $file) ? $file : '';
    }
}
