<?php
namespace Elementor\Core\Editor;

use Elementor\Core\Editor\Config_Providers\Config_Provider_Interface;
use Elementor\Core\Utils\Collection;
use Elementor\Plugin;
use Elementor\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

class Editor_Loader {
	/**
	 * @var Config_Provider_Interface
	 */
	private $config_provider;

	/**
	 * @param Config_Provider_Interface $config_provider
	 */
	public function __construct( Config_Provider_Interface $config_provider ) {
		$this->config_provider = $config_provider;
	}

	/**
	 * @return void
	 */
	public function register_hooks() {
		$script_configs = $this->get_script_configs();

		// Pointing WordPress to use translations for external files ('assets/js/editor.strings.js') and not
		// for the original file (assets/js/editor.js).
		add_filter( 'load_script_textdomain_relative_path', function ( $relative_path, $src ) use ( $script_configs ) {
			return $this->replace_requested_translation_file( $relative_path, $src, $script_configs );
		}, 10, 2 );
	}

	/**
	 * @return void
	 */
	public function register_scripts() {
		$script_configs = $this->get_script_configs();

		foreach ( $script_configs as $script_config ) {
			wp_register_script(
				$script_config['handle'],
				$script_config['src'],
				$script_config['deps'],
				$script_config['version'],
				$script_config['in_footer']
			);
		}
	}

	public function print_client_settings() {
		$client_settings = $this->config_provider->get_client_settings();

		$client_settings = Collection::make( $client_settings )
			->filter( function ( $config ) {
				return (
					! empty( $config['handle'] ) &&
					! empty( $config['name'] ) &&
					! empty( $config['settings'] )
				);
			} );

		foreach ( $client_settings as $client_setting ) {
			Utils::print_js_config(
				$client_setting['handle'],
				$client_setting['name'],
				$client_setting['settings']
			);
		}
	}

	/**
	 * @return void
	 */
	public function enqueue_scripts() {
		foreach ( $this->config_provider->get_script_handles_to_enqueue() as $script_handle ) {
			wp_enqueue_script( $script_handle );
		}
	}

	/**
	 * @return void
	 */
	public function load_scripts_translations() {
		$script_configs = $this->get_script_configs();

		foreach ( $script_configs as $script_config ) {
			if ( $script_config['i18n']['domain'] ) {
				wp_set_script_translations(
					$script_config['handle'],
					$script_config['i18n']['domain']
				);
			}
		}
	}

	/**
	 * @return void
	 */
	public function register_styles() {
		$styles_config = $this->get_style_configs();

		foreach ( $styles_config as $style_config ) {
			wp_register_style(
				$style_config['handle'],
				$style_config['src'],
				$style_config['deps'],
				$style_config['version'],
				$style_config['media']
			);
		}
	}

	/**
	 * @return void
	 */
	public function enqueue_styles() {
		foreach ( $this->config_provider->get_style_handles_to_enqueue() as $style_handle ) {
			wp_enqueue_style( $style_handle );
		}
	}

	/**
	 * @return void
	 */
	public function print_root_template() {
		// Exposing the path for the view part to render the body of the editor template.
		$body_file_path = $this->config_provider->get_template_body_file_path();

		include ELEMENTOR_PATH . 'includes/editor-templates/editor-wrapper.php';
	}

	public function register_additional_templates() {
		$templates = $this->config_provider->get_additional_template_paths();

		foreach ( $templates as $template_path ) {
			Plugin::$instance->common->add_template( $template_path );
		}
	}

	/**
	 * @return Collection
	 */
	private function get_script_configs() {
		return $this->normalize_asset_configs(
			$this->config_provider->get_script_configs(),
			'script'
		);
	}

	/**
	 * @return Collection
	 */
	private function get_style_configs() {
		return $this->normalize_asset_configs(
			$this->config_provider->get_style_configs(),
			'style'
		);
	}

	/**
	 * Normalize script/style configs to enqueue and register methods.
	 *
	 * @param array $script_configs
	 * @param string $type can be ['script', 'style']
	 *
	 * @return Collection
	 */
	private function normalize_asset_configs( array $script_configs, $type ) {
		$additional_defaults = 'style' === $type ?
			[ 'media' => 'all' ] :
			[
				'in_footer' => true,
				'i18n' => [
					'domain' => null,
					'replace_requested_file' => false,
					'replace_requested_file_callback' => function ( $relative_path ) {
						// By default, we suffix the file with `.strings` e.g 'assets/js/editor.js' => 'assets/js/editor.strings.js'.

						// Translations are always based on the non-minified filename.
						$relative_path_without_ext = preg_replace( '/(\.min)?\.js$/i', '', $relative_path );

						return implode( '.', [
							$relative_path_without_ext,
							'strings',
							'js',
						] );
					},
				],
			];

		$default = array_replace_recursive( [
			'handle' => '',
			'src' => '',
			'deps' => [],
			'version' => ELEMENTOR_VERSION,
			'in_footer' => true,
		], $additional_defaults );

		$replacements = [
			'{{ELEMENTOR_ASSETS_URL}}' => ELEMENTOR_ASSETS_URL,
			'{{MIN_SUFFIX}}' => ( Utils::is_script_debug() || Utils::is_elementor_tests() ) ? '' : '.min',
			'{{DIRECTION_SUFFIX}}' => is_rtl() ? '-rtl' : '',
		];

		return Collection::make( $script_configs )
			->filter( function ( $config ) {
				return ! empty( $config['handle'] ) && ! empty( $config['src'] );
			} )
			->map( function ( $config ) use ( $default, $replacements ) {
				// Assign default values.
				$config = array_replace_recursive( $default, $config );

				// Replace placeholders with actual values.
				foreach ( $replacements as $replacement_key => $replacement_value ) {
					$config['src'] = str_replace(
						$replacement_key,
						$replacement_value,
						$config['src']
					);
				}

				return $config;
			} );
	}

	/**
	 * The purpose of this function is to replace the requested translation file
	 * with a file that contains all the translations for specific scripts.
	 *
	 * When writing a script and using Webpack's dynamic load feature,
	 * the script will be split into multiple chunks,
	 * therefore the translations that generated by WordPress will also split into multiple files,
	 * for that reason we replace the requested translation file with another file (generated in the build process)
	 * that contains all the translations for the specific script (including dynamically loaded chunks).
	 *
	 * Want to go deeper? Read the following article:
	 * @see https://developer.wordpress.com/2022/01/06/wordpress-plugin-i18n-webpack-and-composer/
	 *
	 * @param string $relative_path
	 * @param string $src
	 * @param Collection $script_configs Collection of all the script configs
	 *
	 * @return string
	 */
	private function replace_requested_translation_file( $relative_path, $src, $script_configs ) {
		$script_config = $script_configs->find( function ( $script_config ) use ( $src ) {
			return $script_config['src'] === $src;
		} );

		$should_suffix_path = $script_config &&
			$script_config['i18n']['domain'] &&
			$script_config['i18n']['replace_requested_file'];

		if ( ! $should_suffix_path ) {
			return $relative_path;
		}

		return $script_config['i18n']['replace_requested_file_callback'](
			$relative_path,
			$script_config
		);
	}
}
