<?php
/**
 * AssetLoader class.
 *
 * Responsible for registering and enqueuing JavaScript and CSS assets
 * for both the core plugin and dynamically registered modules.
 *
 * @package Ilabs\Firevert\Core\Services\Loader
 */

declare( strict_types=1 );

namespace Ilabs\Firevert\Core\Services\Loader;

use Ilabs\Firevert\Core\Services\Resolver\PageContextResolver;
use Ilabs\Firevert\Core\Services\Resolver\PathResolver;

/**
 * Loads JS/CSS assets for Core and registered modules.
 */
final class AssetLoader {

	/**
	 * Asset context constants
	 */
	private const CONTEXT_ADMIN    = 'admin';
	private const CONTEXT_FRONTEND = 'frontend';

	/**
	 * Asset type constants
	 */
	private const TYPE_CSS = 'css';
	private const TYPE_JS  = 'js';

	/**
	 * Path resolver instance.
	 *
	 * @var PathResolver
	 */
	private PathResolver $paths;

	/**
	 * Registered core asset definitions.
	 *
	 * @var array<string, array>
	 */
	private array $core_assets = array();

	/**
	 * Registered module asset definitions.
	 *
	 * @var array<string, array>
	 */
	private array $module_assets = array();

	/**
	 * AssetLoader constructor.
	 *
	 * @param PathResolver $paths Path resolver instance.
	 */
	public function __construct( PathResolver $paths ) {
		$this->paths = $paths;
	}

	/**
	 * Registers default admin and frontend assets for the core plugin.
	 *
	 * @return void
	 */
	public function register_core_assets(): void {
		$this->core_assets = array(
			'admin_css'    => array(
				'handle' => 'firevert-admin',
				'path'   => 'css/admin.min.css',
			),
			'admin_js'     => array(
				'handle' => 'firevert-admin-js',
				'path'   => 'js/admin.min.js',
				'deps'   => array( 'jquery' ),
			),
			'frontend_css' => array(
				'handle' => 'firevert-frontend',
				'path'   => 'css/frontend.min.css',
			),
			'frontend_js'  => array(
				'handle' => 'firevert-frontend-js',
				'path'   => 'js/frontend.min.js',
				'deps'   => array(),
			),
		);
	}

	/**
	 * Registers asset definitions for a specific module.
	 *
	 * @param string $slug Module slug.
	 * @param array  $assets List of asset definitions.
	 * @param string $base_path Optional base path for assets.
	 * @param string $base_url Optional base URL for assets.
	 *
	 * @return void
	 */
	public function register_module_assets(
		string $slug,
		array $assets,
		string $base_path = '',
		string $base_url = ''
	): void {
		$this->module_assets[ $slug ] = array(
			'assets'    => $assets,
			'base_path' => $base_path,
			'base_url'  => $base_url,
		);
	}

	/**
	 * Enqueues admin JS/CSS for core and registered modules.
	 *
	 * @return void
	 */
	public function enqueue_admin_assets(): void {
		$this->enqueue_assets_by_context( self::CONTEXT_ADMIN );
	}

	/**
	 * Enqueues frontend JS/CSS for core and registered modules.
	 *
	 * @return void
	 */
	public function enqueue_frontend_assets(): void {
		$this->enqueue_assets_by_context( self::CONTEXT_FRONTEND );
	}

	/**
	 * Internal logic for enqueuing assets based on context.
	 *
	 * @param string $context 'admin' or 'frontend'.
	 *
	 * @return void
	 */
	private function enqueue_assets_by_context( string $context ): void {
		$this->enqueue_core_assets( $context );
		$this->enqueue_module_assets( $context );
	}

	/**
	 * Enqueues core assets for a specific context.
	 *
	 * @param string $context The context to enqueue assets for.
	 *
	 * @return void
	 */
	private function enqueue_core_assets( string $context ): void {
		$css_key = "{$context}_css";
		$js_key  = "{$context}_js";

		$this->enqueue_asset_if_exists(
			self::TYPE_CSS,
			"css/{$context}.min.css",
			$this->core_assets[ $css_key ]['handle'] ?? '',
			$this->core_assets[ $css_key ]['path'] ?? '',
			$this->core_assets[ $css_key ]['deps'] ?? array(),
			true
		);

		$handle = $this->core_assets[ $js_key ]['handle'] ?? '';
		$this->enqueue_asset_if_exists(
			self::TYPE_JS,
			"js/{$context}.min.js",
			$handle,
			$this->core_assets[ $js_key ]['path'] ?? '',
			$this->core_assets[ $js_key ]['deps'] ?? array(),
			true
		);

		if ( 'frontend' === $context && ! empty( $handle ) ) {
			wp_localize_script(
				$handle,
				'firevert',
				array(
					'restUrl'   => esc_url_raw( rest_url( 'firevert/v1/' ) ),
					'restNonce' => wp_create_nonce( 'wp_rest' ),
				)
			);
		}
	}

	/**
	 * Enqueues module assets for a specific context.
	 *
	 * @param string $context The context to enqueue assets for.
	 *
	 * @return void
	 */
	private function enqueue_module_assets( string $context ): void {
		$page_context = new PageContextResolver();

		foreach ( $this->module_assets as $slug => $data ) {
			$assets    = $data['assets'];
			$base_path = ! empty( $data['base_path'] )
				? $data['base_path']
				: $this->paths->get_module_path( $slug, 'dist' );

			$base_url = ! empty( $data['base_url'] )
				? $data['base_url']
				: $this->paths->get_module_url( $slug, 'dist' );

			foreach ( $assets as $asset ) {
				if ( ( $asset['context'] ?? self::CONTEXT_ADMIN ) !== $context ) {
					continue;
				}

				if (
					$page_context->is_firevert_settings_page()
					&& 'modules' === $page_context->get_tab()
					&& $page_context->get_module() !== $slug
				) {
					continue;
				}

				$handle = "firevert-{$slug}-{$asset['type']}";
				$path   = $base_path . '/' . $asset['filename'];
				$url    = $base_url . '/' . $asset['filename'];

				$this->enqueue_asset_if_exists(
					$asset['type'],
					$path,
					$handle,
					$url,
					$asset['deps'] ?? array(),
				);

				if ( 'frontend' === $context && 'js' === $asset['type'] ) {
					wp_localize_script(
						$handle,
						'firevert',
						array(
							'restUrl'   => esc_url_raw( rest_url( 'firevert/v1/' ) ),
							'restNonce' => wp_create_nonce( 'wp_rest' ),
						)
					);
				}
			}
		}
	}

	/**
	 * Enqueues an asset if the file exists.
	 *
	 * @param string $type Asset type (css or js).
	 * @param string $path File path to check existence.
	 * @param string $handle Asset handle.
	 * @param string $url Asset URL.
	 * @param array  $deps Asset dependencies.
	 * @param bool   $is_core Whether this is a core asset.
	 *
	 * @return void
	 */
	private function enqueue_asset_if_exists(
		string $type,
		string $path,
		string $handle,
		string $url,
		array $deps = array(),
		bool $is_core = false
	): void {
		if ( $is_core ) {
			$url  = $this->paths->get_core_dist_url( $path );
			$path = $this->paths->get_core_dist_path( $path );
		}

		if ( file_exists( $path ) ) {
			$version = $this->get_asset_version( $path );

			if ( self::TYPE_CSS === $type ) {
				wp_enqueue_style( $handle, $url, array(), $version );
			} elseif ( self::TYPE_JS === $type ) {
				wp_enqueue_script( $handle, $url, $deps, $version, true );
			}
		}
	}

	/**
	 * Returns file version based on its modification time.
	 *
	 * @param string $path Absolute file path.
	 *
	 * @return string|null
	 */
	private function get_asset_version( string $path ): ?string {
		return file_exists( $path ) ? (string) filemtime( $path ) : null;
	}
}
