<?php
/**
 * Elementor Pro Theme Builder compatibility.
 *
 * Register locations, render them on theme hooks, fall back to theme markup.
 * Elementor Pro owns editor / page-template selection — the theme does not force
 * Canvas or Full Width.
 *
 * @package Eronex
 * @since   0.0.1
 */

use Elementor\Plugin;

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

if ( ! class_exists( 'Eronex_Elementor_Compatibility' ) ) {

	/**
	 * Elementor compatibility.
	 *
	 * @since 0.0.1
	 */
	class Eronex_Elementor_Compatibility {

		/**
		 * Singleton instance.
		 *
		 * @since 0.0.1
		 * @var Eronex_Elementor_Compatibility|null
		 */
		private static $instance;

		/**
		 * Returns the singleton instance.
		 *
		 * @since 0.0.1
		 *
		 * @return Eronex_Elementor_Compatibility
		 */
		public static function get_instance() {
			if ( ! isset( self::$instance ) ) {
				self::$instance = new self();
			}

			return self::$instance;
		}

		/**
		 * Constructor.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function __construct() {
			add_action( 'elementor/theme/register_locations', array( $this, 'register_locations' ) );

			// Header / footer — theme header.php / footer.php tags.
			add_action( 'eronex_header', array( $this, 'do_header' ), 0 );
			add_action( 'eronex_footer', array( $this, 'do_footer' ), 0 );

			// Singular / archive content — theme content hooks.
			add_action( 'eronex_archive_loop', array( $this, 'do_archive' ), 0 );
			add_action( 'eronex_single_content', array( $this, 'do_single' ), 0 );

			// Strip theme chrome when Theme Builder owns the view.
			add_action( 'wp', array( $this, 'maybe_disable_theme_chrome' ) );

			// 404 / WooCommerce have no eronex content hooks — thin override only.
			add_filter( 'template_include', array( $this, 'template_include' ), 12 );

			// Editor / Pro polish (unrelated to locations, but required for Theme Builder UX).
			add_action( 'elementor/init', array( $this, 'fix_optimized_markup_duplicate_controls' ), 20 );
			add_action( 'elementor/editor/before_enqueue_scripts', array( $this, 'ensure_editor_sprintf_global' ) );
		}

		/**
		 * Registers Elementor Pro Theme Builder locations.
		 *
		 * @since 0.0.1
		 *
		 * @param object $manager Locations manager.
		 * @return void
		 */
		public function register_locations( $manager ) {
			if ( apply_filters( 'eronex_register_elementor_locations', true ) ) {
				$manager->register_all_core_location();
			}
		}

		/**
		 * Renders Theme Builder header, or keeps the theme header.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function do_header() {
			if ( self::do_location( 'header' ) ) {
				remove_action( 'eronex_header', 'eronex_header_markup' );
			}
		}

		/**
		 * Renders Theme Builder footer, or keeps the theme footer.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function do_footer() {
			if ( self::do_location( 'footer' ) ) {
				remove_action( 'eronex_footer', 'eronex_footer_markup' );
			}
		}

		/**
		 * Renders Theme Builder archive content, or keeps the theme loop.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function do_archive() {
			if ( self::do_location( 'archive' ) ) {
				$this->remove_theme_content_actions();
			}
		}

		/**
		 * Renders Theme Builder singular content, or keeps the theme single markup.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function do_single() {
			if ( self::do_location( 'single' ) ) {
				$this->remove_theme_content_actions();
			}
		}

		/**
		 * Disables banner / sidebar when Theme Builder content is active.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function maybe_disable_theme_chrome() {
			if ( 'elementor_library' === get_post_type() ) {
				return;
			}

			$use_archive = ( is_archive() || is_home() || is_search() ) && self::location_exists( 'archive' );
			$use_single  = ( is_singular() || is_404() ) && self::location_exists( 'single' );

			if ( function_exists( 'is_product' ) && is_product() && ( self::location_exists( 'product' ) || self::location_exists( 'single' ) ) ) {
				$use_single = true;
			}

			if ( function_exists( 'is_shop' ) && ( is_shop() || is_product_taxonomy() ) && ( self::location_exists( 'product_archive' ) || self::location_exists( 'archive' ) ) ) {
				$use_archive = true;
			}

			if ( $use_archive || $use_single ) {
				remove_action( 'eronex_banner', 'eronex_banner_markup' );
				remove_action( 'eronex_sidebar', 'eronex_get_sidebar' );
			}
		}

		/**
		 * Uses a thin Theme Builder wrapper for templates without eronex content hooks.
		 *
		 * @since 0.0.1
		 *
		 * @param string $template Template path.
		 * @return string
		 */
		public function template_include( $template ) {
			// Never fight Elementor's editor / preview templates.
			if ( $this->is_elementor_preview() ) {
				return $template;
			}

			if ( false !== strpos( $template, 'elementor' ) && false !== strpos( $template, 'page-templates' ) ) {
				return $template;
			}

			$location = $this->get_hookless_override_location();

			if ( ! $location ) {
				return $template;
			}

			$override = ERONEX_DIR . 'includes/elementor/theme-builder.php';

			return file_exists( $override ) ? $override : $template;
		}

		/**
		 * Location for views that cannot use eronex_archive_loop / eronex_single_content.
		 *
		 * @since 0.0.1
		 *
		 * @return string
		 */
		protected function get_hookless_override_location() {
			if ( is_404() && self::location_exists( 'single' ) ) {
				return 'single';
			}

			if ( function_exists( 'is_product' ) && is_product() ) {
				if ( self::location_exists( 'product' ) ) {
					return 'product';
				}
				if ( self::location_exists( 'single' ) ) {
					return 'single';
				}
			}

			if ( function_exists( 'is_shop' ) && ( is_shop() || is_product_taxonomy() || is_product_category() || is_product_tag() ) ) {
				if ( self::location_exists( 'product_archive' ) ) {
					return 'product_archive';
				}
				if ( self::location_exists( 'archive' ) ) {
					return 'archive';
				}
			}

			return '';
		}

		/**
		 * Renders the override location inside the thin Theme Builder template.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function render_override_location() {
			$location = $this->get_hookless_override_location();

			if ( ! $location ) {
				return;
			}

			$this->remove_theme_content_actions();
			self::do_location( $location );
		}

		/**
		 * Removes theme content actions once Theme Builder prints the view.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		protected function remove_theme_content_actions() {
			remove_action( 'eronex_banner', 'eronex_banner_markup' );
			remove_action( 'eronex_archive_loop', 'eronex_archive_loop_markup' );
			remove_action( 'eronex_single_content', 'eronex_single_content_markup' );
			remove_action( 'eronex_sidebar', 'eronex_get_sidebar' );
		}

		/**
		 * Whether Elementor is editing or previewing.
		 *
		 * @since 0.0.1
		 *
		 * @return bool
		 */
		protected function is_elementor_preview() {
			if ( isset( $_GET['elementor-preview'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
				return true;
			}

			if ( ! class_exists( '\Elementor\Plugin' ) ) {
				return false;
			}

			$plugin = Plugin::$instance;

			if ( ! empty( $plugin->preview ) && method_exists( $plugin->preview, 'is_preview_mode' ) && $plugin->preview->is_preview_mode() ) {
				return true;
			}

			if ( ! empty( $plugin->editor ) && method_exists( $plugin->editor, 'is_edit_mode' ) && $plugin->editor->is_edit_mode() ) {
				return true;
			}

			return false;
		}

		/**
		 * Whether a Theme Builder location has an assigned document.
		 *
		 * @since 0.0.1
		 *
		 * @param string $location Location slug.
		 * @return bool
		 */
		public static function location_exists( $location ) {
			return function_exists( 'elementor_location_exits' ) && elementor_location_exits( $location, true );
		}

		/**
		 * Prints a Theme Builder location when one is assigned.
		 *
		 * @since 0.0.1
		 *
		 * @param string $location Location slug.
		 * @return bool
		 */
		public static function do_location( $location ) {
			return function_exists( 'elementor_theme_do_location' ) && elementor_theme_do_location( $location );
		}

		/**
		 * Exposes wp.i18n.sprintf as window.sprintf for Elementor Pro editor UI.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function ensure_editor_sprintf_global() {
			wp_enqueue_script( 'wp-i18n' );

			wp_add_inline_script(
				'wp-i18n',
				'window.sprintf=window.sprintf||(window.wp&&wp.i18n&&wp.i18n.sprintf);window.__=window.__||(window.wp&&wp.i18n&&wp.i18n.__);',
				'after'
			);
		}

		/**
		 * Prevents Elementor Pro Advanced controls from being registered twice.
		 *
		 * @since 0.0.1
		 *
		 * @return void
		 */
		public function fix_optimized_markup_duplicate_controls() {
			if ( ! class_exists( '\Elementor\Plugin' ) || ! class_exists( '\ElementorPro\Plugin' ) ) {
				return;
			}

			$elementor = Plugin::$instance;

			if ( empty( $elementor->experiments ) || ! method_exists( $elementor->experiments, 'is_feature_active' ) ) {
				return;
			}

			if ( ! $elementor->experiments->is_feature_active( 'e_optimized_markup' ) ) {
				return;
			}

			$modules_manager = \ElementorPro\Plugin::instance()->modules_manager;

			if ( ! $modules_manager || ! method_exists( $modules_manager, 'get_modules' ) ) {
				return;
			}

			$this->guard_pro_common_section_controls(
				$modules_manager->get_modules( 'motion-fx' ),
				array(
					'elementor/element/common/section_effects/after_section_start',
					'elementor/element/common-optimized/section_effects/after_section_start',
				),
				'add_controls_group_to_element',
				'handle_motion_fx_asset_loading'
			);

			$this->guard_pro_common_section_controls(
				$modules_manager->get_modules( 'sticky' ),
				array(
					'elementor/element/common/section_effects/after_section_start',
					'elementor/element/common-optimized/section_effects/after_section_start',
				),
				'register_controls',
				'sticky'
			);

			$this->guard_display_conditions_controls( $modules_manager->get_modules( 'display-conditions' ) );
		}

		/**
		 * Replaces Pro module callbacks with a version that skips existing controls.
		 *
		 * @since 0.0.1
		 *
		 * @param object|null $module     Module instance.
		 * @param string[]    $hooks      Hook names.
		 * @param string      $method     Register method.
		 * @param string      $control_id Control ID to detect duplicates.
		 * @return void
		 */
		protected function guard_pro_common_section_controls( $module, $hooks, $method, $control_id ) {
			if ( ! $module || ! method_exists( $module, $method ) ) {
				return;
			}

			foreach ( $hooks as $hook ) {
				remove_action( $hook, array( $module, $method ) );
				add_action(
					$hook,
					static function ( $element ) use ( $module, $method, $control_id ) {
						if ( $element->get_controls( $control_id ) ) {
							return;
						}
						$module->{$method}( $element );
					}
				);
			}
		}

		/**
		 * Guards Display Conditions controls.
		 *
		 * @since 0.0.1
		 *
		 * @param object|null $module Display Conditions module.
		 * @return void
		 */
		protected function guard_display_conditions_controls( $module ) {
			if ( ! $module ) {
				return;
			}

			$hooks = array(
				'elementor/element/common/_section_style/after_section_end'           => '_css_classes',
				'elementor/element/common-optimized/_section_style/after_section_end' => '_css_classes',
			);

			$reflection = new ReflectionClass( $module );

			if ( ! $reflection->hasMethod( 'add_control_to_advanced_tab' ) ) {
				return;
			}

			$method = $reflection->getMethod( 'add_control_to_advanced_tab' );
			$method->setAccessible( true );

			foreach ( $hooks as $hook => $injection_position ) {
				$this->remove_module_closures_from_hook( $hook, $module, 10 );

				add_action(
					$hook,
					static function ( $element, $args ) use ( $module, $method, $injection_position ) {
						if ( $element->get_controls( 'e_display_conditions_trigger' ) ) {
							return;
						}
						$method->invoke( $module, $element, $args, $injection_position );
					},
					10,
					2
				);
			}
		}

		/**
		 * Removes Closure callbacks bound to a module instance.
		 *
		 * @since 0.0.1
		 *
		 * @param string $hook     Hook name.
		 * @param object $module   Module instance.
		 * @param int    $priority Priority.
		 * @return void
		 */
		protected function remove_module_closures_from_hook( $hook, $module, $priority = 10 ) {
			global $wp_filter;

			if ( empty( $wp_filter[ $hook ] ) || empty( $wp_filter[ $hook ]->callbacks[ $priority ] ) ) {
				return;
			}

			foreach ( $wp_filter[ $hook ]->callbacks[ $priority ] as $callback ) {
				if ( empty( $callback['function'] ) || ! ( $callback['function'] instanceof Closure ) ) {
					continue;
				}

				$closure = new ReflectionFunction( $callback['function'] );

				if ( $closure->getClosureThis() === $module ) {
					remove_action( $hook, $callback['function'], $priority );
				}
			}
		}
	}

	Eronex_Elementor_Compatibility::get_instance();

}
