/**
 * Function to convert a Hexadecimal color to an RGB color.
 *
 * @since x.x.x
 * @param {string} hex The hexadecimal color string.
 * @return {Array} An array with the formatted red, green, and blue colors.
 */
const hexToRgb = ( hex ) => {
	// First strip the pound (or hashtag) from the color.
	const cleanHex = hex.replace( '#', '' );

	// Then return an array of the Red, Green and Blue values parsed from the hexadecimal values (base 16).
  	return [
		parseInt( cleanHex.substring( 0, 2 ), 16 ),
		parseInt( cleanHex.substring( 2, 4 ), 16 ),
		parseInt( cleanHex.substring( 4, 6 ), 16 ),
	];
}

/**
 * Function to convert an RGB color to an HSL color.
 *
 * @since x.x.x
 * @param {Array}  rgb     - The RGB color array.
 * @param {number} rgb."0" - The Red color.
 * @param {number} rgb."1" - The Greem color.
 * @param {number} rgb."2" - The Blue color.
 * @return {Array} An array with the formatted hue, saturation and lightness.
 */
const rgbToHsl = ( [ r, g, b ] ) => {
	// First normalize the red, green, and blue values between 0 and 1 from 255 (the max in RGB).
	r /= 255;
	g /= 255;
	b /= 255;
	
	// Then get the minimum and maximum channel values.
	const max = Math.max( r, g, b );
	const min = Math.min( r, g, b );

	// Set default variables for hue, saturation, and lightness (which will be have of the min and max combined).
	let h = 0;
	let s = 0;
	const l = ( max + min ) / 2;

	// If the colors aren't identical, we need to calculate the hue and saturation.
	if ( max !== min ) {
		// Get the delta of max from min to use in HSL calculations.
		const delta = max - min;

		// Update the saturation.
		s = ( l > 0.5 )
			? delta / ( 2 - max - min ) // If lightness is above half, divide the delta by 2 minus the min and max.
			: delta / ( max + min ); // If lightness is half or below, divide the delta by the sum of min and max.

		// Update the hue.
		switch ( max ) {
			case r:
				// If red is the max, the hue should be green minus blue by the delta plus 6 or 0, depending on if green is less than blue.
				h = ( g - b ) / delta + ( g < b ? 6 : 0 );
				break;
			case g:
				// If green is the max, the hue should be blue minus red by the delta plus 2.
				h = ( b - r ) / delta + 2;
				break;
			case b:
				// If blue is the max, the hue should be red minus green by the delta plus 4.
				h = ( r - g ) / delta + 4;
				break;
		}

		// Finally multiply the hue by 60 to get the value between 0° and 360°.
		h *= 60;
	}

	// Return an array with the rounded hue, the rounded saturation between 0% and 100%, and the rounded lightness between 0% and 100%.
	return [
		Math.round( h ),
		Math.round( s * 100 ),
		Math.round( l * 100 ),
	];
}

/**
 * Function to convert an HSL color to an RGB color.
 *
 * @since x.x.x
 * @param {Array}  hsl     - The HSL color array.
 * @param {number} hsl."0" - The Hue degree.
 * @param {number} hsl."1" - The Saturation percentage.
 * @param {number} hsl."2" - The Lightness percentage.
 * @return {Array} An array with the formatted red, green and blue colors.
 */
const hslToRgb = ( [ h, s, l ] ) => {
	s /= 100;
	l /= 100;
	
	const c = ( 1 - Math.abs( 2 * l - 1 ) ) * s
	const x = c * ( 1 - Math.abs( ( h / 60 ) % 2 - 1 ) )
	const m = l - c/2
	let r = 0, g = 0, b = 0

	if ( 0 <= h && h < 60 ) { r = c; g = x; b = 0 }
	else if ( 60 <= h && h < 120 ) { r = x; g = c; b = 0 }
	else if ( 120 <= h && h < 180 ) { r = 0; g = c; b = x }
	else if ( 180 <= h && h < 240 ) { r = 0; g = x; b = c }
	else if ( 240 <= h && h < 300 ) { r = x; g = 0; b = c }
	else if ( 300 <= h && h < 360 ) { r = c; g = 0; b = x }

	return [
		Math.round( ( r + m ) * 255 ),
		Math.round( ( g + m ) * 255 ),
		Math.round( ( b + m ) * 255 )
	]
}

const rgbToHex = ( [r, g, b] ) => {
  const toHex = ( n ) => 
	n.toString( 16 ).padStart( 2, '0' )
  
  return `#${toHex( r )}${toHex( g )}${toHex( b )}`
}

/**
 * Determines whether a hex color is light or dark and returns the appropriate text color.
 *
 * @param {string} hex     - Hex color code (e.g., #f2e8cf).
 * @param {string} isDark  - Color value to use if the contrast is a dark color.
 * @param {string} isLight - Color value to use if the contrast is a light color.
 * @return {string}       - The light or dark color that contrasts the current hex.
 */
export const getContrastEndColor = ( hex, isDark = '#fff', isLight = '#000' ) => {
	if ( ! hex ) {
		return '';
	}
    // Convert hex to RGB array.
    const rgb = hexToRgb( hex );
    
    // Calculate relative luminance using W3C formula.
    const r = rgb[ 0 ] / 255;
    const g = rgb[ 1 ] / 255;
    const b = rgb[ 2 ] / 255;
    
    // Apply gamma correction.
    const r2 = r <= 0.03928 ? r / 12.92 : Math.pow( ( r + 0.055 ) / 1.055, 2.4 );
    const g2 = g <= 0.03928 ? g / 12.92 : Math.pow( ( g + 0.055 ) / 1.055, 2.4 );
    const b2 = b <= 0.03928 ? b / 12.92 : Math.pow( ( b + 0.055 ) / 1.055, 2.4 );
    
    // Calculate luminance.
    const luminance = 0.2126 * r2 + 0.7152 * g2 + 0.0722 * b2;
    
    // Return #fff for dark colors (luminance < 0.5) and #000 for light colors.
    return luminance < 0.5 ? isDark : isLight;
}

export const convertColorToShades = ( inputColor ) => {
	if ( ! inputColor ) {
		return '';
	}

	const processColor = ( color ) => {
		try {
			const rgb = hexToRgb( color );
			const hsl = rgbToHsl( rgb );
		
			return {
				hex: color.toLowerCase(),
				rgb: `rgb(${rgb.join( ',' )})`,
				hsl: `hsl(${hsl.join( ',' )})`,
			};
		} catch {
			return [];
		}
	}

	const processed = processColor( inputColor )
	if ( ! processed ) return [];

	const [h, s, l] = rgbToHsl( hexToRgb( inputColor ) )

	const labels = {
		light: [
			'light',
			'lighter',
			'lightest',
			'near-white',
		],
		dark: [
			'dark',
			'darker',
			'darkest',
			'near-black',
		],
		complement: 'complement',
		inverted: 'inverted',
		main: 'main',
	}
	
	const lighterShades = Array.from( { length: 4 }, ( _, i ) => ( {
		label: labels.light[ i ],
		color: rgbToHex(
			hslToRgb(
				[ h, s, Math.min(
					100,
					l + ( ( 100 - l ) * ( 3 === i ? 0.95 : ( ( i + 1 ) / 4 ) ) ) // Lightness should be 25%, 50%, 75%, or 95% specifically.
				) ]
			)
		),
	} ) );
	
	const darkerShades = Array.from( { length: 4 }, ( _, i ) => ( {
		label: labels.dark[ i ],
		color: rgbToHex(
			hslToRgb(
				[ h, s, Math.max(
					0,
					l - ( ( l ) * ( 3 === i ? 0.95 : ( ( i + 1 ) / 4 ) ) ) // Darkness should be 25%, 50%, 75%, or 95% specifically.
				) ]
			)
		),
	} ) )
	
	const complementary = rgbToHex( hslToRgb( [( h + 180 ) % 360, s, l] ) )

	const rgb = hexToRgb( inputColor );
	const invertedRgb = [
		255 - rgb[0],
		255 - rgb[1],
		255 - rgb[2]
	];
	const inverted = rgbToHex( invertedRgb );

	const generatedShades = [
		{ label: labels.main, color: inputColor },
		...darkerShades,
		...lighterShades,
		{ label: labels.complement, color: complementary },
		{ label: labels.inverted, color: inverted },
	];

	return generatedShades;
}; 
