<?php
/**
 * MenuX — Accessibility System (WCAG 2.2 AA)
 *
 * Manages all accessibility-related settings, HTML output, CSS generation
 * and JavaScript configuration for the MenuX plugin.
 *
 * Criteria covered:
 *   1.1.1 Non-text Content (A)        — icon aria-hidden, badge SR text
 *   1.3.1 Info & Relationships (A)    — nav landmark, list semantics
 *   1.4.3 Contrast Minimum (AA)       — enforce_contrast, prefers-contrast
 *   1.4.4 Resize Text (AA)            — prevent pinch-zoom disable
 *   1.4.11 Non-text Contrast (AA)     — focus ring color
 *   1.4.12 Text Spacing (AA)          — text_spacing compat
 *   2.1.1 Keyboard (A)                — keyboard_nav, arrow_keys
 *   2.1.2 No Keyboard Trap (A)        — escape_closes, focus_trap_mobile
 *   2.4.1 Bypass Blocks (A)           — skip_nav_link
 *   2.4.3 Focus Order (A)             — tab_behavior
 *   2.4.11 Focus Appearance (AA) ★    — focus_style, focus_width, focus_color
 *   2.4.12 Focus Not Obscured (AA) ★  — focus_not_obscured
 *   2.5.8 Target Size (AA) ★          — touch_target, touch_spacing
 *   4.1.2 Name, Role, Value (A)       — aria_expanded, aria_haspopup, labels
 *   4.1.3 Status Messages (AA)        — live_region
 *
 * ★ = new in WCAG 2.2
 *
 * @package GiuliomaxMenuBuilder
 */

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

class Menux_Accessibility {

	/** Cached settings (cleared on save). */
	private static $settings = null;

	/** WordPress option key. */
	const OPTION_KEY = 'menux_accessibility';

	// ─────────────────────────────────────────────────────────────────────
	// Schema / Defaults
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Return all default values.
	 *
	 * @return array
	 */
	public static function defaults() {
		return array(
			// ── Master toggle ────────────────────────────────────────────
			'enabled'                => '0',   // Disabled by default — user opts in

			// ── 1. Keyboard Navigation ──────────────────────────────────
			'keyboard_nav'           => '1',   // Enable full keyboard nav
			'tab_behavior'           => 'top_level_only', // top_level_only | all_items
			'arrow_keys'             => '1',   // Arrow-key nav in menus
			'home_end_keys'          => '1',   // Home/End jump to first/last
			'escape_closes'          => '1',   // Esc closes open submenus
			'skip_nav_link'          => '1',   // "Skip to content" link
			'skip_nav_text'          => 'Skip to content',
			'skip_nav_target'        => 'main',

			// ── 2. Focus Appearance (WCAG 2.4.11) ───────────────────────
			'focus_style'            => 'ring',   // ring | fill | custom | theme
			'focus_color'            => '#005FCC', // ≥3:1 contrast on white
			'focus_width'            => '3',       // px — WCAG req min 2px area
			'focus_offset'           => '2',       // px outline-offset
			'focus_not_obscured'     => '1',       // Scroll when sticky hides focused el
			'focus_trap_mobile'      => '1',       // Trap focus in mobile overlay

			// ── 3. ARIA & Screen Readers ─────────────────────────────────
			'nav_aria_label'         => 'Main navigation',
			'aria_expanded'          => '1',       // aria-expanded on triggers
			'aria_current'           => 'page',    // page | true | disabled
			'aria_haspopup_mega'     => 'dialog',  // menu | dialog | false
			'icon_aria_hidden'       => '1',       // Hide decorative icons from AT
			'badge_sr_text'          => 'New notification',
			'hamburger_label_open'   => 'Open menu',
			'hamburger_label_close'  => 'Close menu',
			'live_region'            => 'off',     // off | polite | assertive
			'heading_level'          => 'h3',      // h2|h3|h4|span (mega menu headings)

			// ── 4. Motion & Animations ───────────────────────────────────
			'reduced_motion'         => '1',       // Respect prefers-reduced-motion
			'anim_multiplier'        => '1',       // 0 | 0.5 | 1 | 2
			'hover_delay'            => '150',     // ms before submenu closes

			// ── 5. Contrast & Visibility ─────────────────────────────────
			'enforce_contrast'       => 'warn',    // false | warn | block
			'respect_prefers_contrast' => '1',
			'forced_colors'          => '1',       // Windows High Contrast / forced-colors
			'text_spacing'           => '1',       // WCAG 1.4.12 text spacing compat

			// ── 6. Mobile & Touch (WCAG 2.5.8) ──────────────────────────
			'touch_target'           => '44',      // px — 24 min WCAG, 44 recommended
			'touch_spacing'          => '8',       // px gap between targets
			'prevent_zoom_disable'   => '1',       // Never disable pinch-zoom
			'close_btn_position'     => 'top_right', // top_right | top_left | bottom_center
		);
	}

	// ─────────────────────────────────────────────────────────────────────
	// Settings I/O
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Return settings merged with defaults (cached).
	 *
	 * @return array
	 */
	public static function get_settings() {
		if ( null === self::$settings ) {
			self::$settings = wp_parse_args(
				(array) get_option( self::OPTION_KEY, array() ),
				self::defaults()
			);
		}
		return self::$settings;
	}

	/** Invalidate settings cache (call after save). */
	public static function clear_cache() {
		self::$settings = null;
	}

	/**
	 * Sanitize raw POST data and persist to database.
	 *
	 * @param array $raw Raw, unslashed POST data.
	 */
	public static function save( $raw ) {
		if ( ! is_array( $raw ) ) return;

		$d = self::defaults();
		$s = array();

		// ── Boolean toggles ──────────────────────────────────────────────
		$bool_keys = array(
			'enabled',
			'keyboard_nav', 'arrow_keys', 'home_end_keys', 'escape_closes',
			'skip_nav_link', 'focus_not_obscured', 'focus_trap_mobile',
			'aria_expanded', 'icon_aria_hidden', 'reduced_motion',
			'respect_prefers_contrast', 'forced_colors', 'text_spacing',
			'prevent_zoom_disable',
		);
		foreach ( $bool_keys as $k ) {
			$s[ $k ] = ! empty( $raw[ $k ] ) ? '1' : '0';
		}

		// ── Selects (whitelist allowed values) ───────────────────────────
		$select_map = array(
			'tab_behavior'       => array( 'top_level_only', 'all_items' ),
			'focus_style'        => array( 'ring', 'fill', 'custom', 'theme' ),
			'aria_current'       => array( 'page', 'true', 'disabled' ),
			'aria_haspopup_mega' => array( 'menu', 'dialog', 'false' ),
			'live_region'        => array( 'off', 'polite', 'assertive' ),
			'heading_level'      => array( 'h2', 'h3', 'h4', 'span' ),
			'enforce_contrast'   => array( 'false', 'warn', 'block' ),
			'close_btn_position' => array( 'top_right', 'top_left', 'bottom_center' ),
		);
		foreach ( $select_map as $k => $allowed ) {
			$val       = sanitize_text_field( $raw[ $k ] ?? $d[ $k ] );
			$s[ $k ]   = in_array( $val, $allowed, true ) ? $val : $d[ $k ];
		}

		// ── Color ────────────────────────────────────────────────────────
		$s['focus_color'] = ! empty( $raw['focus_color'] )
			? ( sanitize_hex_color( $raw['focus_color'] ) ?: $d['focus_color'] )
			: $d['focus_color'];

		// ── Integers ─────────────────────────────────────────────────────
		foreach ( array( 'focus_width', 'focus_offset', 'touch_target', 'touch_spacing', 'hover_delay' ) as $k ) {
			$s[ $k ] = isset( $raw[ $k ] ) ? absint( $raw[ $k ] ) : absint( $d[ $k ] );
		}

		// ── Float select (animation multiplier) ──────────────────────────
		$allowed_mult   = array( '0', '0.5', '1', '2' );
		$mult_raw       = sanitize_text_field( $raw['anim_multiplier'] ?? '1' );
		$s['anim_multiplier'] = in_array( $mult_raw, $allowed_mult, true ) ? $mult_raw : '1';

		// ── Free-text strings (sanitize_text_field) ───────────────────────
		$text_keys = array(
			'skip_nav_text', 'skip_nav_target', 'nav_aria_label', 'badge_sr_text',
			'hamburger_label_open', 'hamburger_label_close',
		);
		foreach ( $text_keys as $k ) {
			$s[ $k ] = sanitize_text_field( $raw[ $k ] ?? $d[ $k ] );
		}

		update_option( self::OPTION_KEY, $s );
		self::clear_cache();
	}

	// ─────────────────────────────────────────────────────────────────────
	// HTML Output Helpers
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Return the skip-navigation link HTML string.
	 * Call this immediately before <nav> in the shortcode output.
	 *
	 * @param  array|null $s  Settings (null = auto-load).
	 * @return string
	 */
	public static function output_skip_link( $s = null ) {
		if ( null === $s ) $s = self::get_settings();
		if ( ( $s['enabled'] ?? '0' ) !== '1' ) return '';
		if ( ( $s['skip_nav_link'] ?? '1' ) !== '1' ) return '';

		$target = '#' . esc_attr( ltrim( $s['skip_nav_target'] ?? 'main', '#' ) );
		$text   = esc_html( $s['skip_nav_text'] ?? 'Skip to content' );

		return '<a class="menux-skip-link" href="' . $target . '">' . $text . '</a>';
	}

	/**
	 * Return the ARIA live-region element HTML string.
	 * Call this once, right after </nav> or at the end of the shortcode.
	 *
	 * @param  array|null $s  Settings (null = auto-load).
	 * @return string
	 */
	public static function output_live_region( $s = null ) {
		if ( null === $s ) $s = self::get_settings();
		if ( ( $s['enabled'] ?? '0' ) !== '1' ) return '';
		$lr = $s['live_region'] ?? 'off';
		if ( $lr === 'off' ) return '';

		return '<div id="menux-live-region" class="menux-sr-only" role="status" '
			 . 'aria-live="' . esc_attr( $lr ) . '" aria-atomic="true"></div>';
	}

	// ─────────────────────────────────────────────────────────────────────
	// CSS Generation
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Generate all accessibility-related CSS.
	 * Output is injected via wp_add_inline_style().
	 *
	 * @param  array|null $s  Settings (null = auto-load).
	 * @return string  Raw CSS string (no <style> tags).
	 */
	public static function generate_css( $s = null ) {
		if ( null === $s ) $s = self::get_settings();

		// Master switch — output nothing when accessibility features are disabled.
		if ( ( $s['enabled'] ?? '0' ) !== '1' ) return '';

		$css = '';

		// ── Utility: screen-reader only ───────────────────────────────────
		$css .= '.menux-sr-only{'
			  . 'position:absolute;width:1px;height:1px;padding:0;margin:-1px;'
			  . 'overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;'
			  . '}';

		// ── Skip link (WCAG 2.4.1) ────────────────────────────────────────
		if ( ( $s['skip_nav_link'] ?? '1' ) === '1' ) {
			$skip_bg = sanitize_hex_color( $s['focus_color'] ?? '#005FCC' ) ?: '#005FCC';
			$css .= '.menux-skip-link{'
				  . 'position:absolute;left:-9999px;top:auto;'
				  . 'width:1px;height:1px;overflow:hidden;'
				  . 'background:' . $skip_bg . ';color:#fff;'
				  . 'font-size:14px;font-weight:600;text-decoration:none;'
				  . 'padding:12px 24px;border-radius:0 0 6px 0;'
				  . 'z-index:100000;'
				  . '}'
				  . '.menux-skip-link:focus,.menux-skip-link:focus-visible{'
				  . 'position:fixed;left:0;top:0;'
				  . 'width:auto;height:auto;overflow:visible;'
				  . 'outline:3px solid rgba(255,255,255,.85);outline-offset:3px;'
				  . '}';
		}

		// ── Focus ring (WCAG 2.4.11) ──────────────────────────────────────
		$focus_style  = $s['focus_style']  ?? 'ring';
		$focus_color  = sanitize_hex_color( $s['focus_color']  ?? '#005FCC' ) ?: '#005FCC';
		$focus_width  = max( 2, intval( $s['focus_width']  ?? 3 ) );
		$focus_offset = max( 0, intval( $s['focus_offset'] ?? 2 ) );

		// Selectors that need the focus indicator
		$fsel = implode( ',', array(
			'.menux-container a.menux-link:focus-visible',
			'.menux-container button.menux-hamburger:focus-visible',
			'.menux-container .menux-hamburger:focus-visible',
			'#menux-close-btn:focus-visible',
			'.menux-submenu a:focus-visible',
			'.menux-mega-panel a:focus-visible',
			'.menux-mega-panel button:focus-visible',
		) );

		if ( $focus_style === 'theme' ) {
			// No override — theme CSS handles focus.
		} elseif ( $focus_style === 'fill' ) {
			$css .= $fsel . '{'
				  . 'outline:none!important;'
				  . 'background:' . $focus_color . '!important;'
				  . 'color:#fff!important;'
				  . 'box-shadow:0 0 0 ' . $focus_width . 'px ' . $focus_color . '!important;'
				  . 'border-radius:4px;'
				  . '}';
		} else {
			// 'ring' (default) or 'custom'
			$css .= $fsel . '{'
				  . 'outline:' . $focus_width . 'px solid ' . $focus_color . '!important;'
				  . 'outline-offset:' . $focus_offset . 'px!important;'
				  . 'border-radius:4px;'
				  . '}';
		}

		// Remove outline on mouse/pointer clicks (keep only for keyboard: :focus-visible)
		if ( $focus_style !== 'theme' ) {
			$css .= '.menux-container a.menux-link:focus:not(:focus-visible),'
				  . '.menux-container .menux-hamburger:focus:not(:focus-visible),'
				  . '#menux-close-btn:focus:not(:focus-visible){'
				  . 'outline:none!important;box-shadow:none!important;'
				  . '}';
		}

		// ── Keyboard-opened submenus (JS adds .menux-open) ────────────────
		// Standard submenu shown via CSS :hover — also show when keyboard opens it.
		$css .= '.menux-has-children.menux-open > .menux-submenu{'
			  . 'opacity:1!important;'
			  . 'pointer-events:auto!important;'
			  . 'transform:translateY(0)!important;'
			  . 'max-height:none!important;'
			  . 'display:block!important;'
			  . '}';

		// ── Touch target size (WCAG 2.5.8) ───────────────────────────────
		$touch   = max( 24, intval( $s['touch_target']  ?? 44 ) );
		$spacing = max( 0,  intval( $s['touch_spacing'] ?? 8  ) );

		$css .= '.menux-container a.menux-link{'
			  . 'min-height:' . $touch . 'px;'
			  . 'display:inline-flex;align-items:center;'
			  . '}';
		$css .= '.menux-submenu a.menux-link{'
			  . 'min-height:' . $touch . 'px!important;'
			  . '}';
		// Touch target on hamburger: only set min-size — never override display.
		// The main CSS controls display:none (desktop) / display:flex (mobile).
		$css .= '.menux-hamburger{'
			  . 'min-width:' . $touch . 'px;min-height:' . $touch . 'px;'
			  . 'box-sizing:content-box;'
			  . '}';
		$css .= '#menux-close-btn{'
			  . 'min-width:' . $touch . 'px;min-height:' . $touch . 'px;'
			  . '}';
		if ( $spacing > 0 ) {
			$css .= '.menux-submenu li + li > a.menux-link{margin-top:' . $spacing . 'px;}';
		}

		// ── Close button position ─────────────────────────────────────────
		$close_pos = $s['close_btn_position'] ?? 'top_right';
		if ( $close_pos === 'top_left' ) {
			$css .= '#menux-close-btn{right:auto!important;left:16px!important;}';
		} elseif ( $close_pos === 'bottom_center' ) {
			$css .= '#menux-close-btn{'
				  . 'top:auto!important;bottom:24px!important;'
				  . 'left:50%!important;right:auto!important;'
				  . 'transform:translateX(-50%)!important;'
				  . '}';
		}

		// ── prefers-reduced-motion (WCAG 2.3.3) ──────────────────────────
		if ( ( $s['reduced_motion'] ?? '1' ) === '1' ) {
			$css .= '@media(prefers-reduced-motion:reduce){'
				  . '.menux-container,.menux-container *{'
				  . 'animation-duration:.001ms!important;'
				  . 'animation-iteration-count:1!important;'
				  . 'transition-duration:.001ms!important;'
				  . 'scroll-behavior:auto!important;'
				  . '}'
				  . '.menux-notif-dot{'
				  . 'animation:none!important;'
				  . 'box-shadow:0 0 0 2px rgba(239,68,68,.7)!important;'
				  . '}'
				  . '}';
		}

		// ── Animation multiplier = 0 (admin override, no motion) ─────────
		if ( floatval( $s['anim_multiplier'] ?? 1 ) === 0.0 ) {
			$css .= '.menux-container,.menux-container *{'
				  . 'animation-duration:.001ms!important;'
				  . 'transition-duration:.001ms!important;'
				  . '}';
		}

		// ── prefers-contrast (WCAG 1.4.3) ────────────────────────────────
		if ( ( $s['respect_prefers_contrast'] ?? '1' ) === '1' ) {
			$css .= '@media(prefers-contrast:more){'
				  . '.menux-container{border-bottom:2px solid currentColor!important;}'
				  . '.menux-container a.menux-link{text-decoration:underline;}'
				  . '.menux-container a.menux-link:hover{text-decoration:underline;}'
				  . '.menux-submenu{border:2px solid currentColor!important;}'
				  . '.menux-mega-panel{border:2px solid currentColor!important;}'
				  . '.menux-hamburger span{background:currentColor!important;}'
				  . '}';
		}

		// ── forced-colors / Windows High Contrast Mode ────────────────────
		if ( ( $s['forced_colors'] ?? '1' ) === '1' ) {
			$css .= '@media(forced-colors:active){'
				  . '.menux-container{'
				  . 'background:Canvas;border-color:ButtonText;'
				  . 'forced-color-adjust:none;'
				  . '}'
				  . '.menux-container a.menux-link{'
				  . 'color:LinkText;forced-color-adjust:auto;'
				  . '}'
				  . '.menux-container a.menux-link:hover,'
				  . '.menux-container a.menux-link:focus-visible{'
				  . 'color:Highlight;outline-color:Highlight;'
				  . '}'
				  . '.menux-container a.menux-link.active,'
				  . '.menux-container a.menux-link[aria-current]{'
				  . 'color:Highlight;text-decoration:underline;'
				  . '}'
				  . '.menux-hamburger{'
				  . 'background:ButtonFace;border:1px solid ButtonText;'
				  . 'forced-color-adjust:none;'
				  . '}'
				  . '.menux-hamburger span{background:ButtonText;}'
				  . '#menux-close-btn{'
				  . 'background:ButtonFace;border:1px solid ButtonText;'
				  . 'color:ButtonText;forced-color-adjust:none;'
				  . '}'
				  . '.menux-submenu{background:Canvas;border:1px solid ButtonText;}'
				  . '.menux-submenu a.menux-link{color:LinkText;}'
				  . '.menux-badge{'
				  . 'background:Highlight;color:HighlightText;forced-color-adjust:none;'
				  . '}'
				  . '.menux-notif-dot{'
				  . 'background:Highlight;forced-color-adjust:none;'
				  . '}'
				  . '.menux-mega-panel{background:Canvas;border:1px solid ButtonText;}'
				  . '.menux-mega-panel a{color:LinkText;}'
				  . '.menux-mega-panel h2,.menux-mega-panel h3,.menux-mega-panel h4{'
				  . 'color:ButtonText;forced-color-adjust:auto;'
				  . '}'
				  . '}';
		}

		// ── Text spacing compatibility (WCAG 1.4.12) ─────────────────────
		// Prevents text clipping when user sets custom spacing via extensions.
		if ( ( $s['text_spacing'] ?? '1' ) === '1' ) {
			$css .= '.menux-container a.menux-link{'
				  . 'height:auto!important;overflow:visible!important;'
				  . '}'
				  . '.menux-label{'
				  . 'overflow:visible!important;white-space:normal!important;'
				  . '}';
		}

		return $css;
	}

	// ─────────────────────────────────────────────────────────────────────
	// JS Configuration
	// ─────────────────────────────────────────────────────────────────────

	/**
	 * Return accessibility config array for injection into frontend JS.
	 * Added to window.menuxFrontendData.a11y.
	 *
	 * @param  array|null $s  Settings (null = auto-load).
	 * @return array
	 */
	public static function get_js_config( $s = null ) {
		if ( null === $s ) $s = self::get_settings();

		return array(
			'enabled'             => ( $s['enabled']             ?? '0' ) === '1',
			'keyboardNav'         => ( $s['keyboard_nav']       ?? '1' ) === '1',
			'tabBehavior'         => $s['tab_behavior']          ?? 'top_level_only',
			'arrowKeys'           => ( $s['arrow_keys']          ?? '1' ) === '1',
			'homeEndKeys'         => ( $s['home_end_keys']       ?? '1' ) === '1',
			'escapeCloses'        => ( $s['escape_closes']       ?? '1' ) === '1',
			'focusNotObscured'    => ( $s['focus_not_obscured']  ?? '1' ) === '1',
			'focusTrapMobile'     => ( $s['focus_trap_mobile']   ?? '1' ) === '1',
			'ariaCurrent'         => $s['aria_current']          ?? 'page',
			'liveRegion'          => $s['live_region']           ?? 'off',
			'hamburgerLabelOpen'  => $s['hamburger_label_open']  ?? 'Open menu',
			'hamburgerLabelClose' => $s['hamburger_label_close'] ?? 'Close menu',
			'hoverDelay'          => max( 0, intval( $s['hover_delay'] ?? 150 ) ),
			'reducedMotion'       => ( $s['reduced_motion']      ?? '1' ) === '1',
			'animMultiplier'      => floatval( $s['anim_multiplier'] ?? 1 ),
		);
	}
}
