<?php
/**
 * Helpers
 */
namespace Brandy\Utils;

class Helpers {

	/**
	 * Update Object nested value with given path.
	 *
	 * @param array     $object Object to change value.
	 * @param array     $path   Given path.
	 * @param           $value  Value to set.
	 */
	public static function set_nested_value( &$object, $path, $value ) {
		$nested_data = &$object;
		$path_length = count( $path );
		while ( 1 < $path_length ) {
			$attribute_key = array_shift( $path );
			if ( ! isset( $nested_data[ $attribute_key ] ) ) {
				$nested_data[ $attribute_key ] = array();
			}
			$nested_data = &$nested_data[ $attribute_key ];
			$path_length = count( $path );
		}
		$attribute_key                 = array_shift( $path );
		$nested_data[ $attribute_key ] = $value;
	}

	/**
	 * Get Object nested value with given path.
	 *
	 * @param array     $object Object to change value.
	 * @param array     $path   Given path.
	 */
	public static function get_nested_value( &$object, $path, $default_value = null ) {
		$nested_data = &$object;
		$path_length = count( $path );
		while ( 1 < $path_length ) {
			$attribute_key = array_shift( $path );
			if ( ! isset( $nested_data[ $attribute_key ] ) ) {
				$nested_data[ $attribute_key ] = array();
			}
			$nested_data = &$nested_data[ $attribute_key ];
			$path_length = count( $path );
		}
		$attribute_key = array_shift( $path );
		return isset( $nested_data[ $attribute_key ] ) ? $nested_data[ $attribute_key ] : $default_value;
	}

	/**
	 * Make wp_parse_args function applying for nested
	 *
	 * @param array $args_1
	 * @param array $args_2
	 */
	public static function recursive_wp_parse_args( &$args_1, $args_2 ) {
		$args_1 = (array) $args_1;
		$args_2 = (array) $args_2;
		$result = $args_2;
		foreach ( $args_1 as $key => &$value ) {
			if ( isset( $result[$key] ) && is_array( $result[$key] ) && ( count( $result[$key] ) === 0 || is_numeric( array_keys( $result[$key] )[0] )) ) {
				$result[ $key ] = $args_1[ $key ];
				continue;	
			}
			if ( is_array( $value ) && isset( $result[ $key ] ) ) {
				$result[ $key ] = self::recursive_wp_parse_args( $value, $result[ $key ] );
			} else {
				$result[ $key ] = $value;
			}
		}
		return $result;
	}

	/**
	 * Get value for given device.
	 *
	 * If given data cannot be responsived, do not process. Returns itself.
	 *
	 * @param           $data       Given data. This can be anything.
	 * @param string    $device     Given device.
	 */
	public static function get_device_value( $data, $device = 'desktop' ) {
		if ( StylesDataHelpers::is_responsive_data( $data ) ) {

			if ( 'mobile' === $device ) {
				return $data['mobile'] ?? $data['tablet'] ?? $data['desktop'];
			}

			return $data[ $device ] ?? $data['desktop'];

		}
		return $data;
	}

	public static function colorToRGB( $color ) {
		if ( preg_match( '/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/', $color ) ) {
			// HEX color format
			return self::hexToRGB( $color );
		} elseif ( preg_match( '/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/', $color, $matches ) ) {
			// RGB color format
			return array(
				'r' => (int) $matches[1],
				'g' => (int) $matches[2],
				'b' => (int) $matches[3],
			);
		} elseif ( preg_match( '/^hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)$/', $color, $matches ) ) {
			// HSL color format
			return self::hslToRGB( (int) $matches[1], (int) $matches[2], (int) $matches[3] );
		} else {
			return null;
		}
	}

	// Convert HEX color to RGB
	public static function hexToRGB( $hex ) {
		$hex = ltrim( $hex, '#' );

		if ( strlen( $hex ) == 3 ) {
			// If shorthand HEX (#FFF), expand to #FFFFFF
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}

		return array(
			'r' => hexdec( substr( $hex, 0, 2 ) ),
			'g' => hexdec( substr( $hex, 2, 2 ) ),
			'b' => hexdec( substr( $hex, 4, 2 ) ),
		);
	}

	// Convert HSL to RGB
	public static function hslToRGB( $h, $s, $l ) {
		$s /= 100;
		$l /= 100;

		$c = ( 1 - abs( 2 * $l - 1 ) ) * $s;
		$x = $c * ( 1 - abs( fmod( $h / 60, 2 ) - 1 ) );
		$m = $l - $c / 2;

		if ( $h >= 0 && $h < 60 ) {
			$rgb = array( $c, $x, 0 );
		} elseif ( $h >= 60 && $h < 120 ) {
			$rgb = array( $x, $c, 0 );
		} elseif ( $h >= 120 && $h < 180 ) {
			$rgb = array( 0, $c, $x );
		} elseif ( $h >= 180 && $h < 240 ) {
			$rgb = array( 0, $x, $c );
		} elseif ( $h >= 240 && $h < 300 ) {
			$rgb = array( $x, 0, $c );
		} else {
			$rgb = array( $c, 0, $x );
		}

		return array(
			'r' => round( ( $rgb[0] + $m ) * 255 ),
			'g' => round( ( $rgb[1] + $m ) * 255 ),
			'b' => round( ( $rgb[2] + $m ) * 255 ),
		);
	}

	public static function getLuminance( $rgb ) {
		$r = $rgb['r'] / 255;
		$g = $rgb['g'] / 255;
		$b = $rgb['b'] / 255;

		// Apply gamma correction
		$r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( $r + 0.055 ) / 1.055, 2.4 );
		$g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( $g + 0.055 ) / 1.055, 2.4 );
		$b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( $b + 0.055 ) / 1.055, 2.4 );

		// Calculate luminance
		return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
	}

	// Adjust RGB color by a percentage (positive for lightening, negative for darkening)
	public static function generate_hover_color( $color, $percent = -0.15 ) {

		if ( false !== strpos( $color, 'var:preset|color|' ) ) {
			$preset_color_slug = str_replace( 'var:preset|color|', '', $color );
			$global_settings   = wp_get_global_settings();
			foreach ( ( $global_settings['color']['palette']['default'] ?? array() ) as $preset_data ) {
				if ( $preset_data['slug'] === $preset_color_slug ) {
					$color = $preset_data['color'];
				}
			}
			foreach ( ( $global_settings['color']['palette']['theme'] ?? array() ) as $preset_data ) {
				if ( $preset_data['slug'] === $preset_color_slug ) {
					$color = $preset_data['color'];
				}
			}
		}

		$rgb = self::colorToRGB( $color );

		if ( null === $rgb ) {
			return null;
		}

		if ( self::getLuminance( $rgb ) < 0.2 ) {
			// Lighten by a fixed percentage (20%)
			$rgb['r'] = min( 255, round( $rgb['r'] + ( $rgb['r'] * abs( $percent ) ) ) );
			$rgb['g'] = min( 255, round( $rgb['g'] + ( $rgb['g'] * abs( $percent ) ) ) );
			$rgb['b'] = min( 255, round( $rgb['b'] + ( $rgb['b'] * abs( $percent ) ) ) );
		} else {
			// Adjust RGB values based on the percentage
			$rgb['r'] = round( $rgb['r'] + ( $rgb['r'] * $percent ) );
			$rgb['g'] = round( $rgb['g'] + ( $rgb['g'] * $percent ) );
			$rgb['b'] = round( $rgb['b'] + ( $rgb['b'] * $percent ) );

			// Clamp values to be between 0 and 255
			$rgb['r'] = max( 0, min( 255, $rgb['r'] ) );
			$rgb['g'] = max( 0, min( 255, $rgb['g'] ) );
			$rgb['b'] = max( 0, min( 255, $rgb['b'] ) );
		}

		return 'rgb(' . implode( ',', array_values( $rgb ) ) . ')';
	}

	public static function generate_shadow_color( $color, $opacity = 0.1 ) {

		if ( false !== strpos( $color, 'var:preset|color|' ) ) {
			$preset_color_slug = str_replace( 'var:preset|color|', '', $color );
			$global_settings   = wp_get_global_settings();
			foreach ( ( $global_settings['color']['palette']['default'] ?? array() ) as $preset_data ) {
				if ( $preset_data['slug'] === $preset_color_slug ) {
					$color = $preset_data['color'];
				}
			}
			foreach ( ( $global_settings['color']['palette']['theme'] ?? array() ) as $preset_data ) {
				if ( $preset_data['slug'] === $preset_color_slug ) {
					$color = $preset_data['color'];
				}
			}
		}

		$rgb = self::colorToRGB( $color );

		if ( null === $rgb ) {
			return null;
		}

		return 'rgba(' . implode( ',', array_values( $rgb ) ) . ', ' . $opacity . ')';
	}

	// Convert RGB back to HEX
	public static function rgbToHex( $rgb ) {
		return '#' . str_pad( dechex( $rgb['r'] ), 2, '0', STR_PAD_LEFT ) .
				   str_pad( dechex( $rgb['g'] ), 2, '0', STR_PAD_LEFT ) .
				   str_pad( dechex( $rgb['b'] ), 2, '0', STR_PAD_LEFT );
	}

	// Convert RGB back to RGB string (for return to original format)
	public static function rgbToRGBString( $rgb ) {
		return "rgb({$rgb['r']}, {$rgb['g']}, {$rgb['b']})";
	}

	/**
	 * Get language url.
	 *
	 * @param string $lang Language slug.
	 * @since 1.2.9
	 */
	public static function get_language_url( $lang ) {
		if ( function_exists( 'PLL' ) && ! empty( \PLL() ) && ! empty( \PLL()->links ) && ! empty( \PLL()->model ) ) {
			return \PLL()->links->get_translation_url( \PLL()->model->get_language( $lang ) );
		}
		if ( function_exists( 'weglot_get_service' ) ) {
			$language_service     = \weglot_get_service( 'Language_Service_Weglot' );
			$request_url_services = \weglot_get_service( 'Request_Url_Service_Weglot' );
			if ( $language_service && $request_url_services ) {
				$language = $language_service->get_all_languages()[ $lang ] ?? null;
				if ( $language ) {
					return $request_url_services->get_weglot_url()->getForLanguage( $language, false );
				}
			}
		}

		global $sitepress;
		if ( ! empty( $sitepress ) ) {
			return $sitepress->language_url( $lang );
		}
		return '';
	}

	public static function get_current_language( $fallback = 'en' ) {
		if ( function_exists( 'pll_current_language' ) ) {
			return (string) pll_current_language();
		}
		if ( function_exists( 'weglot_get_current_language' ) ) {
			return \weglot_get_current_language();
		}

		global $sitepress;
		if ( ! empty( $sitepress ) ) {
			return $sitepress->get_current_language();
		}

		return $fallback;
	}

	/**
	 * Convert hex color to RGB array
	 *
	 * @param string $hex Hex color code
	 * @return array|null Array with r, g, b, and optional a keys, or null on failure
	 * 
	 * @since 1.3.9
	 */
	public static function hex_to_rgb( $hex ) {
		// Remove # if present
		$hex = ltrim( $hex, '#' );

		// Support both 6-digit (RRGGBB) and 8-digit (RRGGBBAA) hex colors
		if ( strlen( $hex ) === 8 ) {
			$r = hexdec( substr( $hex, 0, 2 ) );
			$g = hexdec( substr( $hex, 2, 2 ) );
			$b = hexdec( substr( $hex, 4, 2 ) );
			$a = hexdec( substr( $hex, 6, 2 ) ) / 255; // Convert to 0-1 range
			return array(
				'r' => $r,
				'g' => $g,
				'b' => $b,
				'a' => $a,
			);
		} elseif ( strlen( $hex ) === 6 ) {
			$r = hexdec( substr( $hex, 0, 2 ) );
			$g = hexdec( substr( $hex, 2, 2 ) );
			$b = hexdec( substr( $hex, 4, 2 ) );
			return array(
				'r' => $r,
				'g' => $g,
				'b' => $b,
			);
		}

		return null;
	}

	/**
	 * Get the relative luminance of a color
	 *
	 * @param string $hex Hex color code
	 * @return float Luminance value (0-1)
	 * 
	 * @since 1.3.9
	 */
	public static function get_luminance( $hex ) {
		$rgb = self::hex_to_rgb( $hex );
		if ( ! $rgb ) {
			return 0;
		}

		// Convert to 0-1 range and apply gamma correction
		$r = $rgb['r'] / 255;
		$g = $rgb['g'] / 255;
		$b = $rgb['b'] / 255;

		$r = ( $r <= 0.03928 ) ? $r / 12.92 : pow( ( $r + 0.055 ) / 1.055, 2.4 );
		$g = ( $g <= 0.03928 ) ? $g / 12.92 : pow( ( $g + 0.055 ) / 1.055, 2.4 );
		$b = ( $b <= 0.03928 ) ? $b / 12.92 : pow( ( $b + 0.055 ) / 1.055, 2.4 );

		// Calculate relative luminance using standard coefficients
		return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
	}

	/**
	 * Lighten a hex color by a percentage
	 *
	 * @param string $hex    Hex color code
	 * @param float  $amount Amount to lighten (0-1), default 0.15
	 * @return string Lightened hex color
	 * 
	 * @since 1.3.9
	 */
	public static function lighten_color( $hex, $amount = 0.15 ) {
		$rgb = self::hex_to_rgb( $hex );
		if ( ! $rgb ) {
			return $hex;
		}

		$r = min( 255, floor( $rgb['r'] + ( 255 - $rgb['r'] ) * $amount ) );
		$g = min( 255, floor( $rgb['g'] + ( 255 - $rgb['g'] ) * $amount ) );
		$b = min( 255, floor( $rgb['b'] + ( 255 - $rgb['b'] ) * $amount ) );

		// Preserve alpha channel if present
		$color_parts = array(
			str_pad( dechex( $r ), 2, '0', STR_PAD_LEFT ),
			str_pad( dechex( $g ), 2, '0', STR_PAD_LEFT ),
			str_pad( dechex( $b ), 2, '0', STR_PAD_LEFT ),
		);

		if ( isset( $rgb['a'] ) ) {
			$alpha         = round( $rgb['a'] * 255 );
			$color_parts[] = str_pad( dechex( $alpha ), 2, '0', STR_PAD_LEFT );
		}

		return '#' . implode( '', $color_parts );
	}

	/**
	 * Darken a hex color by a percentage
	 *
	 * @param string $hex    Hex color code
	 * @param float  $amount Amount to darken (0-1), default 0.15
	 * @return string Darkened hex color
	 * 
	 * @since 1.3.9
	 */
	public static function darken_color( $hex, $amount = 0.15 ) {
		$rgb = self::hex_to_rgb( $hex );
		if ( ! $rgb ) {
			return $hex;
		}

		$r = max( 0, floor( $rgb['r'] * ( 1 - $amount ) ) );
		$g = max( 0, floor( $rgb['g'] * ( 1 - $amount ) ) );
		$b = max( 0, floor( $rgb['b'] * ( 1 - $amount ) ) );

		// Preserve alpha channel if present
		$color_parts = array(
			str_pad( dechex( $r ), 2, '0', STR_PAD_LEFT ),
			str_pad( dechex( $g ), 2, '0', STR_PAD_LEFT ),
			str_pad( dechex( $b ), 2, '0', STR_PAD_LEFT ),
		);

		if ( isset( $rgb['a'] ) ) {
			$alpha         = round( $rgb['a'] * 255 );
			$color_parts[] = str_pad( dechex( $alpha ), 2, '0', STR_PAD_LEFT );
		}

		return '#' . implode( '', $color_parts );
	}

	/**
	 * Convert hex color to OKLCH color space
	 * OKLCH provides perceptually uniform lightness for better dark mode conversion
	 *
	 * @param string $hex Hex color code
	 * @return array|null Array with 'l' (lightness 0-1), 'c' (chroma 0-0.4), 'h' (hue 0-360)
	 *
	 * @since 1.5.0
	 */
	public static function hex_to_oklch( $hex ) {
		$rgb = self::hex_to_rgb( $hex );
		if ( ! $rgb ) {
			return null;
		}

		// Convert RGB to linear sRGB
		$linear = self::srgb_to_linear( $rgb['r'] / 255, $rgb['g'] / 255, $rgb['b'] / 255 );

		// Convert linear sRGB to OKLab
		$oklab = self::linear_srgb_to_oklab( $linear['r'], $linear['g'], $linear['b'] );

		// Convert OKLab to OKLCH
		$c = sqrt( $oklab['a'] * $oklab['a'] + $oklab['b'] * $oklab['b'] );
		$h = 0;
		if ( $c > 0.00001 ) {
			$h = atan2( $oklab['b'], $oklab['a'] ) * 180 / M_PI;
			if ( $h < 0 ) {
				$h += 360;
			}
		}

		return array(
			'l' => $oklab['l'],
			'c' => $c,
			'h' => $h,
		);
	}

	/**
	 * Convert OKLCH color to hex
	 *
	 * @param float $l Lightness (0-1)
	 * @param float $c Chroma (0-0.4 typical)
	 * @param float $h Hue (0-360)
	 * @return string Hex color code
	 *
	 * @since 1.5.0
	 */
	public static function oklch_to_hex( $l, $c, $h ) {
		// Convert OKLCH to OKLab
		$h_rad = $h * M_PI / 180;
		$a     = $c * cos( $h_rad );
		$b     = $c * sin( $h_rad );

		// Convert OKLab to linear sRGB
		$linear = self::oklab_to_linear_srgb( $l, $a, $b );

		// Gamut mapping: clamp to sRGB gamut
		$linear = self::gamut_map_oklch( $linear, $l, $c, $h );

		// Convert linear sRGB to sRGB
		$srgb = self::linear_to_srgb( $linear['r'], $linear['g'], $linear['b'] );

		// Clamp and convert to hex
		$r = max( 0, min( 255, round( $srgb['r'] * 255 ) ) );
		$g = max( 0, min( 255, round( $srgb['g'] * 255 ) ) );
		$b = max( 0, min( 255, round( $srgb['b'] * 255 ) ) );

		return '#' . str_pad( dechex( $r ), 2, '0', STR_PAD_LEFT )
			. str_pad( dechex( $g ), 2, '0', STR_PAD_LEFT )
			. str_pad( dechex( $b ), 2, '0', STR_PAD_LEFT );
	}

	/**
	 * Convert sRGB to linear RGB
	 *
	 * @since 1.5.0
	 */
	private static function srgb_to_linear( $r, $g, $b ) {
		$linearize = function( $v ) {
			return $v <= 0.04045 ? $v / 12.92 : pow( ( $v + 0.055 ) / 1.055, 2.4 );
		};

		return array(
			'r' => $linearize( $r ),
			'g' => $linearize( $g ),
			'b' => $linearize( $b ),
		);
	}

	/**
	 * Convert linear RGB to sRGB
	 *
	 * @since 1.5.0
	 */
	private static function linear_to_srgb( $r, $g, $b ) {
		$gamma = function( $v ) {
			return $v <= 0.0031308 ? 12.92 * $v : 1.055 * pow( $v, 1 / 2.4 ) - 0.055;
		};

		return array(
			'r' => $gamma( $r ),
			'g' => $gamma( $g ),
			'b' => $gamma( $b ),
		);
	}

	/**
	 * Convert linear sRGB to OKLab
	 * Based on Björn Ottosson's OKLab color space
	 *
	 * @since 1.5.0
	 */
	private static function linear_srgb_to_oklab( $r, $g, $b ) {
		$l = 0.4122214708 * $r + 0.5363325363 * $g + 0.0514459929 * $b;
		$m = 0.2119034982 * $r + 0.6806995451 * $g + 0.1073969566 * $b;
		$s = 0.0883024619 * $r + 0.2817188376 * $g + 0.6299787005 * $b;

		$l_ = self::cbrt( $l );
		$m_ = self::cbrt( $m );
		$s_ = self::cbrt( $s );

		return array(
			'l' => 0.2104542553 * $l_ + 0.7936177850 * $m_ - 0.0040720468 * $s_,
			'a' => 1.9779984951 * $l_ - 2.4285922050 * $m_ + 0.4505937099 * $s_,
			'b' => 0.0259040371 * $l_ + 0.7827717662 * $m_ - 0.8086757660 * $s_,
		);
	}

	/**
	 * Convert OKLab to linear sRGB
	 *
	 * @since 1.5.0
	 */
	private static function oklab_to_linear_srgb( $l, $a, $b ) {
		$l_ = $l + 0.3963377774 * $a + 0.2158037573 * $b;
		$m_ = $l - 0.1055613458 * $a - 0.0638541728 * $b;
		$s_ = $l - 0.0894841775 * $a - 1.2914855480 * $b;

		$l = $l_ * $l_ * $l_;
		$m = $m_ * $m_ * $m_;
		$s = $s_ * $s_ * $s_;

		return array(
			'r' => +4.0767416621 * $l - 3.3077115913 * $m + 0.2309699292 * $s,
			'g' => -1.2684380046 * $l + 2.6097574011 * $m - 0.3413193965 * $s,
			'b' => -0.0041960863 * $l - 0.7034186147 * $m + 1.7076147010 * $s,
		);
	}

	/**
	 * Cube root function that handles negative numbers
	 *
	 * @since 1.5.0
	 */
	private static function cbrt( $x ) {
		return $x < 0 ? -pow( -$x, 1 / 3 ) : pow( $x, 1 / 3 );
	}

	/**
	 * Gamut map OKLCH color to fit within sRGB
	 * Uses chroma reduction to bring out-of-gamut colors back in
	 *
	 * @since 1.5.0
	 */
	private static function gamut_map_oklch( $linear, $l, $c, $h ) {
		// Check if already in gamut
		if ( self::is_in_srgb_gamut( $linear ) ) {
			return $linear;
		}

		// Binary search to find maximum chroma that fits in gamut
		$lo = 0;
		$hi = $c;

		for ( $i = 0; $i < 20; $i++ ) {
			$mid   = ( $lo + $hi ) / 2;
			$h_rad = $h * M_PI / 180;
			$a     = $mid * cos( $h_rad );
			$b     = $mid * sin( $h_rad );

			$test = self::oklab_to_linear_srgb( $l, $a, $b );

			if ( self::is_in_srgb_gamut( $test ) ) {
				$lo     = $mid;
				$linear = $test;
			} else {
				$hi = $mid;
			}
		}

		return $linear;
	}

	/**
	 * Check if linear RGB values are within sRGB gamut
	 *
	 * @since 1.5.0
	 */
	private static function is_in_srgb_gamut( $linear ) {
		$epsilon = 0.0001;
		return $linear['r'] >= -$epsilon && $linear['r'] <= 1 + $epsilon
			&& $linear['g'] >= -$epsilon && $linear['g'] <= 1 + $epsilon
			&& $linear['b'] >= -$epsilon && $linear['b'] <= 1 + $epsilon;
	}

}
