<?php
/**
 * Assets service for registering and enqueuing scripts and styles.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Services;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use Awesomemotive\Rewardswp\Settings\Options\General\BrandColorOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\BrandForegroundModeOption;
use Awesomemotive\Rewardswp\Settings\Options\RewardsWidget\LauncherPositionOption;

use function Awesomemotive\Rewardswp\Helpers\derive_dark_foreground;
use function Awesomemotive\Rewardswp\Helpers\get_contrasting_text_color;
use function Awesomemotive\Rewardswp\Helpers\get_option_value;
use function Awesomemotive\Rewardswp\Helpers\is_rewardswp_admin_page;
use function Awesomemotive\Rewardswp\Helpers\is_rewardswp_lite_plan;

/**
 * Class AssetsService
 *
 * Handles the registration and enqueueing of scripts and styles.
 *
 * @since 1.0.0
 */
class AssetsService {
	/**
	 * The main script handle name.
	 *
	 * Since our JS is centered in one file `main.js`, we should make the script name available
	 * everywhere in our application, so other scripts can use this name for other manipulations,
	 * like using wp_inline_script() to add data before the script loads.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const MAIN_SCRIPT_HANDLE = 'rewardswp-main';

	/**
	 * The admin script handle name.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const ADMIN_SCRIPT_HANDLE = 'rewardswp-admin';

	/**
	 * The main style handle name.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const MAIN_STYLE_HANDLE = 'rewardswp-main-styles';

	/**
	 * The admin style handle name.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const ADMIN_STYLE_HANDLE = 'rewardswp-admin-styles';

	/**
	 * The frontend components style handle name.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	const COMPONENTS_STYLE_HANDLE = 'rewardswp-components';

	/**
	 * Base URL for assets.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	private string $base_url;

	/**
	 * Assets version.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	private string $version;

	/**
	 * Constructor.
	 *
	 * @since 1.0.0
	 *
	 * @param string $base_url The base URL for assets (usually points to /dist/ folder).
	 * @param string $version The version of the assets.
	 */
	public function __construct( string $base_url, string $version ) {
		$this->base_url = untrailingslashit( $base_url );
		$this->version  = $version;

		add_action( 'wp_enqueue_scripts', [ $this, 'register_main_assets' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_menu_css' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'register_main_assets' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'register_admin_assets' ] );
	}

	/**
	 * Get the main CSS filename based on environment and plan.
	 *
	 * In development, both main-pro.css and main-lite.css exist.
	 * In production builds, only main.css exists (renamed by build script).
	 *
	 * @since 1.2.0
	 *
	 * @return string The main CSS filename.
	 */
	public function get_main_css_filename(): string {
		if ( file_exists( AM_REWARDSWP_PLUGIN_DIR . 'dist/main-pro.css' ) ) {
			return is_rewardswp_lite_plan() ? 'main-lite.css' : 'main-pro.css';
		}

		return 'main.css';
	}

	/**
	 * Retrieve the full URL for an asset file.
	 *
	 * @since 1.0.0
	 *
	 * @param string $src The filename.
	 * @return string The file URL.
	 */
	public function get_file_url( string $src ): string {
		$version = $this->version;

		// In development, use file modification time for cache busting.
		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			$file_path = AM_REWARDSWP_PLUGIN_DIR . 'dist/' . $src;
			if ( file_exists( $file_path ) ) {
				$version = (string) filemtime( $file_path );
			}
		}

		return sprintf( '%s/%s?ver=%s', $this->base_url, $src, $version );
	}

	/**
	 * Register a script.
	 *
	 * @since 1.0.0
	 *
	 * @param string      $handle The handle for the script.
	 * @param string|null $src Optional. The source URL for the script. If provided, registers the script before enqueuing.
	 * @param array       $deps Optional. An array of dependencies. Default empty array.
	 * @param bool|array  $in_footer Optional. The loading strategy to use. Default true to load on the footer.
	 */
	public function register_script( string $handle, string $src, array $deps = [], $in_footer = true ) {
		wp_register_script( $handle, $this->get_file_url( $src ), $deps, $this->version, $in_footer );
	}

	/**
	 * Register a style.
	 *
	 * @since 1.0.0
	 *
	 * @param string $handle The handle for the style.
	 * @param string $src The source URL for the style.
	 * @param array  $deps Optional. An array of dependencies. Default empty array.
	 * @param string $media Optional. The media for which this stylesheet has been defined. Default 'all'.
	 */
	public function register_style( string $handle, string $src, array $deps = [], string $media = 'all' ) {
		wp_register_style(
			$handle,
			empty( $src )
				? false
				: $this->get_file_url( $src ),
			$deps,
			$this->version,
			$media
		);
	}

	/**
	 * Enqueue a script. If the script is not registered, it will be registered first.
	 *
	 * @since 1.0.0
	 *
	 * @param string      $handle The handle for the script.
	 * @param string|null $src Optional. The source URL for the script. If provided, registers the script before enqueuing.
	 * @param array       $deps Optional. An array of dependencies. Default empty array.
	 * @param bool|array  $in_footer Optional. The loading strategy to use. Default true to load on the footer.
	 */
	public function enqueue_script( string $handle, ?string $src = null, array $deps = [], $in_footer = true ) {
		// Register the script if $src is provided and the script is not yet registered.
		if ( null !== $src && ! wp_script_is( $handle, 'registered' ) ) {
			$this->register_script( $handle, $src, $deps, $in_footer );
		}

		// Enqueue the script.
		wp_enqueue_script( $handle );
	}

	/**
	 * Enqueue a style. If the style is not registered, it will be registered first.
	 *
	 * @since 1.0.0
	 *
	 * @param string      $handle The handle for the style.
	 * @param string|null $src Optional. The source URL for the style. If provided, registers the style before enqueuing.
	 * @param array       $deps Optional. An array of dependencies. Default empty array.
	 * @param string      $media Optional. The media for which this stylesheet has been defined. Default 'all'.
	 */
	public function enqueue_style( string $handle, ?string $src = null, array $deps = [], string $media = 'all' ) {
		// Register the style if $src is provided and the style is not yet registered.
		if ( null !== $src && ! wp_style_is( $handle, 'registered' ) ) {
			$this->register_style( $handle, $src, $deps, $media );
		}

		// Enqueue the style.
		wp_enqueue_style( $handle );
	}

	/**
	 * Check the conditions to load the main assets.
	 *
	 * The main assets are mainly responsible to render the panel.
	 *
	 * @since 1.0.0
	 *
	 * @return bool
	 */
	private function should_load_main_assets(): bool {
		/**
		 * Filter whether the main assets should be loaded.
		 *
		 * @since 1.0.0
		 *
		 * @param bool $should_load Indicates if the main assets should be loaded.
		 */
		return apply_filters(
			'rewardswp_should_load_main_assets',
			! is_admin() || is_rewardswp_admin_page() // Only load on frontend by default.
		);
	}

	/**
	 * Register main assets.
	 *
	 * @since 1.0.0
	 */
	public function register_main_assets() {
		if ( ! $this->should_load_main_assets() ) {
			return;
		}

		// Register the main CSS file.
		$this->register_style( self::MAIN_STYLE_HANDLE, $this->get_main_css_filename() );

		// Register and enqueue frontend components CSS (banners, notices, etc.).
		if ( ! is_admin() ) {
			$this->enqueue_style( self::COMPONENTS_STYLE_HANDLE, 'components.css' );
		}

		// Register the main JS file.
		$this->register_script(
			self::MAIN_SCRIPT_HANDLE,
			'main.js',
			[],
			[
				'strategy'  => 'async',
				'in_footer' => true,
			]
		);
	}

	/**
	 * Register admin assets.
	 *
	 * @since 1.0.0
	 */
	public function register_admin_assets() {
		if ( ! is_rewardswp_admin_page() ) {
			return;
		}

		// Register the admin styles.
		$this->register_script( self::ADMIN_SCRIPT_HANDLE, 'admin.js' );
		$this->register_style( self::ADMIN_STYLE_HANDLE, 'admin-styles.css' );
	}

	/**
	 * Enqueue menu css.
	 *
	 * This should be loaded everywhere in the admin for Lite plans, since it applies styles to the Upgrade to Pro.
	 *
	 * @since 1.0.0
	 */
	public function enqueue_admin_menu_css(): void {
		if ( ! is_rewardswp_lite_plan() ) {
			return;
		}

		$this->enqueue_style( 'rewardswp-admin-menu-styles', 'admin-menu.css' ); // Enqueue admin menu styles.
	}

	/**
	 * Print brand CSS variables as inline styles.
	 *
	 * This method outputs the brand color variables that are dynamically set
	 * based on user configuration. Used by both frontend and admin contexts.
	 *
	 * @since 1.1.0
	 *
	 * @return void
	 */
	public function print_brand_css_variables(): void {
		$brand_color                 = get_option_value( BrandColorOption::class );
		$brand_foreground_color_mode = get_option_value( BrandForegroundModeOption::class );

		// Determine the foreground color based on mode setting.
		switch ( $brand_foreground_color_mode ) {
			case 'light':
				$brand_foreground_color = '#ffffff';
				break;
			case 'dark':
				$brand_foreground_color = derive_dark_foreground( $brand_color );
				break;
			case 'auto':
			default:
				$brand_foreground_color = get_contrasting_text_color( $brand_color );
				break;
		}

		$css = sprintf(
			':root, rewards-panel, rewards-launcher {
				--color-rwp-brand: %s;
				--color-rwp-brand-hover: color-mix(in srgb, white 20%%, %s);
				--color-rwp-brand-foreground: %s;
				--color-rwp-widget-button-text: %s;
				--rwp-header-text: %s;
				--rwp-button-text: %s;
			}',
			esc_attr( $brand_color ),
			esc_attr( $brand_color ),
			esc_attr( $brand_foreground_color ),
			esc_attr( $brand_foreground_color ),
			esc_attr( $brand_foreground_color ),
			esc_attr( $brand_foreground_color )
		);

		/*
		 * Alpine x-cloak FOUC prevention is admin-only. The frontend widget renders
		 * inside a shadow root and scopes its own [x-cloak] rule via addXCloakStyle()
		 * in assets/js/shared/utils.js, so a global light-DOM rule would only affect
		 * third-party Alpine consumers on the page (e.g. Breakdance Builder). See #5779.
		 */
		if ( is_admin() ) {
			$css .= '
			[x-cloak] { display: none !important; }';
		}

		// Add panel positioning CSS variables.
		$launcher_position = get_option_value( LauncherPositionOption::class );
		$is_left           = 'left' === $launcher_position;

		if ( is_admin() ) {
			// Admin preview: absolute positioning within preview container.
			$css .= sprintf(
				'
			:root {
				--rwp-panel-position: absolute;
				--rwp-panel-top: auto;
				--rwp-panel-bottom: 96px;
				--rwp-panel-left: %s;
				--rwp-panel-right: %s;
				--rwp-panel-transform-origin: %s;
				--rwp-launcher-position: absolute;
				--rwp-launcher-bottom: 24px;
				--rwp-launcher-left: %s;
				--rwp-launcher-right: %s;
			}',
				$is_left ? '24px' : 'auto',
				$is_left ? 'auto' : '24px',
				$is_left ? 'bottom left' : 'bottom right',
				$is_left ? '24px' : 'auto',
				$is_left ? 'auto' : '24px'
			);
		} else {
			// Frontend: fixed positioning.
			$css .= sprintf(
				'
			:root {
				--rwp-panel-position: fixed;
				--rwp-panel-top: auto;
				--rwp-panel-bottom: 96px;
				--rwp-panel-left: %s;
				--rwp-panel-right: %s;
				--rwp-panel-transform-origin: %s;
				--rwp-launcher-position: fixed;
				--rwp-launcher-bottom: 24px;
				--rwp-launcher-left: %s;
				--rwp-launcher-right: %s;
			}',
				$is_left ? '24px' : 'auto',
				$is_left ? 'auto' : '24px',
				$is_left ? 'bottom left' : 'bottom right',
				$is_left ? '24px' : 'auto',
				$is_left ? 'auto' : '24px'
			);
		}

		$handle = is_admin()
			? self::ADMIN_STYLE_HANDLE
			: self::COMPONENTS_STYLE_HANDLE;

		wp_add_inline_style( $handle, $css );
	}

	/**
	 * Get the appropriate Select2 handle to use.
	 *
	 * Checks for existing Select2 libraries in this priority order:
	 * 1. selectWoo (WooCommerce)
	 * 2. select2 (generic/other plugins)
	 * 3. Our own Select2 version
	 *
	 * @since 1.0.0
	 *
	 * @return string The Select2 handle to use.
	 */
	public function get_select2_handle(): string {
		// Check for WooCommerce's selectWoo first (most compatible).
		if ( wp_script_is( 'selectWoo', 'registered' ) || wp_script_is( 'selectWoo', 'enqueued' ) ) {
			return 'selectWoo';
		}

		// Check for generic select2.
		if ( wp_script_is( 'select2', 'registered' ) || wp_script_is( 'select2', 'enqueued' ) ) {
			return 'select2';
		}

		// Use our own version as fallback.
		return 'rewardswp-select2';
	}

	/**
	 * Ensure Select2 is available and return the handle.
	 *
	 * @since 1.0.0
	 *
	 * @return string The Select2 handle that's ready to use.
	 */
	public function ensure_select2_loaded(): string {
		$handle = $this->get_select2_handle();

		// If we have an existing Select2 (WooCommerce or other plugin).
		if ( 'rewardswp-select2' !== $handle ) {
			// Ensure the existing one is enqueued.
			wp_enqueue_script( $handle );

			// Try to enqueue the corresponding CSS.
			$style_handle = str_replace( [ 'selectWoo', 'select2' ], [ 'selectWoo', 'select2' ], $handle );
			if ( wp_style_is( $style_handle, 'registered' ) ) {
				wp_enqueue_style( $style_handle );
			}
		} else {
			// No existing Select2 found, register and enqueue our own.
			$this->register_script( 'rewardswp-select2', 'select2.min.js', [ 'jquery' ] );
			$this->register_style( 'rewardswp-select2-styles', 'select2.min-styles.css' );

			wp_enqueue_script( 'rewardswp-select2' );
			wp_enqueue_style( 'rewardswp-select2-styles' );
		}

		return $handle;
	}
}
