<?php
/**
 * Asset Class
 *
 * @package errigal
 * @since 1.1.0
 */

namespace Errigal\Shared;

defined( 'ABSPATH' ) || exit;

use Errigal\Constant\Errigal_Asset_Hook as Asset_Hook;
use Errigal\Customiser\{
	Errigal_Custom_Colour_Scheme as Colour_Scheme,
	Errigal_Custom_Heading_Style as Heading_Style,
};
use Errigal\Shared\{
	Errigal_Theme_Info as Theme_Info,
	Errigal_Utility as Utility,
	Errigal_Validator as Validator,
};
use WP_Screen;

/**
 * This class is in charge of providing asset functions.
 *
 * @since 1.1.0
 */
class Errigal_Asset {
	/**
	 * Editor.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const EDITOR = 'editor';

	/**
	 * Dependency script: customize-controls.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_CUSTOMIZE_CONTROLS = 'customize-controls';

	/**
	 * Dependency script: customize-preview.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_CUSTOMIZE_PREVIEW = 'customize-preview';

	/**
	 * Dependency script: jquery.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_JQUERY = 'jquery';

	/**
	 * Dependency script: wp-blocks.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_WP_BLOCKS = 'wp-blocks';

	/**
	 * Dependency script: wp-dom.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_WP_DOM = 'wp-dom';

	/**
	 * Dependency script: wp-i18n.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const DEP_SCRIPT_WP_I18N = 'wp-i18n';

	/**
	 * Script arg: strategy.
	 *
	 * @since 1.2.0
	 *
	 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/
	 *
	 * @var string
	 */
	public const SCRIPT_ARG_STRATEGY = 'strategy';

	/**
	 * Script arg: in_footer.
	 *
	 * @since 1.2.0
	 *
	 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/
	 *
	 * @var string
	 */
	public const SCRIPT_ARG_IN_FOOTER = 'in_footer';

	/**
	 * Stylesheet name: style.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	public const STYLE_THEME = 'style';

	/**
	 * Directory: CSS files.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	protected const DIR_CSS = '/assets/css/';

	/**
	 * Directory: JavaScript files.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	protected const DIR_JS = '/assets/js/';

	/**
	 * File extension: CSS.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	private const EXTENSION_CSS = '.min.css';

	/**
	 * File extension: JavaScript.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	private const EXTENSION_JS = '.min.js';

	/**
	 * Regular expression: Non-alphanumeric or underscore characters.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	private const REGEX_ALPHANUMERIC_UNDERSCORE = '/[^a-zA-Z0-9_]/';

	/**
	 * Regular expression: Starts with one or more non-alphabetic characters.
	 *
	 * @since 1.1.0
	 *
	 * @var string
	 */
	private const REGEX_STARTS_WITH_NON_ALPHA = '/^[^a-zA-Z]+/';

	/**
	 * Enqueues script.
	 *
	 * @since 1.1.0
	 *
	 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_script/
	 *
	 * @param string     $script_file_name_without_extension Script file name without extension.
	 * @param string[]   $deps                               Optional. Registered script handles this script depends on.
	 *                                                       Default empty array.
	 * @param array|bool $args                               Optional. Additional script loading strategies.
	 *                                                       See wp_enqueue_script for information on accepted arguments.
	 *                                                       Default array with 'in_footer' set to true.
	 * @return string Name of script.
	 */
	public static function enqueue_script( $script_file_name_without_extension, $deps = array(), $args = array() ) {
		if ( ! is_array( $deps ) || ! is_array( $args ) ) {
			return '';
		}

		$args = wp_parse_args(
			$args,
			array(
				self::SCRIPT_ARG_IN_FOOTER => true,
			)
		);

		$script_file_name_without_extension = sanitize_file_name(
			/**
			 * Filters script file name without extension.
			 *
			 * @since 1.1.0
			 *
			 * @param string $script_file_name_without_extension Script file name without extension.
			 */
			apply_filters(
				Asset_Hook::SCRIPT_FILE_NAME,
				$script_file_name_without_extension
			)
		);

		if ( ! Validator::is_non_empty_string( $script_file_name_without_extension ) ) {
			return '';
		}

		$ver = self::get_asset_version();

		if ( ! Validator::is_non_empty_string( $ver ) ) {
			return '';
		}

		$handle = self::get_prefixed_handle( $script_file_name_without_extension );
		$src    = static::get_asset_root_uri() . static::DIR_JS . $script_file_name_without_extension . self::EXTENSION_JS;

		wp_enqueue_script(
			sanitize_key( $handle ),
			esc_url( $src ),
			$deps,
			esc_attr( $ver ),
			$args
		);

		return $handle;
	}

	/**
	 * Localises script.
	 *
	 * @since 1.1.0
	 *
	 * @param string $handle      Script handle data will be attached to.
	 * @param string $object_name Name for JavaScript object. Passed directly, so it should be qualified JS variable.
	 *                            Example: '/[a-zA-Z0-9_]+/'.
	 * @param array  $l10n        Associative array of localised data.
	 * @return bool True if script was successfully localised, false otherwise.
	 */
	public static function localise_script( $handle, $object_name, $l10n ) {
		if (
			! Validator::is_non_empty_string( $handle ) ||
			! Validator::is_non_empty_string( $object_name ) ||
			! is_array( $l10n )
		) {
			return false;
		}

		return wp_localize_script(
			sanitize_key( $handle ),
			self::sanitise_js_variable( $object_name ),
			$l10n
		);
	}

	/**
	 * Enqueues CSS stylesheet.
	 *
	 * @since 1.1.0
	 *
	 * @link https://developer.wordpress.org/reference/functions/wp_enqueue_style/
	 *
	 * @param string   $handle Name of stylesheet. Should be unique.
	 * @param string   $src    Full URL of stylesheet, or path of stylesheet relative to WordPress root directory.
	 * @param string[] $deps   Optional. Registered stylesheet handles this stylesheet depends on. Default empty array.
	 * @return void
	 */
	public static function enqueue_style( $handle, $src, $deps = array() ) {
		if (
			! Validator::is_non_empty_string( $handle ) ||
			! Validator::is_non_empty_string( $src ) ||
			! is_array( $deps )
		) {
			return;
		}

		/**
		 * Filters stylesheet version.
		 *
		 * @since 1.1.0
		 *
		 * @param string $stylesheet_version Stylesheet version.
		 */
		$ver = apply_filters(
			Asset_Hook::STYLESHEET_VERSION,
			self::get_asset_version()
		);

		if ( ! Validator::is_non_empty_string( $ver ) ) {
			return;
		}

		wp_enqueue_style(
			self::get_prefixed_handle( $handle ),
			esc_url( $src ),
			$deps,
			esc_attr( $ver )
		);
	}

	/**
	 * Adds extra CSS styles to registered stylesheet.
	 *
	 * @since 1.1.0
	 *
	 * @param string $handle Name of stylesheet to add extra styles to.
	 * @param string $data   String containing CSS styles to be added.
	 * @return bool True on success, false on failure.
	 */
	public static function add_inline_style( $handle, $data ) {
		if (
			! Validator::is_non_empty_string( $handle ) ||
			! Validator::is_non_empty_string( $data )
		) {
			return false;
		}

		return wp_add_inline_style(
			self::get_prefixed_handle( $handle ),
			wp_strip_all_tags( $data )
		);
	}

	/**
	 * Gets theme stylesheet URI.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public static function get_theme_stylesheet_uri() {
		$theme_stylesheet_file_name_without_extension = sanitize_file_name(
			/**
			 * Filters theme stylesheet file name without extension.
			 *
			 * @since 1.1.0
			 *
			 * @param string $file_name_without_extension File name without extension.
			 */
			apply_filters(
				Asset_Hook::THEME_STYLESHEET_FILE_NAME_WITHOUT_EXTENSION,
				self::get_theme_stylesheet_file_name_without_extension()
			)
		);

		if ( ! Validator::is_non_empty_string( $theme_stylesheet_file_name_without_extension ) ) {
			return '';
		}

		return self::get_stylesheet_uri( $theme_stylesheet_file_name_without_extension );
	}

	/**
	 * Gets editor stylesheet URI.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	public static function get_editor_stylesheet_uri() {
		$editor_stylesheet_file_name_without_extension = sanitize_file_name(
			/**
			 * Filters editor stylesheet file name without extension.
			 *
			 * @since 1.1.0
			 *
			 * @param string $file_name_without_extension File name without extension.
			 */
			apply_filters(
				Asset_Hook::EDITOR_STYLESHEET_FILE_NAME_WITHOUT_EXTENSION,
				self::get_editor_stylesheet_file_name_without_extension()
			)
		);

		if ( ! Validator::is_non_empty_string( $editor_stylesheet_file_name_without_extension ) ) {
			return '';
		}

		return self::get_stylesheet_uri( $editor_stylesheet_file_name_without_extension );
	}

	/**
	 * Gets stylesheet URI.
	 *
	 * @since 1.1.0
	 *
	 * @param string $stylesheet_file_name_without_extension Stylesheet file name without extension.
	 * @return string
	 */
	public static function get_stylesheet_uri( $stylesheet_file_name_without_extension ) {
		$stylesheet_file_name_without_extension = sanitize_file_name(
			/**
			 * Filters stylesheet file name without extension.
			 *
			 * @since 1.1.0
			 *
			 * @param string $stylesheet_file_name_without_extension Stylesheet file name without extension.
			 */
			apply_filters(
				Asset_Hook::STYLESHEET_FILE_NAME,
				$stylesheet_file_name_without_extension
			)
		);

		if ( ! Validator::is_non_empty_string( $stylesheet_file_name_without_extension ) ) {
			return '';
		}

		/**
		 * Filters stylesheet root URI.
		 *
		 * @since 1.1.0
		 *
		 * @param string $stylesheet_root_uri Stylesheet root uri.
		 */
		$root_uri = apply_filters(
			Asset_Hook::STYLESHEET_ROOT_URI,
			self::get_asset_root_uri()
		);

		if ( ! Validator::is_non_empty_string( $root_uri ) ) {
			return '';
		}

		return esc_url(
			$root_uri . static::DIR_CSS . $stylesheet_file_name_without_extension . self::EXTENSION_CSS
		);
	}

	/**
	 * Determines whether block editor is loaded on current screen.
	 *
	 * @since 1.2.0
	 *
	 * @return bool
	 */
	public static function is_block_editor() {
		if ( ! function_exists( 'get_current_screen' ) ) {
			return false;
		}

		$current_screen = get_current_screen();

		return self::is_wp_screen( $current_screen ) &&
			method_exists( $current_screen, 'is_block_editor' ) &&
			$current_screen->is_block_editor();
	}

	/**
	 * Gets asset root URI.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	private static function get_asset_root_uri() {
		if ( self::is_parent_theme_asset() ) {
			return get_template_directory_uri();
		}

		return get_stylesheet_directory_uri();
	}

	/**
	 * Gets asset version.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	private static function get_asset_version() {
		return Theme_Info::get_theme_version( self::is_parent_theme_asset() );
	}

	/**
	 * Gets prefixed handle.
	 *
	 * @since 1.1.0
	 *
	 * @param string $handle Handle to be prefixed.
	 * @return string
	 */
	private static function get_prefixed_handle( $handle ) {
		if ( ! Validator::is_non_empty_string( $handle ) ) {
			return '';
		}

		return sanitize_key(
			Utility::join(
				Utility::SEPARATOR_HYPHEN,
				sanitize_title( Theme_Info::get_theme_name( self::is_parent_theme_asset() ) ),
				sanitize_title( $handle )
			)
		);
	}

	/**
	 * Gets theme stylesheet file name without extension.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	private static function get_theme_stylesheet_file_name_without_extension() {
		return sanitize_file_name(
			Utility::join(
				Utility::SEPARATOR_HYPHEN,
				self::STYLE_THEME,
				Colour_Scheme::get_colour_scheme()
			)
		);
	}

	/**
	 * Gets editor stylesheet file name without extension.
	 *
	 * @since 1.1.0
	 *
	 * @return string
	 */
	private static function get_editor_stylesheet_file_name_without_extension() {
		$theme_stylesheet_file_name_without_extension = self::get_theme_stylesheet_file_name_without_extension();

		if ( ! Validator::is_non_empty_string( $theme_stylesheet_file_name_without_extension ) ) {
			return '';
		}

		return sanitize_file_name(
			Utility::join(
				Utility::SEPARATOR_HYPHEN,
				self::EDITOR,
				$theme_stylesheet_file_name_without_extension,
				Heading_Style::get_editor_theme_stylesheet_file_name_suffix()
			)
		);
	}

	/**
	 * Determines whether asset belongs to parent theme.
	 *
	 * @since 1.1.0
	 *
	 * @return bool
	 */
	private static function is_parent_theme_asset() {
		return __CLASS__ === get_called_class();
	}

	/**
	 * Sanitises JS variable.
	 *
	 * @since 1.1.0
	 *
	 * @param string $text Text to be sanitised.
	 * @return string
	 */
	private static function sanitise_js_variable( $text ) {
		if ( ! Validator::is_non_empty_string( $text ) ) {
			return '';
		}

		$text = preg_replace( self::REGEX_ALPHANUMERIC_UNDERSCORE, '', $text );
		$text = preg_replace( self::REGEX_STARTS_WITH_NON_ALPHA, '', $text );

		return $text;
	}

	/**
	 * Determines whether $screen is instance of WP_Screen.
	 *
	 * @since 1.2.0
	 *
	 * @param WP_Screen $screen Screen object.
	 * @return bool
	 */
	private static function is_wp_screen( $screen ) {
		return $screen instanceof WP_Screen;
	}
}
