<?php
/**
 * Enqueuer - Handles asset enqueueing for admin and frontend.
 *
 * @package B3WVS\VariationSwatches\Assets
 */

namespace B3WVS\VariationSwatches\Assets;

defined( 'ABSPATH' ) || exit;

/**
 * Class Enqueuer
 */
class Enqueuer {
	/**
	 * Module script handles.
	 *
	 * @var array<int, string>
	 */
	private $module_handles = array();

	/**
	 * Plugin directory path.
	 *
	 * @var string
	 */
	private $plugin_path;

	/**
	 * Plugin directory URL.
	 *
	 * @var string
	 */
	private $plugin_url;

	/**
	 * Manifest reader.
	 *
	 * @var ManifestReader
	 */
	private $manifest_reader;

	/**
	 * Localizer.
	 *
	 * @var Localizer
	 */
	private $localizer;

	/**
	 * Constructor.
	 *
	 * @param string         $plugin_path Plugin path.
	 * @param string         $plugin_url  Plugin URL.
	 * @param ManifestReader $manifest_reader Manifest reader.
	 * @param Localizer      $localizer Data localizer.
	 */
	public function __construct( $plugin_path, $plugin_url, ManifestReader $manifest_reader, Localizer $localizer ) {
		$this->plugin_path     = $plugin_path;
		$this->plugin_url      = $plugin_url;
		$this->manifest_reader = $manifest_reader;
		$this->localizer       = $localizer;

		add_filter( 'script_loader_tag', array( $this, 'mark_module_scripts' ), 10, 2 );
	}

	/**
	 * Add type="module" to registered Vite script handles.
	 *
	 * @param string $tag Existing script tag.
	 * @param string $handle Script handle.
	 * @return string
	 */
	public function mark_module_scripts( $tag, $handle ) {
		if ( ! in_array( $handle, $this->module_handles, true ) ) {
			return $tag;
		}

		if ( false !== strpos( $tag, ' type=' ) ) {
			$updated_tag = preg_replace( '/\stype=(["\']).*?\1/i', ' type="module"', $tag );
			return is_string( $updated_tag ) ? $updated_tag : $tag;
		}

		return str_replace( '<script ', '<script type="module" ', $tag );
	}

	/**
	 * Enqueue an asset using the manifest or dev server fallback.
	 *
	 * @param string $handle   Asset handle.
	 * @param string $entry    Entry point in manifest.
	 * @param array  $deps     Optional. Script dependencies. Default [ 'wp-element' ].
	 * @param bool   $localize Optional. Whether to localize b3WvsData. Default true.
	 */
	public function enqueue_asset( $handle, $entry, $deps = array(), $localize = true ) {
		if ( defined( 'B3_WVS_DEV' ) && B3_WVS_DEV ) {
			$this->enqueue_dev_assets( $handle, $entry, $deps, $localize );
			return;
		}

		$manifest = $this->manifest_reader->get_manifest();

		if ( empty( $manifest ) || ! $this->manifest_reader->has_entry( $entry ) ) {
			return;
		}

		$asset_data = $this->manifest_reader->get_entry( $entry );
		$js_file    = $asset_data['file'];
		$css_files  = $this->collect_css_files( $entry );

		$js_path = $this->plugin_path . 'dist/' . $js_file;
		$js_ver  = $this->manifest_reader->get_file_version( $js_path );

		wp_enqueue_script(
			'b3-wvs-' . $handle,
			$this->plugin_url . 'dist/' . $js_file,
			$deps,
			$js_ver,
			true
		);

		if ( function_exists( 'wp_script_add_data' ) ) {
			wp_script_add_data( 'b3-wvs-' . $handle, 'type', 'module' );
		}

		$this->register_module_handle( 'b3-wvs-' . $handle );

		foreach ( $css_files as $index => $css_file ) {
			$css_path = $this->plugin_path . 'dist/' . $css_file;
			$css_ver  = $this->manifest_reader->get_file_version( $css_path );

			wp_enqueue_style(
				'b3-wvs-' . $handle . '-' . $index,
				$this->plugin_url . 'dist/' . $css_file,
				array(),
				$css_ver
			);
		}

		if ( $localize ) {
			$this->localizer->localize_data( $handle );
		}
	}

	/**
	 * Collect CSS files for a manifest entry and its static imports.
	 *
	 * Vite can attach shared CSS to imported chunks instead of the top-level
	 * entry. WordPress will not discover those files automatically, so we walk
	 * the manifest import graph and enqueue every referenced stylesheet once.
	 *
	 * @param string $entry Entry point in the manifest.
	 * @return array<int, string>
	 */
	private function collect_css_files( $entry ) {
		$manifest = $this->manifest_reader->get_manifest();
		if ( empty( $manifest ) || ! isset( $manifest[ $entry ] ) ) {
			return array();
		}

		$css_files = array();
		$visited   = array();
		$stack     = array( $entry );

		while ( ! empty( $stack ) ) {
			$current_entry = array_pop( $stack );
			if ( ! is_string( $current_entry ) || isset( $visited[ $current_entry ] ) ) {
				continue;
			}

			$visited[ $current_entry ] = true;
			if ( ! isset( $manifest[ $current_entry ] ) || ! is_array( $manifest[ $current_entry ] ) ) {
				continue;
			}

			$current_asset = $manifest[ $current_entry ];

			if ( isset( $current_asset['css'] ) && is_array( $current_asset['css'] ) ) {
				foreach ( $current_asset['css'] as $css_file ) {
					if ( is_string( $css_file ) && ! in_array( $css_file, $css_files, true ) ) {
						$css_files[] = $css_file;
					}
				}
			}

			if ( isset( $current_asset['imports'] ) && is_array( $current_asset['imports'] ) ) {
				foreach ( $current_asset['imports'] as $import_entry ) {
					if ( is_string( $import_entry ) && ! isset( $visited[ $import_entry ] ) ) {
						$stack[] = $import_entry;
					}
				}
			}
		}

		return $css_files;
	}

	/**
	 * Enqueue assets for development (Vite HMR).
	 *
	 * @param string $handle   Asset handle.
	 * @param string $entry    Entry point.
	 * @param array  $deps     Optional. Script dependencies. Default [ 'wp-element' ].
	 * @param bool   $localize Optional. Whether to localize b3WvsData. Default true.
	 */
	public function enqueue_dev_assets( $handle, $entry, $deps = array(), $localize = true ) {
		$dev_server_url   = defined( 'B3_WVS_DEV_SERVER' ) ? B3_WVS_DEV_SERVER : 'http://localhost:5173';
		$dev_server_url   = untrailingslashit( $dev_server_url );
		$canonical_handle = 'b3-wvs-' . $handle;

		$client_handle = 'b3-wvs-vite-client';
		if ( ! wp_script_is( $client_handle, 'registered' ) ) {
			// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Dev-server URLs must omit ver query strings or Vite returns untransformed modules.
			wp_register_script( $client_handle, $dev_server_url . '/@vite/client', array(), null, true );
			$this->register_module_handle( $client_handle );
		}

		$preamble_handle = 'b3-wvs-vite-react-preamble';
		if ( ! wp_script_is( $preamble_handle, 'registered' ) ) {
			// phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Dev-server URLs must omit ver query strings or the React refresh preamble breaks.
			wp_register_script( $preamble_handle, $dev_server_url . '/src/dev/react-refresh-preamble.ts', array(), null, true );
			$this->register_module_handle( $preamble_handle );
		}

		$entry_handle = 'b3-wvs-' . $handle . '-dev';
		// phpcs:disable WordPress.WP.EnqueuedResourceParameters.MissingVersion -- Dev-server URLs must omit ver query strings or Vite entry modules break.
		wp_register_script(
			$entry_handle,
			$dev_server_url . '/' . ltrim( $entry, '/' ),
			$deps,
			null,
			true
		);
		// phpcs:enable
		$this->register_module_handle( $entry_handle );

		if ( ! wp_script_is( $canonical_handle, 'registered' ) ) {
			wp_register_script(
				$canonical_handle,
				false,
				array( $entry_handle ),
				B3_WVS_VERSION,
				true
			);
		}

		if ( ! wp_script_is( $client_handle, 'enqueued' ) ) {
			wp_enqueue_script( $client_handle );
		}
		if ( ! wp_script_is( $preamble_handle, 'enqueued' ) ) {
			wp_enqueue_script( $preamble_handle );
		}
		wp_enqueue_script( $entry_handle );

		if ( $localize ) {
			$this->localizer->localize_data( $handle );
		}
	}

	/**
	 * Track a script handle as a module script.
	 *
	 * @param string $handle Script handle.
	 */
	private function register_module_handle( $handle ) {
		if ( ! in_array( $handle, $this->module_handles, true ) ) {
			$this->module_handles[] = $handle;
		}
	}
}
