<?php
/**
 * Centralized sanitization class.
 *
 * @package LoginPageCustomizerAndDesigner
 */

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

/**
 * Class LPCD_Sanitizer
 *
 * Provides static helpers for sanitizing all plugin input.
 */
class LPCD_Sanitizer {

	/**
	 * Sanitize a hex color value.
	 *
	 * @param string $value Raw color value.
	 * @return string Sanitized hex color or empty string.
	 */
	public static function sanitize_color( $value ) {
		$color = sanitize_hex_color( $value );
		return $color ? $color : '';
	}

	/**
	 * Sanitize an integer within a range.
	 *
	 * @param mixed $value Raw value.
	 * @param int   $min   Minimum allowed.
	 * @param int   $max   Maximum allowed.
	 * @return int Clamped integer.
	 */
	public static function sanitize_int( $value, $min = 0, $max = PHP_INT_MAX ) {
		$int_val = intval( $value );
		return max( $min, min( $max, $int_val ) );
	}

	/**
	 * Sanitize a float within a range.
	 *
	 * @param mixed $value Raw value.
	 * @param float $min   Minimum allowed.
	 * @param float $max   Maximum allowed.
	 * @return float Clamped float.
	 */
	public static function sanitize_float( $value, $min = 0.0, $max = PHP_FLOAT_MAX ) {
		$float_val = floatval( $value );
		return max( $min, min( $max, $float_val ) );
	}

	/**
	 * Sanitize a value against a whitelist of allowed options.
	 *
	 * @param string $value     Raw value.
	 * @param array  $whitelist Allowed values.
	 * @param string $default   Default if not in whitelist.
	 * @return string Sanitized value.
	 */
	public static function sanitize_select( $value, $whitelist, $default = '' ) {
		$value = sanitize_text_field( $value );
		return in_array( $value, $whitelist, true ) ? $value : $default;
	}

	/**
	 * Sanitize a text string.
	 *
	 * @param string $value Raw text.
	 * @return string Sanitized text.
	 */
	public static function sanitize_text( $value ) {
		return sanitize_text_field( $value );
	}

	/**
	 * Sanitize a URL.
	 *
	 * @param string $value Raw URL.
	 * @return string Sanitized URL.
	 */
	public static function sanitize_url( $value ) {
		return esc_url_raw( $value );
	}

	/**
	 * Sanitize CSS (strip all HTML tags).
	 *
	 * @param string $value Raw CSS.
	 * @return string Sanitized CSS.
	 */
	public static function sanitize_css( $value ) {
		return wp_strip_all_tags( $value );
	}

	/**
	 * Sanitize a boolean value.
	 *
	 * @param mixed $value Raw value.
	 * @return bool Boolean value.
	 */
	public static function sanitize_bool( $value ) {
		return rest_sanitize_boolean( $value );
	}

	/**
	 * Sanitize template JSON data.
	 *
	 * Validates structure and sanitizes each field by its expected type.
	 *
	 * @param array $data Decoded JSON data as associative array.
	 * @return array|WP_Error Sanitized template data or error.
	 */
	public static function sanitize_template_json( $data ) {
		if ( ! is_array( $data ) ) {
			return new WP_Error(
				'lpcd_invalid_json',
				__( 'Invalid template data format.', 'login-page-customizer-and-designer' )
			);
		}

		$required_keys = array( 'pageSettings', 'blocks' );
		foreach ( $required_keys as $key ) {
			if ( ! isset( $data[ $key ] ) ) {
				return new WP_Error(
					'lpcd_missing_key',
					/* translators: %s: Missing key name. */
					sprintf( __( 'Missing required key: %s', 'login-page-customizer-and-designer' ), $key )
				);
			}
		}

		$sanitized = array();

		// Top-level optional fields.
		$sanitized['id']      = isset( $data['id'] ) ? sanitize_key( $data['id'] ) : '';
		$sanitized['name']    = isset( $data['name'] ) ? sanitize_text_field( $data['name'] ) : '';
		$sanitized['version'] = isset( $data['version'] ) ? sanitize_text_field( $data['version'] ) : '1.0';

		// Page settings.
		$sanitized['pageSettings'] = self::sanitize_page_settings(
			is_array( $data['pageSettings'] ) ? $data['pageSettings'] : array()
		);

		// Blocks.
		$sanitized['blocks'] = array();
		if ( is_array( $data['blocks'] ) ) {
			foreach ( $data['blocks'] as $block ) {
				if ( is_array( $block ) ) {
					$sanitized['blocks'][] = self::sanitize_block( $block );
				}
			}
		}

		return $sanitized;
	}

	/**
	 * Sanitize page-level settings.
	 *
	 * @param array $settings Raw page settings.
	 * @return array Sanitized page settings.
	 */
	public static function sanitize_page_settings( $settings ) {
		$bg_types    = array( 'color', 'gradient', 'image', 'animated-gradient', 'video' );
		$anim_types  = array( 'none', 'fadeIn', 'slideUp', 'zoomIn' );
		$bg_pos      = array( 'center', 'top', 'bottom', 'left', 'right' );
		$bg_size     = array( 'cover', 'contain', 'auto' );
		$layout_opts = array( 'centered', 'split_left', 'split_right' );
		$align_opts  = array( 'center', 'flex-start', 'flex-end', 'stretch' );

		$sanitized = array();

		// Layout options.
		$sanitized['layoutType'] = isset( $settings['layoutType'] )
			? self::sanitize_select( $settings['layoutType'], $layout_opts, 'centered' )
			: 'centered';
		$sanitized['splitAlignment'] = isset( $settings['splitAlignment'] )
			? self::sanitize_select( $settings['splitAlignment'], $align_opts, 'center' )
			: 'center';
		$sanitized['splitFormWidth'] = isset( $settings['splitFormWidth'] )
			? self::sanitize_int( $settings['splitFormWidth'], 20, 80 )
			: 40;

		// Branding panel settings (for split layouts).
		$bp = isset( $settings['brandingPanel'] ) && is_array( $settings['brandingPanel'] )
			? $settings['brandingPanel']
			: array();
		$bp_bg_types = array( 'color', 'gradient', 'image' );
		$bp_v_align  = array( 'center', 'flex-start', 'flex-end' );
		$sanitized['brandingPanel'] = array(
			'backgroundColor'  => isset( $bp['backgroundColor'] ) ? self::sanitize_color( $bp['backgroundColor'] ) : '#1a1a2e',
			'backgroundType'   => isset( $bp['backgroundType'] ) ? self::sanitize_select( $bp['backgroundType'], $bp_bg_types, 'color' ) : 'color',
			'backgroundImage'  => isset( $bp['backgroundImage'] ) ? self::sanitize_url( $bp['backgroundImage'] ) : '',
			'backgroundPosition' => isset( $bp['backgroundPosition'] ) ? self::sanitize_select( $bp['backgroundPosition'], $bg_pos, 'center' ) : 'center',
			'backgroundSize'   => isset( $bp['backgroundSize'] ) ? self::sanitize_select( $bp['backgroundSize'], $bg_size, 'cover' ) : 'cover',
			'gradientColor1'   => isset( $bp['gradientColor1'] ) ? self::sanitize_color( $bp['gradientColor1'] ) : '#1a1a2e',
			'gradientColor2'   => isset( $bp['gradientColor2'] ) ? self::sanitize_color( $bp['gradientColor2'] ) : '#16213e',
			'gradientAngle'    => isset( $bp['gradientAngle'] ) ? self::sanitize_int( $bp['gradientAngle'], 0, 360 ) : 135,
			'textColor'        => isset( $bp['textColor'] ) ? self::sanitize_color( $bp['textColor'] ) : '#ffffff',
			'verticalAlign'    => isset( $bp['verticalAlign'] ) ? self::sanitize_select( $bp['verticalAlign'], $bp_v_align, 'center' ) : 'center',
		);

		$sanitized['backgroundColor'] = isset( $settings['backgroundColor'] )
			? self::sanitize_color( $settings['backgroundColor'] )
			: '#f0f0f1';

		$sanitized['backgroundType'] = isset( $settings['backgroundType'] )
			? self::sanitize_select( $settings['backgroundType'], $bg_types, 'color' )
			: 'color';

		// Pro gating: force free users back to 'color' if they try Pro background types.
		$pro_bg_types = array( 'animated-gradient', 'video' );
		if ( ! lpcd_is_pro() && in_array( $sanitized['backgroundType'], $pro_bg_types, true ) ) {
			$sanitized['backgroundType'] = 'color';
		}

		$sanitized['backgroundImage'] = isset( $settings['backgroundImage'] )
			? self::sanitize_url( $settings['backgroundImage'] )
			: '';

		// Background gradient.
		$grad = isset( $settings['backgroundGradient'] ) && is_array( $settings['backgroundGradient'] )
			? $settings['backgroundGradient']
			: array();
		$sanitized['backgroundGradient'] = array(
			'color1' => isset( $grad['color1'] ) ? self::sanitize_color( $grad['color1'] ) : '#667eea',
			'color2' => isset( $grad['color2'] ) ? self::sanitize_color( $grad['color2'] ) : '#764ba2',
			'angle'  => isset( $grad['angle'] ) ? self::sanitize_int( $grad['angle'], 0, 360 ) : 135,
		);

		// Background overlay.
		$overlay = isset( $settings['backgroundOverlay'] ) && is_array( $settings['backgroundOverlay'] )
			? $settings['backgroundOverlay']
			: array();
		$sanitized['backgroundOverlay'] = array(
			'color'   => isset( $overlay['color'] ) ? self::sanitize_color( $overlay['color'] ) : '#000000',
			'opacity' => isset( $overlay['opacity'] ) ? self::sanitize_float( $overlay['opacity'], 0, 1 ) : 0,
		);

		// Background position and size (for image type).
		$sanitized['backgroundPosition'] = isset( $settings['backgroundPosition'] )
			? self::sanitize_select( $settings['backgroundPosition'], $bg_pos, 'center' )
			: 'center';

		$sanitized['backgroundSize'] = isset( $settings['backgroundSize'] )
			? self::sanitize_select( $settings['backgroundSize'], $bg_size, 'cover' )
			: 'cover';

		$sanitized['overlayEnabled'] = isset( $settings['overlayEnabled'] )
			? self::sanitize_bool( $settings['overlayEnabled'] )
			: false;

		$sanitized['overlayColor'] = isset( $settings['overlayColor'] )
			? self::sanitize_color( $settings['overlayColor'] )
			: '#000000';

		$sanitized['overlayOpacity'] = isset( $settings['overlayOpacity'] )
			? self::sanitize_float( $settings['overlayOpacity'], 0, 1 )
			: 0.4;

		// Video background settings.
		$video_sources = array( 'youtube', 'upload' );
		$sanitized['videoSource'] = isset( $settings['videoSource'] )
			? self::sanitize_select( $settings['videoSource'], $video_sources, 'youtube' )
			: 'youtube';

		$sanitized['videoYoutubeUrl'] = isset( $settings['videoYoutubeUrl'] )
			? self::sanitize_url( $settings['videoYoutubeUrl'] )
			: '';

		$sanitized['videoUploadUrl'] = isset( $settings['videoUploadUrl'] )
			? self::sanitize_url( $settings['videoUploadUrl'] )
			: '';

		$sanitized['videoFallbackImage'] = isset( $settings['videoFallbackImage'] )
			? self::sanitize_url( $settings['videoFallbackImage'] )
			: '';

		$sanitized['videoPlayOnMobile'] = isset( $settings['videoPlayOnMobile'] )
			? self::sanitize_bool( $settings['videoPlayOnMobile'] )
			: false;

		$video_fits = array( 'cover', 'contain', 'fill' );
		$sanitized['videoObjectFit'] = isset( $settings['videoObjectFit'] )
			? self::sanitize_select( $settings['videoObjectFit'], $video_fits, 'cover' )
			: 'cover';

		// Page animation.
		$anim = isset( $settings['pageAnimation'] ) && is_array( $settings['pageAnimation'] )
			? $settings['pageAnimation']
			: array();
		$sanitized['pageAnimation'] = array(
			'type'     => isset( $anim['type'] ) ? self::sanitize_select( $anim['type'], $anim_types, 'none' ) : 'none',
			'duration' => isset( $anim['duration'] ) ? self::sanitize_int( $anim['duration'], 200, 1500 ) : 600,
		);

		// Custom CSS.
		$sanitized['customCss'] = isset( $settings['customCss'] )
			? self::sanitize_css( $settings['customCss'] )
			: '';

		// Animated gradient.
		$ag = isset( $settings['animatedGradient'] ) && is_array( $settings['animatedGradient'] )
			? $settings['animatedGradient']
			: array();
		$ag_colors = array();
		if ( isset( $ag['colors'] ) && is_array( $ag['colors'] ) ) {
			foreach ( array_slice( $ag['colors'], 0, 5 ) as $c ) {
				$sanitized_c = self::sanitize_color( $c );
				if ( ! empty( $sanitized_c ) ) {
					$ag_colors[] = $sanitized_c;
				}
			}
		}
		if ( count( $ag_colors ) < 2 ) {
			$ag_colors = array( '#667eea', '#764ba2' );
		}
		$sanitized['animatedGradient'] = array(
			'colors' => $ag_colors,
			'angle'  => isset( $ag['angle'] ) ? self::sanitize_int( $ag['angle'], 0, 360 ) : 135,
			'speed'  => isset( $ag['speed'] ) ? self::sanitize_int( $ag['speed'], 2, 20 ) : 8,
		);

		return $sanitized;
	}

	/**
	 * Sanitize a single block.
	 *
	 * @param array $block Raw block data.
	 * @return array Sanitized block data.
	 */
	public static function sanitize_block( $block ) {
		$valid_types  = array( 'loginForm', 'logo', 'heading', 'text', 'divider', 'videoBackground', 'customHtml' );
		$valid_panels = array( 'branding', 'form' );

		$block_type    = isset( $block['type'] ) ? self::sanitize_select( $block['type'], $valid_types, 'text' ) : 'text';
		$default_panel = 'loginForm' === $block_type ? 'form' : 'branding';

		$sanitized = array(
			'id'       => isset( $block['id'] ) ? sanitize_key( $block['id'] ) : wp_generate_uuid4(),
			'type'     => $block_type,
			'order'    => isset( $block['order'] ) ? absint( $block['order'] ) : 0,
			'panel'    => isset( $block['panel'] ) ? self::sanitize_select( $block['panel'], $valid_panels, $default_panel ) : $default_panel,
			'settings' => array(),
		);

		if ( isset( $block['settings'] ) && is_array( $block['settings'] ) ) {
			$sanitized['settings'] = self::sanitize_block_settings( $sanitized['type'], $block['settings'] );
		}

		return $sanitized;
	}

	/**
	 * Sanitize block settings based on block type.
	 *
	 * @param string $type     Block type.
	 * @param array  $settings Raw settings.
	 * @return array Sanitized settings.
	 */
	public static function sanitize_block_settings( $type, $settings ) {
		switch ( $type ) {
			case 'loginForm':
				return self::sanitize_login_form_settings( $settings );
			case 'logo':
				return self::sanitize_logo_settings( $settings );
			case 'heading':
				return self::sanitize_heading_settings( $settings );
			case 'text':
				return self::sanitize_text_block_settings( $settings );
			case 'divider':
				return self::sanitize_divider_settings( $settings );
			case 'videoBackground':
				return self::sanitize_video_background_settings( $settings );
			case 'customHtml':
				return self::sanitize_custom_html_settings( $settings );
			default:
				return array();
		}
	}

	/**
	 * Sanitize Login Form block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_login_form_settings( $s ) {
		$shadow_opts     = array( 'none', 'soft', 'medium', 'strong', 'custom' );
		$anim_type_opts  = array( 'none', 'fadeIn', 'slideUp', 'zoomIn' );
		$anim_dur_opts   = array( 'fast', 'normal', 'slow' );
		$font_weight_opts = array( '400', '500', '600', '700' );
		$hover_opts      = array( 'none', 'lift', 'glow', 'scale', 'pulse', 'bgSlide' );

		return array(
			// Form container.
			'formBackground'    => isset( $s['formBackground'] ) ? self::sanitize_color( $s['formBackground'] ) : '#ffffff',
			'formBorderRadius'  => isset( $s['formBorderRadius'] ) ? self::sanitize_int( $s['formBorderRadius'], 0, 20 ) : 4,
			'formPadding'       => isset( $s['formPadding'] ) ? self::sanitize_int( $s['formPadding'], 16, 60 ) : 26,
			'formWidth'         => isset( $s['formWidth'] ) ? self::sanitize_int( $s['formWidth'], 300, 480 ) : 360,
			'formBoxShadow'     => isset( $s['formBoxShadow'] ) ? self::sanitize_select( $s['formBoxShadow'], $shadow_opts, 'none' ) : 'none',
			'formBorderEnabled' => isset( $s['formBorderEnabled'] ) ? self::sanitize_bool( $s['formBorderEnabled'] ) : false,
			'formBorderColor'   => isset( $s['formBorderColor'] ) ? self::sanitize_color( $s['formBorderColor'] ) : '#e0e0e0',
			'formBorderWidth'   => isset( $s['formBorderWidth'] ) ? self::sanitize_int( $s['formBorderWidth'], 1, 4 ) : 1,

			// Block Margins
			'marginTop'         => isset( $s['marginTop'] ) ? self::sanitize_int( $s['marginTop'], 0, 100 ) : 0,
			'marginBottom'      => isset( $s['marginBottom'] ) ? self::sanitize_int( $s['marginBottom'], 0, 100 ) : 20,

			// Form animation.
			'animationType'     => isset( $s['animationType'] ) ? self::sanitize_select( $s['animationType'], $anim_type_opts, 'none' ) : 'none',
			'animationDuration' => isset( $s['animationDuration'] ) ? self::sanitize_select( $s['animationDuration'], $anim_dur_opts, 'normal' ) : 'normal',

			// Username field.
			'usernameLabel'           => isset( $s['usernameLabel'] ) ? self::sanitize_text( $s['usernameLabel'] ) : 'Username or Email Address',
			'usernameShowLabel'       => isset( $s['usernameShowLabel'] ) ? self::sanitize_bool( $s['usernameShowLabel'] ) : true,
			'usernamePlaceholder'     => isset( $s['usernamePlaceholder'] ) ? self::sanitize_text( $s['usernamePlaceholder'] ) : 'Enter your username',
			'usernameBackgroundColor' => isset( $s['usernameBackgroundColor'] ) ? self::sanitize_color( $s['usernameBackgroundColor'] ) : '#f9f9f9',
			'usernameBorderColor'     => isset( $s['usernameBorderColor'] ) ? self::sanitize_color( $s['usernameBorderColor'] ) : '#e0e0e0',
			'usernameBorderRadius'    => isset( $s['usernameBorderRadius'] ) ? self::sanitize_int( $s['usernameBorderRadius'], 0, 12 ) : 4,
			'usernameTextColor'       => isset( $s['usernameTextColor'] ) ? self::sanitize_color( $s['usernameTextColor'] ) : '#333333',
			'usernameFontSize'        => isset( $s['usernameFontSize'] ) ? self::sanitize_int( $s['usernameFontSize'], 12, 20 ) : 15,
			'usernameHeight'          => isset( $s['usernameHeight'] ) ? self::sanitize_int( $s['usernameHeight'], 36, 56 ) : 44,

			// Password field.
			'passwordLabel'           => isset( $s['passwordLabel'] ) ? self::sanitize_text( $s['passwordLabel'] ) : 'Password',
			'passwordShowLabel'       => isset( $s['passwordShowLabel'] ) ? self::sanitize_bool( $s['passwordShowLabel'] ) : true,
			'passwordPlaceholder'     => isset( $s['passwordPlaceholder'] ) ? self::sanitize_text( $s['passwordPlaceholder'] ) : 'Enter your password',
			'passwordBackgroundColor' => isset( $s['passwordBackgroundColor'] ) ? self::sanitize_color( $s['passwordBackgroundColor'] ) : '#f9f9f9',
			'passwordBorderColor'     => isset( $s['passwordBorderColor'] ) ? self::sanitize_color( $s['passwordBorderColor'] ) : '#e0e0e0',
			'passwordBorderRadius'    => isset( $s['passwordBorderRadius'] ) ? self::sanitize_int( $s['passwordBorderRadius'], 0, 12 ) : 4,
			'passwordTextColor'       => isset( $s['passwordTextColor'] ) ? self::sanitize_color( $s['passwordTextColor'] ) : '#333333',
			'passwordFontSize'        => isset( $s['passwordFontSize'] ) ? self::sanitize_int( $s['passwordFontSize'], 12, 20 ) : 15,
			'passwordHeight'          => isset( $s['passwordHeight'] ) ? self::sanitize_int( $s['passwordHeight'], 36, 56 ) : 44,
			'showPasswordToggle'      => isset( $s['showPasswordToggle'] ) ? self::sanitize_bool( $s['showPasswordToggle'] ) : false,

			// Submit button.
			'submitText'                => isset( $s['submitText'] ) ? self::sanitize_text( $s['submitText'] ) : '',
			'submitBackgroundColor'     => isset( $s['submitBackgroundColor'] ) ? self::sanitize_color( $s['submitBackgroundColor'] ) : '#2271b1',
			'submitTextColor'           => isset( $s['submitTextColor'] ) ? self::sanitize_color( $s['submitTextColor'] ) : '#ffffff',
			'submitHoverBackgroundColor' => isset( $s['submitHoverBackgroundColor'] ) ? self::sanitize_color( $s['submitHoverBackgroundColor'] ) : '#135e96',
			'submitBorderRadius'        => isset( $s['submitBorderRadius'] ) ? self::sanitize_int( $s['submitBorderRadius'], 0, 30 ) : 4,
			'submitHeight'              => isset( $s['submitHeight'] ) ? self::sanitize_int( $s['submitHeight'], 36, 56 ) : 44,
			'submitFontSize'            => isset( $s['submitFontSize'] ) ? self::sanitize_int( $s['submitFontSize'], 12, 22 ) : 16,
			'submitFontWeight'          => isset( $s['submitFontWeight'] ) ? self::sanitize_select( $s['submitFontWeight'], $font_weight_opts, '600' ) : '600',
			'submitFullWidth'           => isset( $s['submitFullWidth'] ) ? self::sanitize_bool( $s['submitFullWidth'] ) : true,

			// Button hover + spinner.
			'hoverEffect'               => isset( $s['hoverEffect'] ) ? self::sanitize_select( $s['hoverEffect'], $hover_opts, 'none' ) : 'none',
			'showLoadingSpinner'        => isset( $s['showLoadingSpinner'] ) ? self::sanitize_bool( $s['showLoadingSpinner'] ) : false,

			// Custom shadow.
			'shadowX'                   => isset( $s['shadowX'] ) ? self::sanitize_int( $s['shadowX'], -50, 50 ) : 0,
			'shadowY'                   => isset( $s['shadowY'] ) ? self::sanitize_int( $s['shadowY'], -50, 50 ) : 20,
			'shadowBlur'                => isset( $s['shadowBlur'] ) ? self::sanitize_int( $s['shadowBlur'], 0, 100 ) : 40,
			'shadowSpread'              => isset( $s['shadowSpread'] ) ? self::sanitize_int( $s['shadowSpread'], -20, 20 ) : 0,
			'shadowColor'               => isset( $s['shadowColor'] ) ? self::sanitize_text( $s['shadowColor'] ) : 'rgba(0,0,0,0.15)',
			'shadowInset'               => isset( $s['shadowInset'] ) ? self::sanitize_bool( $s['shadowInset'] ) : false,

			// Additional links.
			'showRememberMe'    => isset( $s['showRememberMe'] ) ? self::sanitize_bool( $s['showRememberMe'] ) : true,
			'rememberMeText'    => isset( $s['rememberMeText'] ) ? self::sanitize_text( $s['rememberMeText'] ) : 'Remember Me',
			'showLostPassword'  => isset( $s['showLostPassword'] ) ? self::sanitize_bool( $s['showLostPassword'] ) : true,
			'lostPasswordText'  => isset( $s['lostPasswordText'] ) ? self::sanitize_text( $s['lostPasswordText'] ) : 'Lost your password?',
			'lostPasswordColor' => isset( $s['lostPasswordColor'] ) ? self::sanitize_color( $s['lostPasswordColor'] ) : '#2271b1',
			'showBackLink'      => isset( $s['showBackLink'] ) ? self::sanitize_bool( $s['showBackLink'] ) : true,
			'showRegisterLink'  => isset( $s['showRegisterLink'] ) ? self::sanitize_bool( $s['showRegisterLink'] ) : true,
		);
	}

	/**
	 * Sanitize Logo block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_logo_settings( $s ) {
		$source_opts    = array( 'site', 'upload', 'url' );
		$alignment_opts = array( 'left', 'center', 'right' );

		return array(
			'source'       => isset( $s['source'] ) ? self::sanitize_select( $s['source'], $source_opts, 'site' ) : 'site',
			'imageUrl'     => isset( $s['imageUrl'] ) ? self::sanitize_url( $s['imageUrl'] ) : '',
			'width'        => isset( $s['width'] ) ? self::sanitize_int( $s['width'], 20, 300 ) : 80,
			'alignment'    => isset( $s['alignment'] ) ? self::sanitize_select( $s['alignment'], $alignment_opts, 'center' ) : 'center',
			'linkUrl'      => isset( $s['linkUrl'] ) ? self::sanitize_url( $s['linkUrl'] ) : '',
			'marginBottom' => isset( $s['marginBottom'] ) ? self::sanitize_int( $s['marginBottom'], 0, 60 ) : 20,
		);
	}

	/**
	 * Sanitize Heading block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_heading_settings( $s ) {
		$tag_opts         = array( 'h1', 'h2', 'h3', 'h4', 'p' );
		$alignment_opts   = array( 'left', 'center', 'right' );
		$font_weight_opts = array( '300', '400', '500', '600', '700', '800' );
		$font_opts        = self::get_allowed_fonts();

		return array(
			'text'          => isset( $s['text'] ) ? self::sanitize_text( $s['text'] ) : 'Welcome Back',
			'tag'           => isset( $s['tag'] ) ? self::sanitize_select( $s['tag'], $tag_opts, 'h1' ) : 'h1',
			'fontFamily'    => isset( $s['fontFamily'] ) ? self::sanitize_select( $s['fontFamily'], $font_opts, 'inherit' ) : 'inherit',
			'fontSize'      => isset( $s['fontSize'] ) ? self::sanitize_int( $s['fontSize'], 12, 60 ) : 24,
			'fontWeight'    => isset( $s['fontWeight'] ) ? self::sanitize_select( $s['fontWeight'], $font_weight_opts, '700' ) : '700',
			'textColor'     => isset( $s['textColor'] ) ? self::sanitize_color( $s['textColor'] ) : '#1d2327',
			'alignment'     => isset( $s['alignment'] ) ? self::sanitize_select( $s['alignment'], $alignment_opts, 'center' ) : 'center',
			'letterSpacing' => isset( $s['letterSpacing'] ) ? self::sanitize_float( $s['letterSpacing'], -2, 8 ) : 0,
			'marginTop'     => isset( $s['marginTop'] ) ? self::sanitize_int( $s['marginTop'], 0, 60 ) : 0,
			'marginBottom'  => isset( $s['marginBottom'] ) ? self::sanitize_int( $s['marginBottom'], 0, 40 ) : 12,
		);
	}

	/**
	 * Sanitize Text block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_text_block_settings( $s ) {
		$alignment_opts   = array( 'left', 'center', 'right' );
		$font_weight_opts = array( '300', '400', '500', '600' );
		$font_opts        = self::get_allowed_fonts();

		return array(
			'text'         => isset( $s['text'] ) ? self::sanitize_text( $s['text'] ) : 'Please sign in to continue.',
			'fontFamily'   => isset( $s['fontFamily'] ) ? self::sanitize_select( $s['fontFamily'], $font_opts, 'inherit' ) : 'inherit',
			'fontSize'     => isset( $s['fontSize'] ) ? self::sanitize_int( $s['fontSize'], 12, 24 ) : 14,
			'fontWeight'   => isset( $s['fontWeight'] ) ? self::sanitize_select( $s['fontWeight'], $font_weight_opts, '400' ) : '400',
			'textColor'    => isset( $s['textColor'] ) ? self::sanitize_color( $s['textColor'] ) : '#50575e',
			'alignment'    => isset( $s['alignment'] ) ? self::sanitize_select( $s['alignment'], $alignment_opts, 'center' ) : 'center',
			'lineHeight'   => isset( $s['lineHeight'] ) ? self::sanitize_float( $s['lineHeight'], 1.0, 3.0 ) : 1.6,
			'marginTop'    => isset( $s['marginTop'] ) ? self::sanitize_int( $s['marginTop'], 0, 60 ) : 0,
			'marginBottom' => isset( $s['marginBottom'] ) ? self::sanitize_int( $s['marginBottom'], 0, 40 ) : 16,
		);
	}

	/**
	 * Sanitize Divider block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_divider_settings( $s ) {
		$style_opts     = array( 'solid', 'dashed', 'dotted', 'invisible' );
		$alignment_opts = array( 'left', 'center', 'right' );

		return array(
			'style'        => isset( $s['style'] ) ? self::sanitize_select( $s['style'], $style_opts, 'solid' ) : 'solid',
			'color'        => isset( $s['color'] ) ? self::sanitize_color( $s['color'] ) : '#e0e0e0',
			'width'        => isset( $s['width'] ) ? self::sanitize_int( $s['width'], 10, 100 ) : 100,
			'thickness'    => isset( $s['thickness'] ) ? self::sanitize_int( $s['thickness'], 1, 8 ) : 1,
			'alignment'    => isset( $s['alignment'] ) ? self::sanitize_select( $s['alignment'], $alignment_opts, 'center' ) : 'center',
			'marginTop'    => isset( $s['marginTop'] ) ? self::sanitize_int( $s['marginTop'], 0, 60 ) : 12,
			'marginBottom' => isset( $s['marginBottom'] ) ? self::sanitize_int( $s['marginBottom'], 0, 60 ) : 12,
		);
	}

	/**
	 * Sanitize Video Background block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_video_background_settings( $s ) {
		$source_opts = array( 'youtube', 'selfhosted', 'upload' );
		return array(
			'source'         => isset( $s['source'] ) ? self::sanitize_select( $s['source'], $source_opts, 'youtube' ) : 'youtube',
			'youtubeUrl'     => isset( $s['youtubeUrl'] ) ? self::sanitize_url( $s['youtubeUrl'] ) : '',
			'videoUrl'       => isset( $s['videoUrl'] ) ? self::sanitize_url( $s['videoUrl'] ) : '',
			'videoUpload'    => isset( $s['videoUpload'] ) ? self::sanitize_url( $s['videoUpload'] ) : '',
			'autoplay'       => isset( $s['autoplay'] ) ? self::sanitize_bool( $s['autoplay'] ) : true,
			'mute'           => isset( $s['mute'] ) ? self::sanitize_bool( $s['mute'] ) : true,
			'loop'           => isset( $s['loop'] ) ? self::sanitize_bool( $s['loop'] ) : true,
			'overlayEnabled' => isset( $s['overlayEnabled'] ) ? self::sanitize_bool( $s['overlayEnabled'] ) : false,
			'overlayColor'   => isset( $s['overlayColor'] ) ? self::sanitize_color( $s['overlayColor'] ) : '#000000',
			'overlayOpacity' => isset( $s['overlayOpacity'] ) ? self::sanitize_float( $s['overlayOpacity'], 0, 1 ) : 0.4,
			'fallbackImage'  => isset( $s['fallbackImage'] ) ? self::sanitize_url( $s['fallbackImage'] ) : '',
		);
	}

	/**
	 * Sanitize Custom HTML block settings.
	 *
	 * @param array $s Raw settings.
	 * @return array Sanitized settings.
	 */
	private static function sanitize_custom_html_settings( $s ) {
		return array(
			'htmlContent' => isset( $s['htmlContent'] ) ? wp_kses_post( $s['htmlContent'] ) : '',
		);
	}

	/**
	 * Get allowed font family names.
	 *
	 * @return array List of allowed font names including 'inherit'.
	 */
	public static function get_allowed_fonts() {
		return array(
			'inherit',
			'system-ui',
			'Arial',
			'Helvetica Neue',
			'Segoe UI',
			'Verdana',
			'Tahoma',
			'Trebuchet MS',
			'Georgia',
			'Times New Roman',
			'Courier New',
			'Lucida Console',
			'custom',
		);
	}

	/**
	 * Get the display label for a system font.
	 *
	 * @return array Mapping of font value to display label.
	 */
	public static function get_font_labels() {
		return array(
			'inherit'        => 'Default (Theme)',
			'system-ui'      => 'System UI',
			'Arial'          => 'Arial',
			'Helvetica Neue' => 'Helvetica Neue',
			'Segoe UI'       => 'Segoe UI',
			'Verdana'        => 'Verdana',
			'Tahoma'         => 'Tahoma',
			'Trebuchet MS'   => 'Trebuchet MS',
			'Georgia'        => 'Georgia',
			'Times New Roman' => 'Times New Roman',
			'Courier New'    => 'Courier New',
			'Lucida Console' => 'Lucida Console',
			'custom'         => 'Custom Font (Upload)',
		);
	}

	/**
	 * Sanitize plugin settings data.
	 *
	 * @param array $data Raw settings data.
	 * @return array Sanitized settings.
	 */
	public static function sanitize_settings( $data ) {
		$schema    = LPCD_Settings_Manager::get_schema();
		$sanitized = array();

		foreach ( $data as $key => $value ) {
			if ( ! isset( $schema[ $key ] ) ) {
				continue;
			}

			$field = $schema[ $key ];

			switch ( $field['type'] ) {
				case 'bool':
					$sanitized[ $key ] = self::sanitize_bool( $value );
					break;
				case 'text':
					$sanitized[ $key ] = self::sanitize_text( $value );
					break;
				case 'url':
					$sanitized[ $key ] = self::sanitize_url( $value );
					break;
				case 'select':
					$sanitized[ $key ] = self::sanitize_select( $value, $field['options'], $field['default'] );
					break;
				case 'color':
					$sanitized[ $key ] = self::sanitize_color( $value );
					break;
				case 'int':
					$min = isset( $field['min'] ) ? $field['min'] : 0;
					$max = isset( $field['max'] ) ? $field['max'] : PHP_INT_MAX;
					$sanitized[ $key ] = self::sanitize_int( $value, $min, $max );
					break;
				case 'textarea':
					$sanitized[ $key ] = sanitize_textarea_field( $value );
					break;
				default:
					$sanitized[ $key ] = self::sanitize_text( $value );
					break;
			}
		}

		return $sanitized;
	}
}
