<?php
/**
 * RSM Editor TinyMCE integration.
 *
 * @package Rsmspark_Editor
 */

defined( 'ABSPATH' ) || exit;

/**
 * Rsmspark_Rich_Text_TinyMCE
 */
final class Rsmspark_Rich_Text_TinyMCE {

	/**
	 * @var bool
	 */
	private static $gutenberg_editor_enqueued = false;

	/**
	 * @return void
	 */
	public static function init() {
		add_action( 'admin_init', array( __CLASS__, 'register_assets' ), 1 );
		add_action( 'admin_init', array( __CLASS__, 'register_siteorigin_enqueue_hooks' ), 20 );
		add_action( 'enqueue_block_editor_assets', array( __CLASS__, 'enqueue_block_sidebar_editor' ), 1 );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_gutenberg_editor' ), 9 );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_admin' ), 20 );
		add_action( 'siteorigin_panel_enqueue_admin_scripts', array( __CLASS__, 'bootstrap_siteorigin_editor' ) );
		add_filter( 'tiny_mce_before_init', array( __CLASS__, 'filter_tinymce_init' ), 999, 2 );
		add_filter( 'mce_buttons', array( __CLASS__, 'filter_mce_buttons' ), 99, 2 );
		add_filter( 'mce_buttons_2', array( __CLASS__, 'filter_mce_buttons_2' ), 99, 2 );
		add_filter( 'mce_external_plugins', array( __CLASS__, 'register_plugin' ) );
		add_filter( 'tiny_mce_version', array( __CLASS__, 'filter_tiny_mce_version' ) );
		add_filter( 'wp_editor_settings', array( __CLASS__, 'filter_editor_settings' ), 20, 2 );
		add_filter( 'siteorigin_widgets_field_tinymce_settings', array( __CLASS__, 'filter_siteorigin_tinymce_settings' ), 20, 3 );
	}

	/**
	 * Register script/style handles on every admin request (including SiteOrigin AJAX).
	 *
	 * @return void
	 */
	public static function register_assets() {
		if ( ! is_admin() ) {
			return;
		}

		Rsmspark_Rich_Text_Assets::register_admin();
	}

	/**
	 * Bootstrap editor assets when SiteOrigin Widgets / Page Builder enqueue admin scripts.
	 *
	 * Uses SiteOrigin's documented enqueue hooks only — we do not register or listen on
	 * wp_ajax_so_* action names (those belong to SiteOrigin).
	 *
	 * @return void
	 */
	public static function bootstrap_siteorigin_editor() {
		self::register_assets();
		Rsmspark_Rich_Text_Assets::enqueue_admin();
		Rsmspark_Rich_Text_Editor_UI::enqueue_scripts( '' );
	}

	/**
	 * Attach per-widget SiteOrigin admin enqueue hooks for Content Widgets SO forms.
	 *
	 * @return void
	 */
	public static function register_siteorigin_enqueue_hooks() {
		if ( ! is_admin() ) {
			return;
		}

		if ( ! class_exists( 'SiteOrigin_Widget', false ) ) {
			return;
		}

		if ( ! class_exists( 'Rsmspark_Content_Widgets_Registry', false ) ) {
			return;
		}

		foreach ( array_keys( Rsmspark_Content_Widgets_Registry::definitions() ) as $registry_id ) {
			$so_id = Rsmspark_Content_Widgets_Registry::siteorigin_folder_id( $registry_id );
			if ( '' === $so_id ) {
				continue;
			}
			add_action(
				'siteorigin_widgets_enqueue_admin_scripts_' . $so_id,
				array( __CLASS__, 'bootstrap_siteorigin_editor' )
			);
		}

		// Legacy SiteOrigin image widget id before rsm-image rename.
		add_action( 'siteorigin_widgets_enqueue_admin_scripts_image', array( __CLASS__, 'bootstrap_siteorigin_editor' ) );
	}

	/**
	 * @param string $hook Admin hook.
	 * @return void
	 */
	public static function enqueue_admin( $hook = '' ) {
		if ( ! self::should_enqueue( $hook ) ) {
			return;
		}

		self::register_assets();
		Rsmspark_Rich_Text_Assets::enqueue_admin();
	}

	/**
	 * Gutenberg block sidebar: classic TinyMCE via wp_enqueue_editor() on admin_enqueue_scripts.
	 *
	 * Must run during admin_enqueue (not enqueue_block_editor_assets) so footer TinyMCE
	 * bootstrap scripts register and jQuery loads in dependency order.
	 *
	 * @param string $hook Admin hook suffix.
	 * @return void
	 */
	public static function enqueue_gutenberg_editor( $hook = '' ) {
		if ( self::$gutenberg_editor_enqueued ) {
			return;
		}

		if ( ! self::is_block_editor_screen() || ! current_user_can( 'edit_posts' ) ) {
			return;
		}

		if ( ! class_exists( 'Rsmspark_Content_Widgets_Settings', false )
			|| ! Rsmspark_Content_Widgets_Settings::blocks_enabled() ) {
			return;
		}

		self::$gutenberg_editor_enqueued = true;

		wp_enqueue_script( 'jquery' );
		wp_enqueue_style( 'dashicons' );

		if ( wp_script_is( 'editor', 'registered' ) ) {
			wp_enqueue_script( 'editor' );
		}

		if ( wp_script_is( 'quicktags', 'registered' ) ) {
			wp_enqueue_script( 'quicktags' );
		}

		if ( wp_style_is( 'editor-buttons', 'registered' ) ) {
			wp_enqueue_style( 'editor-buttons' );
		}

		self::register_assets();
		Rsmspark_Rich_Text_Assets::enqueue_admin();
		Rsmspark_Rich_Text_Editor_UI::enqueue_gutenberg_media( $hook );
	}

	/**
	 * Block editor sidebar: mirror admin_enqueue stack (WP 6.9+ may skip admin order).
	 *
	 * @return void
	 */
	public static function enqueue_block_sidebar_editor() {
		self::enqueue_gutenberg_editor( 'post.php' );
	}

	/**
	 * @param string $hook Admin hook.
	 * @return bool
	 */
	public static function should_enqueue( $hook = '' ) {
		if ( ! is_admin() || ! current_user_can( 'edit_posts' ) ) {
			return false;
		}

		if ( self::is_block_editor_screen() && ! wp_doing_ajax() ) {
			return false;
		}

		$hook = (string) $hook;
		$needles = array(
			'post.php',
			'post-new.php',
			'widgets.php',
			'admin.php',
			'rsmspark-content-widgets',
			'rsm-buttons',
			'siteorigin',
			'page-builder',
		);

		foreach ( $needles as $needle ) {
			if ( '' !== $hook && false !== strpos( $hook, $needle ) ) {
				return true;
			}
		}

		return (bool) apply_filters( 'rsmspark_rich_text_should_enqueue_admin', false, $hook );
	}

	/**
	 * @return bool
	 */
	public static function is_block_editor_screen() {
		if ( ! function_exists( 'get_current_screen' ) ) {
			return false;
		}

		$screen = get_current_screen();
		if ( ! $screen || ! method_exists( $screen, 'is_block_editor' ) ) {
			return false;
		}

		return (bool) $screen->is_block_editor();
	}

	/**
	 * @param array<string, mixed> $init      TinyMCE init.
	 * @param string               $editor_id Editor id.
	 * @return array<string, mixed>
	 */
	public static function filter_tinymce_init( $init, $editor_id ) {
		$profile = self::resolve_profile( (string) $editor_id, is_array( $init ) ? $init : array() );
		if ( '' === $profile ) {
			return $init;
		}

		$profile_settings = Rsmspark_Rich_Text_Registry::tinymce_settings( $profile );
		$init             = array_merge( $init, $profile_settings );
		$init             = self::merge_external_plugin( $init );
		$init             = self::merge_extended_valid_elements( $init, 'ul[class|style],ol[class|style]' );

		return $init;
	}

	/**
	 * Ensure TinyMCE knows the plugin script URL (avoids core /plugins/rsm_rich_text/ fallback).
	 *
	 * @param array<string, mixed> $init TinyMCE init.
	 * @return array<string, mixed>
	 */
	/**
	 * Append list attribute rules without clobbering core extended_valid_elements.
	 *
	 * @param array<string, mixed> $init TinyMCE init.
	 * @param string               $extra Extended valid element rules.
	 * @return array<string, mixed>
	 */
	private static function merge_extended_valid_elements( array $init, $extra ) {
		$extra   = trim( (string) $extra );
		$current = trim( (string) ( $init['extended_valid_elements'] ?? '' ) );

		if ( '' === $extra ) {
			return $init;
		}

		if ( '' === $current ) {
			$init['extended_valid_elements'] = $extra;
			return $init;
		}

		if ( false !== strpos( $current, 'ul[' ) ) {
			return $init;
		}

		$init['extended_valid_elements'] = $current . ',' . $extra;

		return $init;
	}

	private static function parse_external_plugins( $value ) {
		if ( is_array( $value ) ) {
			return $value;
		}

		if ( ! is_string( $value ) || '' === trim( $value ) ) {
			return array();
		}

		$decoded = json_decode( $value, true );

		return is_array( $decoded ) ? $decoded : array();
	}

	private static function merge_external_plugin( array $init ) {
		$external = self::parse_external_plugins( $init['external_plugins'] ?? null );

		$external['rsm_rich_text'] = Rsmspark_Rich_Text_Assets::script_url();
		$init['external_plugins']  = wp_json_encode( $external );

		return $init;
	}

	/**
	 * @param array<string, mixed> $settings  Editor settings.
	 * @param string               $editor_id Editor id.
	 * @return array<string, mixed>
	 */
	public static function filter_editor_settings( $settings, $editor_id ) {
		$context = is_array( $settings ) ? $settings : array();
		if ( ! empty( $settings['tinymce'] ) && is_array( $settings['tinymce'] ) ) {
			$context = array_merge( $context, $settings['tinymce'] );
		}

		$profile = self::resolve_profile( (string) $editor_id, $context );
		if ( '' === $profile ) {
			return $settings;
		}

		$classes   = trim( (string) ( $settings['editor_class'] ?? '' ) );
		$classes  .= ' rsmspark-rt-editor-wrap rsmspark-rt-editor rsmspark-rt-root rsmspark-rt-profile-' . $profile;
		$settings['editor_class'] = trim( $classes );

		if ( empty( $settings['tinymce'] ) || ! is_array( $settings['tinymce'] ) ) {
			$settings['tinymce'] = array();
		}

		$settings['tinymce'] = self::merge_external_plugin(
			array_merge(
				$settings['tinymce'],
				Rsmspark_Rich_Text_Registry::tinymce_settings( $profile )
			)
		);

		return $settings;
	}

	/**
	 * @param array<string, mixed> $settings TinyMCE settings.
	 * @param array<string, mixed> $field    Field definition.
	 * @param mixed                $widget   Widget instance.
	 * @return array<string, mixed>
	 */
	public static function filter_siteorigin_tinymce_settings( $settings, $field, $widget ) {
		unset( $widget );

		if ( ! is_array( $settings ) ) {
			$settings = array();
		}

		if ( ! is_array( $field ) ) {
			return $settings;
		}

		$profile = '';
		if ( ! empty( $field['rsm_rt_profile'] ) ) {
			$profile = sanitize_key( (string) $field['rsm_rt_profile'] );
		} elseif ( ! empty( $settings['rsm_rt_profile'] ) ) {
			$profile = sanitize_key( (string) $settings['rsm_rt_profile'] );
		} elseif ( ! empty( $field['editor_class'] ) && preg_match( '/rsmspark-rt-profile-(\w+)/', (string) $field['editor_class'], $matches ) ) {
			$profile = sanitize_key( $matches[1] );
		}

		if ( ! Rsmspark_Rich_Text_Registry::is_valid_profile( $profile ) ) {
			return $settings;
		}

		$settings = array_merge( $settings, Rsmspark_Rich_Text_Registry::tinymce_settings( $profile ) );

		return self::merge_external_plugin( $settings );
	}

	/**
	 * SiteOrigin builds toolbar1 from mce_buttons before tiny_mce_before_init runs.
	 *
	 * @param string[] $buttons   Button ids.
	 * @param string   $editor_id Editor id.
	 * @return string[]
	 */
	public static function filter_mce_buttons( $buttons, $editor_id ) {
		$profile = self::resolve_profile( (string) $editor_id, array() );
		if ( '' === $profile ) {
			return $buttons;
		}

		$toolbar = Rsmspark_Rich_Text_Registry::toolbar1( $profile );

		return array_values( array_filter( explode( ',', $toolbar ) ) );
	}

	/**
	 * @param string[] $buttons   Button ids.
	 * @param string   $editor_id Editor id.
	 * @return string[]
	 */
	public static function filter_mce_buttons_2( $buttons, $editor_id ) {
		$profile = self::resolve_profile( (string) $editor_id, array() );
		if ( '' === $profile ) {
			return $buttons;
		}

		return array();
	}

	/**
	 * @param array<string, string> $plugins Plugins.
	 * @return array<string, string>
	 */
	public static function register_plugin( $plugins ) {
		if ( ! is_array( $plugins ) ) {
			$plugins = array();
		}

		self::register_assets();
		$plugins['rsm_rich_text'] = Rsmspark_Rich_Text_Assets::script_url();

		return $plugins;
	}

	/**
	 * Bust WordPress TinyMCE script cache (wp-mce-* query) when RSM editor JS changes.
	 *
	 * @param string $version TinyMCE cache version suffix.
	 * @return string
	 */
	public static function filter_tiny_mce_version( $version ) {
		return (string) $version . '-rsmspark-rt-' . RSMSPARK_RICH_TEXT_VERSION;
	}

	/**
	 * @param string               $editor_id Editor id.
	 * @param array<string, mixed> $context   Init/settings context.
	 * @return string
	 */
	public static function resolve_profile( $editor_id, array $context = array() ) {
		if ( ! empty( $context['rsm_rt_profile'] ) && Rsmspark_Rich_Text_Registry::is_valid_profile( (string) $context['rsm_rt_profile'] ) ) {
			return sanitize_key( (string) $context['rsm_rt_profile'] );
		}

		$body_class = (string) ( $context['body_class'] ?? '' );
		if ( preg_match( '/rsmspark-rt-profile-(\w+)/', $body_class, $matches ) && Rsmspark_Rich_Text_Registry::is_valid_profile( $matches[1] ) ) {
			return sanitize_key( $matches[1] );
		}

		$editor_class = (string) ( $context['editor_class'] ?? '' );
		if ( preg_match( '/rsmspark-rt-profile-(\w+)/', $editor_class, $matches ) && Rsmspark_Rich_Text_Registry::is_valid_profile( $matches[1] ) ) {
			return sanitize_key( $matches[1] );
		}

		$editor_id = (string) $editor_id;

		if ( self::editor_id_is_headline_field( $editor_id ) ) {
			return Rsmspark_Rich_Text_Registry::PROFILE_HEADLINE;
		}

		if ( self::editor_id_is_body_field( $editor_id ) ) {
			return Rsmspark_Rich_Text_Registry::PROFILE_BODY;
		}

		if ( preg_match( '/^rsmspark-rt-block-headline-/i', $editor_id ) ) {
			return Rsmspark_Rich_Text_Registry::PROFILE_HEADLINE;
		}

		if ( preg_match( '/^rsmspark-rt-block-body-/i', $editor_id ) ) {
			return Rsmspark_Rich_Text_Registry::PROFILE_BODY;
		}

		return '';
	}

	/**
	 * @param string $editor_id TinyMCE editor id / SiteOrigin element id.
	 * @return bool
	 */
	private static function editor_id_is_headline_field( $editor_id ) {
		$editor_id = (string) $editor_id;

		if ( preg_match( '/rsm[-_]headline|widget[-_]headline|headline[-_]widget|content[-_](overline|headline|subheadline)/i', $editor_id ) ) {
			return true;
		}

		if ( preg_match( '/widget-headline-/i', $editor_id ) ) {
			return (bool) preg_match( '/-(overline|headline|subheadline)$/i', $editor_id );
		}

		if ( preg_match( '/widget-features-/i', $editor_id ) ) {
			return (bool) preg_match( '/(?:^|-)title(?:-|_|$|\d)/i', $editor_id );
		}

		return (bool) preg_match( '/^(overline|headline|subheadline)$/i', $editor_id );
	}

	/**
	 * SiteOrigin repeater tinymce ids embed the field name before the row index, e.g.
	 * widget-features-c159-general-features-description-1-1 (not …-description).
	 *
	 * @param string $editor_id TinyMCE editor id / SiteOrigin element id.
	 * @return bool
	 */
	private static function editor_id_is_body_field( $editor_id ) {
		$editor_id = (string) $editor_id;

		if ( preg_match( '/widget-features-/i', $editor_id ) ) {
			return (bool) preg_match( '/(?:description|popout_content)/i', $editor_id );
		}

		if ( preg_match( '/widget-(tabs|accordion)-/i', $editor_id ) ) {
			return (bool) preg_match( '/(?:items[-_]?content|^content$|[-_]content(?:-|_|$|\d))/i', $editor_id );
		}

		return false;
	}
}
