<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit; // disable direct access
}

if ( ! class_exists( 'Mega_Menu_Location' ) ) :

	/**
	 * Represents a single menu location, its Mega Menu settings, and CSS generation.
	 *
	 * @since   3.9
	 * @package MegaMenu
	 */
	class Mega_Menu_Location {

		/**
		 * Location slug (e.g. 'primary', 'max_mega_menu_1').
		 *
		 * @var string
		 */
		public $id;

		/**
		 * Human-readable location title.
		 *
		 * @var string
		 */
		public $title;

		/**
		 * Per-location settings slice from the megamenu_settings option.
		 *
		 * @var array
		 */
		public $settings;



		/**
		 * Constructor.
		 *
		 * @param string $id       Location slug.
		 * @param string $title    Location title.
		 * @param array  $settings Per-location settings.
		 */
		public function __construct( $id, $title, $settings = [] ) {
			$this->id       = $id;
			$this->title    = $title;
			$this->settings = $settings;
		}


		/**
		 * Whether Mega Menu has been enabled for this location.
		 *
		 * @return bool
		 */
		public function is_enabled() {
			return isset( $this->settings['enabled'] ) && true === boolval( $this->settings['enabled'] );
		}


		/**
		 * Whether Max Mega Menu should run for this location on the front end:
		 * the location toggle is on and a real nav menu is assigned (not a stale term ID).
		 *
		 * @since 3.9.3
		 * @return bool
		 */
		public function is_active() {
			if ( ! $this->is_enabled() ) {
				return false;
			}
			return $this->get_valid_menu_id() > 0;
		}


		/**
		 * Return the theme ID assigned to this location.
		 * Defaults to 'default' if none is saved.
		 *
		 * @return string
		 */
		public function get_theme_id() {
			return isset( $this->settings['theme'] ) ? $this->settings['theme'] : 'default';
		}


		/**
		 * Return the WP nav menu term ID assigned to this location.
		 * Returns 0 if no menu has been assigned.
		 *
		 * @return int
		 */
		public function get_menu_id() {
			$locations = get_nav_menu_locations();
			return isset( $locations[ $this->id ] ) ? (int) $locations[ $this->id ] : 0;
		}


		/**
		 * Return the assigned nav menu term ID only if that menu still exists.
		 * Theme locations can retain a stale ID after the nav menu is deleted.
		 *
		 * @since 3.9.3
		 * @return int Term ID, or 0 when none or invalid.
		 */
		public function get_valid_menu_id() {
			$menu_id = $this->get_menu_id();
			if ( ! $menu_id ) {
				return 0;
			}
			$menu = wp_get_nav_menu_object( $menu_id );
			return ( $menu && ! is_wp_error( $menu ) ) ? $menu_id : 0;
		}


		/**
		 * Return the full settings array for this location.
		 *
		 * @return array
		 */
		public function get_settings() {
			return $this->settings;
		}


		/**
		 * Return a single setting for this location, or $default if not set.
		 *
		 * @param  string $key     Setting key.
		 * @param  mixed  $default Fallback value.
		 * @return mixed
		 */
		public function get_setting( string $key, $default = null ) {
			return $this->settings[ $key ] ?? $default;
		}


		/**
		 * Generate compiled CSS for this location.
		 *
		 * An optional theme may be passed (e.g. for preview/test compilation). When
		 * omitted the location fetches its own assigned theme.
		 *
		 * @param  Mega_Menu_Theme|null $theme Optional theme override.
		 * @return string|WP_Error Compiled CSS string, or WP_Error on failure.
		 */
		public function generate_css( $theme = null ) {
			if ( null === $theme ) {
				$theme = Mega_Menu_Theme::find( $this->get_theme_id() );
			}

			if ( ( defined( 'MEGAMENU_PRO_VERSION' ) && version_compare( MEGAMENU_PRO_VERSION, '2.3.1' ) < 0 ) || ( defined( 'MEGAMENU_SCSS_COMPILER_COMPAT' ) && MEGAMENU_SCSS_COMPILER_COMPAT ) ) {
				return $this->compile_scss_old( $theme );
			}

			return $this->compile_scss_new( $theme );
		}


		// -------------------------------------------------------------------------
		// Private: SCSS compilation
		// -------------------------------------------------------------------------

		/**
		 * SCSS variable map for this location and theme (after `megamenu_scss_variables`).
		 *
		 * @param Mega_Menu_Theme $theme Active theme.
		 * @return array<string, string>
		 */
		public function get_scss_variables( Mega_Menu_Theme $theme ) {
			return $this->build_scss_variables( $theme );
		}


		/**
		 * Build the SCSS variables array for this location and theme.
		 *
		 * @param Mega_Menu_Theme $theme Active theme.
		 * @return array<string, string>
		 */
		private function build_scss_variables( Mega_Menu_Theme $theme ) {
			$location_id    = $this->id;
			$menu_id        = $this->get_valid_menu_id();
			$theme_settings = $theme->settings;

			$sanitized_location = str_replace( apply_filters( 'megamenu_location_replacements', [ '-', ' ' ] ), '-', $location_id );

			$wrap_selector = apply_filters( 'megamenu_scss_wrap_selector', "#mega-menu-wrap-{$sanitized_location}", $menu_id, $location_id );
			$menu_selector = apply_filters( 'megamenu_scss_menu_selector', "#mega-menu-{$sanitized_location}", $menu_id, $location_id );

			$vars['date']                   = "'" . date( 'l jS F Y H:i:s e' ) . "'";
			$vars['time']                   = "'" . time() . "'";
			$vars['wrap']                   = "'$wrap_selector'";
			$vars['menu']                   = "'$menu_selector'";
			$vars['location']               = "'$sanitized_location'";
			$vars['menu_id']                = "'$menu_id'";
			$vars['elementor_pro_active']   = 'false';
			$vars['megamenu_pro_active']    = 'false';
			$vars['megamenu_pro_version']   = 'false';
			$vars['arrow_font']             = 'dashicons';
			$vars['arrow_font_weight']      = 'normal';
			foreach ( [ 'arrow_up', 'arrow_down', 'arrow_left', 'arrow_right' ] as $arrow_key ) {
				if ( isset( $theme_settings[ $arrow_key ] ) ) {
					if ( strpos( $theme_settings[ $arrow_key ], 'svg-' ) === 0 ) {
						$vars['arrow_font'] = 'svg';
						break;
					} elseif ( strpos( $theme_settings[ $arrow_key ], 'mat-' ) === 0 ) {
						$vars['arrow_font'] = 'var(--wp--preset--font-family--material-symbols)';
						break;
					}
				}
			}
			$vars['close_icon_font']        = 'dashicons';
			if ( isset( $theme_settings['close_icon'] ) && strpos( $theme_settings['close_icon'], 'mat-' ) === 0 ) {
				$vars['close_icon_font'] = 'var(--wp--preset--font-family--material-symbols)';
			} elseif ( isset( $theme_settings['close_icon'] ) && strpos( $theme_settings['close_icon'], 'svg-' ) === 0 ) {
				$vars['close_icon_font'] = 'svg';
			}
			$vars['close_icon_font_weight'] = 'normal';
			$vars['arrow_combinator']       = "'>'";
			$vars['css_type']               = isset( $theme_settings['use_flex_css'] ) && $theme_settings['use_flex_css'] == 'on' ? 'flex' : 'standard';

			$current_wp_theme = wp_get_theme();
			$vars['wp_theme'] = strtolower( str_replace( [ '.', ' ' ], '_', $current_wp_theme->template ) );

			if ( empty( $vars['wp_theme'] ) ) {
				$vars['wp_theme'] = 'unknown';
			}

			if ( ! function_exists( 'is_plugin_active' ) ) {
				include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
			}

			if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'elementor-pro/elementor-pro.php' ) ) {
				$vars['elementor_pro_active'] = 'true';
			}

			if ( function_exists( 'is_plugin_active' ) && is_plugin_active( 'megamenu-pro/megamenu-pro.php' ) ) {
				$vars['megamenu_pro_active'] = 'true';
				if ( defined( 'MEGAMENU_PRO_VERSION' ) ) {
					$parts = explode( '.', MEGAMENU_PRO_VERSION );
					$vars['megamenu_pro_version'] = $parts[0] . ( isset( $parts[1] ) ? '.' . $parts[1] : '' );
				}
			}

			if ( isset( $this->settings['effect_speed'] ) && absint( $this->settings['effect_speed'] ) > 0 ) {
				$vars['effect_speed'] = absint( $this->settings['effect_speed'] ) . 'ms';
			} else {
				$vars['effect_speed'] = '200ms';
			}

			if ( isset( $this->settings['effect_speed_mobile'] ) && absint( $this->settings['effect_speed_mobile'] ) > 0 ) {
				$vars['effect_speed_mobile'] = absint( $this->settings['effect_speed_mobile'] ) . 'ms';
			} else {
				$vars['effect_speed_mobile'] = '200ms';
			}

			$vars['effect_mobile'] = isset( $this->settings['effect_mobile'] ) ? $this->settings['effect_mobile'] : 'disabled';

			$valid_theme_settings = Mega_Menu_Theme::get_default()->settings;

			foreach ( $theme_settings as $name => $value ) {

				if ( ! array_key_exists( $name, $valid_theme_settings ) ) {
					continue;
				}


				if ( in_array( $name, [ 'arrow_up', 'arrow_down', 'arrow_left', 'arrow_right', 'close_icon' ] ) ) {
					if ( strpos( $value, 'svg-' ) === 0 ) {
						if ( $name !== 'close_icon' ) {
							$svgs          = Mega_Menu_SVG_Icons::get_svg_arrows();
							$vars[ $name ] = isset( $svgs[ $value ] )
								? '"data:image/svg+xml,' . Mega_Menu_SVG_Icons::svg_to_data_uri( $svgs[ $value ] ) . '"'
								: '""';
						} else {
							$vars[ $name ] = 'none';
						}
					} else {
						$parts         = explode( '-', $value );
						$code          = end( $parts );
						$vars[ $name ] = $code == 'disabled' ? "''" : "'\\" . $code . "'";
					}
					continue;
				}

				if ( in_array( $name, [ 'menu_font_family', 'menu_item_link_font', 'panel_font_family', 'panel_header_font', 'panel_second_level_font', 'panel_third_level_font', 'flyout_link_family', 'tabbed_link_family' ] ) ) {
					$vars[ $name ] = "'" . stripslashes( htmlspecialchars_decode( $value ) ) . "'";

					$font_name_with_single_quotes = $vars[ $name ];
					$font_name_with_no_quotes     = str_replace( "'", '', $font_name_with_single_quotes );
					$font_name_parts              = explode( ' ', $font_name_with_no_quotes );

					if ( is_array( $font_name_parts ) ) {
						foreach ( $font_name_parts as $part ) {
							if ( is_numeric( $part ) ) {
								$vars[ $name ] = "\"{$font_name_with_single_quotes}\"";
							}
						}
					}
					continue;
				}

				if ( in_array( $name, [ 'responsive_text' ] ) ) {
					$vars[ $name ] = strlen( $value ) ? "'" . do_shortcode( $value ) . "'" : "''";
					continue;
				}

				if ( in_array( $name, [ 'panel_width', 'panel_inner_width', 'mobile_menu_force_width_selector' ] ) ) {
					if ( preg_match( '/^\d/', $value ) !== 1 ) {
						$vars[ $name ] = '100%';
						continue;
					}
				}

				if ( $name != 'custom_css' ) {
					$vars[ $name ] = $value;
				}
			}

			// Non-standard characters in the title will break CSS compilation.
			unset( $vars['title'] );

			return apply_filters( 'megamenu_scss_variables', $vars, $location_id, $theme_settings, $menu_id, $theme->id );
		}


		/**
		 * Build the complete SCSS string for this location using the given theme.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return string Full SCSS ready for compilation.
		 */
		private function get_complete_scss( Mega_Menu_Theme $theme ) {
			$location_id    = $this->id;
			$menu_id        = $this->get_valid_menu_id();
			$theme_settings = $theme->settings;

			$vars = $this->build_scss_variables( $theme );

			$scss = '';
			foreach ( $vars as $name => $value ) {
				$scss .= '$' . $name . ': ' . $value . ";\n";
			}

			$scss .= $this->load_scss_file( $theme );
			$scss .= stripslashes( html_entity_decode( $theme_settings['custom_css'], ENT_QUOTES ) );

			return apply_filters( 'megamenu_scss', $scss, $location_id, $theme_settings, $menu_id );
		}


		/**
		 * HTML body for a failed SCSS compile (notice-safe): warning, compiler message, full source for CodeMirror.
		 *
		 * @param Exception $e             Compiler exception.
		 * @param string    $scss_source   Full SCSS string passed to the compiler (stdin).
		 * @return string
		 */
		private function format_scss_compile_error_message( Exception $e, $scss_source ) {
			$intro = __( 'Warning: CSS compilation failed. Please check your changes or revert the theme.', 'megamenu' );
			$html  = '<p>' . esc_html( $intro ) . '</p>';
			$html .= '<p>' . esc_html( $e->getMessage() ) . '</p>';
			$html .= '<p><label for="megamenu-compile-failed-scss"><strong>' . esc_html__( 'Full SCSS sent to the compiler:', 'megamenu' ) . '</strong></label></p>';
			$html .= '<div class="megamenu-compile-failed-scss-editor">';
			$html .= '<textarea id="megamenu-compile-failed-scss" readonly="readonly" rows="18" class="large-text code megamenu-compile-failed-scss__textarea" style="width:100%;box-sizing:border-box;">';
			$html .= esc_textarea( $scss_source );
			$html .= '</textarea></div>';

			return $html;
		}


		/**
		 * Compile SCSS using the legacy scssphp 0.0.12 compiler.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return string|WP_Error
		 */
		private function compile_scss_old( Mega_Menu_Theme $theme ) {
			if ( is_readable( MEGAMENU_PATH . 'vendor/scss/0.0.12/scss.inc.php' ) && ! class_exists( 'scssc' ) ) {
				include_once MEGAMENU_PATH . 'vendor/scss/0.0.12/scss.inc.php';
			}

			$scssc = new scssc();
			$scssc->setFormatter( 'scss_formatter' );

			foreach ( $this->scss_import_paths() as $path ) {
				$scssc->addImportPath( $path );
			}

			$scss_source = $this->get_complete_scss( $theme );

			try {
				return $scssc->compile( $scss_source );
			} catch ( Exception $e ) {
				return new WP_Error( 'scss_compile_fail', $this->format_scss_compile_error_message( $e, $scss_source ) );
			}
		}


		/**
		 * Compile SCSS using the scssphp 1.11.1 compiler.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return string|WP_Error
		 */
		private function compile_scss_new( Mega_Menu_Theme $theme ) {
			if ( is_readable( MEGAMENU_PATH . 'vendor/scss/1.11.1/scss.inc.php' ) && ! class_exists( 'MMMScssPhp\ScssPhp\Compiler' ) ) {
				require_once MEGAMENU_PATH . 'vendor/scss/1.11.1/scss.inc.php';
			}

			$scssc = new \MMMScssPhp\ScssPhp\Compiler();
			$scssc->setCharset( false );

			foreach ( $this->scss_import_paths() as $path ) {
				$scssc->addImportPath( $path );
			}

			$scss_source = $this->get_complete_scss( $theme );

			try {
				if ( method_exists( $scssc, 'compileString' ) ) {
					return $scssc->compileString( $scss_source )->getCss();
				} elseif ( method_exists( $scssc, 'compile' ) ) {
					return $scssc->compile( $scss_source );
				}
			} catch ( Exception $e ) {
				return new WP_Error( 'scss_compile_fail', $this->format_scss_compile_error_message( $e, $scss_source ) );
			}
		}


		/**
		 * Return the SCSS import paths.
		 *
		 * @return array
		 */
		private function scss_import_paths() {
			return apply_filters(
				'megamenu_scss_import_paths',
				[
					trailingslashit( get_stylesheet_directory() ) . trailingslashit( 'megamenu' ),
					trailingslashit( get_stylesheet_directory() ),
					trailingslashit( get_template_directory() ) . trailingslashit( 'megamenu' ),
					trailingslashit( get_template_directory() ),
					trailingslashit( WP_PLUGIN_DIR ),
				]
			);
		}


		/**
		 * Load the SCSS file contents for the given theme, preferring child-theme /
		 * parent-theme overrides before falling back to the bundled plugin file.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return string Combined SCSS file contents.
		 */
		private function load_scss_file( Mega_Menu_Theme $theme ) {
			$scss = file_get_contents( MEGAMENU_PATH . trailingslashit( 'css' ) . 'mixin.scss' );

			foreach ( $this->possible_scss_file_locations( $theme ) as $path ) {
				if ( file_exists( $path ) ) {
					$scss .= file_get_contents( $path );
					// @todo: add break once custom SCSS file warning is in place.
				}
			}

			$scss .= file_get_contents( MEGAMENU_PATH . trailingslashit( 'css' ) . 'compatibility.scss' );

			return apply_filters( 'megamenu_load_scss_file_contents', $scss );
		}


		/**
		 * Return all possible SCSS file locations in priority order.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return array
		 */
		private function possible_scss_file_locations( Mega_Menu_Theme $theme ) {
			return apply_filters(
				'megamenu_scss_locations',
				[
					trailingslashit( get_stylesheet_directory() ) . trailingslashit( 'megamenu' ) . 'megamenu.scss', // child theme
					trailingslashit( get_template_directory() ) . trailingslashit( 'megamenu' ) . 'megamenu.scss', // parent theme
					$this->default_scss_file_location( $theme ),
				]
			);
		}


		/**
		 * Return the absolute path to the bundled SCSS file, chosen based on
		 * whether the theme uses flex CSS.
		 *
		 * @param  Mega_Menu_Theme $theme The resolved theme.
		 * @return string
		 */
		private function default_scss_file_location( Mega_Menu_Theme $theme ) {
			$use_flex = $theme->get( 'use_flex_css' ) === 'on';
			$filename = $use_flex ? 'megamenu.flex.scss' : 'megamenu.scss';
			return MEGAMENU_PATH . trailingslashit( 'css' ) . $filename;
		}


		/**
		 * Return a panel width value suitable for a data attribute (read by JS).
		 *
		 * Pixel/rem/em values are intentionally excluded — the JS doesn't need them
		 * and passing them causes conflicts with CSS-driven sizing. Viewport units and
		 * non-numeric values (e.g. 'auto') are passed through so JS can consume them.
		 *
		 * @param  string $value Raw panel_width theme setting.
		 * @return string The original value, or '' when JS should ignore it.
		 */
		private static function resolve_panel_width_attribute( string $value ): string {
			if ( preg_match( '/^\d/', $value ) !== 1 ) {
				return $value;
			}
			if ( preg_match( '/^\d+(vw|vh|vmin|vmax)$/', $value ) === 1 ) {
				return $value;
			}
			return '';
		}


		/**
		 * Build the data-* attribute array for the menu wrapper element.
		 *
		 * @param  Mega_Menu_Theme $theme Active theme for this location.
		 * @return array<string, string>
		 */
		public function get_wrap_attributes( Mega_Menu_Theme $theme ) {
			$menu_id = $this->get_valid_menu_id();

			// Location settings.
			$effect              = $this->get_setting( 'effect', 'disabled' );
			$effect_speed        = $this->get_setting( 'effect_speed', '200' );
			$effect_mobile       = $this->get_setting( 'effect_mobile', 'disabled' );
			$second_click        = $this->get_setting( 'second_click', 'go' );
			$vertical_behaviour  = $this->get_setting( 'mobile_behaviour', 'standard' );
			$mobile_state        = $this->get_setting( 'mobile_state', 'collapse_all' );
			$unbind              = 'disabled' === $this->get_setting( 'unbind', 'enabled' ) ? 'false' : 'true';
			$effect_speed_mobile = 'disabled' !== $effect_mobile ? $this->get_setting( 'effect_speed_mobile', 0 ) : 0;

			$raw_event = $this->get_setting( 'event', 'hover_intent' );
			if ( 'hover' === $raw_event ) {
				$event = 'hover_intent';
			} elseif ( 'hover_' === $raw_event ) {
				$event = 'hover';
			} else {
				$event = $raw_event;
			}

			// Theme settings.
			$panel_width       = self::resolve_panel_width_attribute( $theme->get( 'panel_width' ) );
			$panel_inner_width = substr( trim( $theme->get( 'panel_inner_width' ) ), -1 ) !== '%' ? $theme->get( 'panel_inner_width' ) : '';
			$mobile_force_width = 'on' === $theme->get( 'mobile_menu_force_width' )
				? $theme->get( 'mobile_menu_force_width_selector', 'body' )
				: 'false';
			$breakpoint = absint( $theme->get( 'responsive_breakpoint' ) );

			// Hover intent.
			$hover_intent_defaults = [ 'timeout' => 300, 'interval' => 100 ];
			$hover_intent_params   = apply_filters( 'megamenu_javascript_localisation', $hover_intent_defaults );
			$hover_intent_timeout  = absint( $hover_intent_params['timeout'] ) !== $hover_intent_defaults['timeout'] ? absint( $hover_intent_params['timeout'] ) : '';
			$hover_intent_interval = absint( $hover_intent_params['interval'] ) !== $hover_intent_defaults['interval'] ? absint( $hover_intent_params['interval'] ) : '';

			$global_settings = get_option( 'megamenu_settings', [] );

			return apply_filters(
				'megamenu_wrap_attributes',
				[
					'id'                         => '%1$s',
					'class'                      => '%2$s mega-no-js',
					'data-event'                 => $event,
					'data-effect'                => $effect,
					'data-effect-speed'          => $effect_speed,
					'data-effect-mobile'         => $effect_mobile,
					'data-effect-speed-mobile'   => $effect_speed_mobile,
					'data-panel-width'           => $panel_width,
					'data-panel-inner-width'     => $panel_inner_width,
					'data-mobile-force-width'    => $mobile_force_width,
					'data-second-click'          => $second_click,
					'data-document-click'        => 'collapse',
					'data-vertical-behaviour'    => $vertical_behaviour,
					'data-breakpoint'            => $breakpoint,
					'data-unbind'                => $unbind,
					'data-mobile-state'          => $mobile_state,
					'data-mobile-direction'      => 'vertical',
					'data-hover-intent-timeout'  => $hover_intent_timeout,
					'data-hover-intent-interval' => $hover_intent_interval,
				],
				$menu_id,
				$this->settings,
				$global_settings,
				$this->id
			);
		}


		// -------------------------------------------------------------------------
		// Static factories
		// -------------------------------------------------------------------------

		/**
		 * Register custom locations (created in the Mega Menu admin) with WordPress.
		 * Hooked to after_setup_theme.
		 *
		 * @return void
		 */
		public static function register_custom() {
			$locations = get_option( 'megamenu_locations' );
			if ( is_array( $locations ) && count( $locations ) ) {
				foreach ( $locations as $key => $val ) {
					register_nav_menu( $key, $val );
				}
			}
		}


		/**
		 * Return all registered menu locations as Mega_Menu_Location instances.
		 *
		 * Combines theme-registered locations (register_nav_menus) with any
		 * custom locations created via the Mega Menu admin.
		 *
		 * @return Mega_Menu_Location[]
		 */
		public static function get_all() {
			$all_settings = get_option( 'megamenu_settings', [] );

			// Theme-registered locations.
			$registered = get_registered_nav_menus();

			// Custom locations created within the Mega Menu admin.
			$custom = get_option( 'megamenu_locations', [] );

			if ( is_array( $custom ) ) {
				$registered = array_merge( $registered, $custom );
			}

			$locations = [];

			foreach ( $registered as $id => $title ) {
				$settings         = isset( $all_settings[ $id ] ) ? (array) $all_settings[ $id ] : [];
				$locations[ $id ] = new self( $id, $title, $settings );
			}

			return $locations;
		}


		/**
		 * Find a single location by its slug.
		 * Returns null if the location is not registered.
		 *
		 * @param  string $id Location slug.
		 * @return Mega_Menu_Location|null
		 */
		public static function find( $id ) {
			$all = self::get_all();
			return isset( $all[ $id ] ) ? $all[ $id ] : null;
		}

	}

endif;
