<?php
/**
 * Vite development/HMR helpers for module scripts.
 *
 * @package FleekDash
 */

declare(strict_types=1);

namespace FleekDash\Libs\Assets;

use WP_HTML_Tag_Processor;

/**
 * Filter script tag
 *
 * @since 0.1.0
 *
 * @param string $handle Script handle.
 */
function filterScriptTag(string $handle): void
{
    add_filter('script_loader_tag', fn(...$args) => setScriptTypeAttribute($handle, ...$args), 10, 3);
}

/**
 * Add `type="module"` to a script tag
 *
 * @since 0.1.0
 * @since 0.8.0 Use WP_HTML_Tag_Processor.
 *
 * @param string $target_handle Handle of the script being targeted by the filter callback.
 * @param string $tag           Original script tag.
 * @param string $handle        Handle of the script that's currently being filtered.
 *
 * @return string Script tag with attribute `type="module"` added.
 */
function setScriptTypeAttribute(string $target_handle, string $tag, string $handle): string
{
    if ($target_handle !== $handle) {
        return $tag;
    }

    $processor = new WP_HTML_Tag_Processor($tag);

    if ($processor->next_tag('script')) {
        $processor->set_attribute('type', 'module');
    }

    return $processor->get_updated_html();
}

/**
 * Generate development asset src
 *
 * @since 0.1.0
 *
 * @param object $manifest Asset manifest.
 * @param string $entry    Asset entry name.
 */
function generateDevelopmentAssetSrc(object $manifest, string $entry): string
{
    $merged = "{$manifest->data->base}/{$entry}";
    $normalized_slashes = preg_replace('#/{2,}#', '/', $merged);

    return sprintf(
        '%s/%s',
        untrailingslashit($manifest->data->origin),
        trim((string) $normalized_slashes, '/'),
    );
}

/**
 * Register vite client script
 *
 * @since 0.1.0
 *
 * @param object $manifest Asset manifest.
 */
function registerViteClientScript(object $manifest): void
{
    if (wp_script_is(VITE_CLIENT_SCRIPT_HANDLE)) {
        return;
    }

    $src = generateDevelopmentAssetSrc($manifest, '@vite/client');

    // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
    wp_register_script(VITE_CLIENT_SCRIPT_HANDLE, $src, array(), null, false);
    filterScriptTag(VITE_CLIENT_SCRIPT_HANDLE);
}

/**
 * Inject react-refresh preamble script once, if needed
 *
 * @since 0.8.0
 *
 * @param object $manifest Asset manifest.
 */
function injectReactRefreshPreambleScript(object $manifest): void
{
    static $is_react_refresh_preamble_printed = false;

    if ($is_react_refresh_preamble_printed) {
        return;
    }

    if (!in_array('vite:react-refresh', $manifest->data->plugins, true)) {
        return;
    }

    $react_refresh_script_src = generateDevelopmentAssetSrc($manifest, '@react-refresh');
    $script_position = 'after';
    $script = <<<EOS
import RefreshRuntime from "{$react_refresh_script_src}";
RefreshRuntime.injectIntoGlobalHook(window);
window.\$RefreshReg$ = () => {};
window.\$RefreshSig$ = () => (type) => type;
window.__vite_plugin_react_preamble_installed__ = true;
EOS;

    wp_add_inline_script(VITE_CLIENT_SCRIPT_HANDLE, $script, $script_position);
    add_filter(
        'wp_inline_script_attributes',
        function (array $attributes) use ($script_position): array {
            if (isset($attributes['id']) && $attributes['id'] === VITE_CLIENT_SCRIPT_HANDLE . "-js-{$script_position}") {
                $attributes['type'] = 'module';
            }

            return $attributes;
        }
    );

    $is_react_refresh_preamble_printed = true;
}

/**
 * Load development asset
 *
 * @since 0.1.0
 *
 * @param object $manifest Asset manifest.
 * @param string $entry    Entrypoint to enqueue.
 * @param array  $options  Enqueue options.
 *
 * @return array|null Array containing registered scripts or NULL if the none was registered.
 */
function loadDevelopmentAsset(object $manifest, string $entry, array $options): ?array
{
    registerViteClientScript($manifest);
    injectReactRefreshPreambleScript($manifest);

    $dependencies = array_merge(
        array(VITE_CLIENT_SCRIPT_HANDLE),
        $options['dependencies'],
    );

    $src = generateDevelopmentAssetSrc($manifest, $entry);

    filterScriptTag($options['handle']);

    // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
    if (!registerScriptCompat($options['handle'], $src, $dependencies, null, $options['in_footer'])) {
        return null;
    }

    $assets = array(
        'scripts' => array($options['handle']),
        'styles' => $options['css-dependencies'],
    );

    /**
     * Filter registered development assets
     *
     * @param array  $assets   Registered assets.
     * @param object $manifest Manifest object.
     * @param string $entry    Entrypoint file.
     * @param array  $options  Enqueue options.
     */
    $assets = apply_filters('vite_for_wp__development_assets', $assets, $manifest, $entry, $options);

    return $assets;
}
