const procUnit = (u, fn) => {
	const unitLess = parseFloat(u.match(/\d*\.?\d*/)[0]);
	const unit = u.split(unitLess.toString())[1];
	const unitLessProccesed = fn(unitLess);
	return `${unitLessProccesed}${unit}`;
};

const halfUnit = (u) => {
	return procUnit(u, (n) => n / 2);
};

/**
 * hexToRGBA
 * Convierte un color en formato Hexadecimal a formato RGBA
 * @param {string} hex El color en formato hexadecimal
 * @param {number} opacity La valor de opacidad del color, de 0 a 1
 * @example
 * Convierte el color #FFFFFF a RGBA()
 * hexToRGBA("#FFFFFF", 0.5)
 * -> "rgba(255, 255, 255, 0.5)"
 *
 * @return {string} Una cadena con el valor rgba dentro de rgba()
 */
const hexToRGBA = (hex, opacity = 1.0) => {
	const clearHex = hex[0] === "#" ? hex.substring(1, 7) : hex;
	const hexToR = parseInt(clearHex.substring(0, 2), 16);
	const hexToG = parseInt(clearHex.substring(2, 4), 16);
	const hexToB = parseInt(clearHex.substring(4, 6), 16);
	return `rgba(${hexToR}, ${hexToG}, ${hexToB}, ${opacity})`;
};

/**
 * isDifferent
 * Comprueba si value1 y value2 son iguales, en caso negativo devuelve una cadena
 * con el valor `value2` asociado a la `prop` (css).
 * Si son iguales devuelve una cadena vacía.
 *
 * Esta función se usa para construir una especia de cascada (a lo css) al
 * construir los estilos de textos responsive. Así si hay una nueva proiedad
 * con un valor diferente
 *
 * @param {string} prop La propiedad css que compara
 * @param {string} value1 El primera valor de la prop
 * @param {string} value2 El segundo valor de la prop
 * @example
 * Compara el font-size
 * isDifferent("font-size", "24px", "24px")
 * -> ""
 *
 * @example
 * Compara el font-size
 * isDifferent("font-size", "10px", "32px")
 * -> "font-size: 32px"
 *
 * @return {string} Una cadena con la propiedad y el valor nuevo, o una cadena
 * vacía, si los valores son iguales.
 */
const isDifferent = (prop, value1, value2) => {
	if (value2) {
		if (value1 === value2) {
			return "";
		} else {
			return `${prop}: ${value2};`;
		}
	} else {
		return "";
	}
};

/**
 * has
 * Determina si existe en el archivo .json una propiedad.
 * Si existe devuelve el par "prop: value", si no busca la proiedad en el
 * apartado defaultStyles y la devuelve. Si tampoco existe ahí, devuelve ""
 *
 * @param {string} prop La propiedad css
 * @param {string} value El valor de la propiedad css
 * @param {string} fallBackValue El valor de la propiedad css en caso de no
 * existir la propiedad value
 */
const has = (prop, value, fallBackValue) => {
	if (value) {
		return `${prop}: ${value};`;
	}
	if (fallBackValue) {
		return `${prop}: ${fallBackValue};`;
	}
	return "";
};

/**
 * parseColors
 * Parsea un objeto de colors con la siguiente estructura
 * {
 *   color: {
 *     colors: {
 *       colorName1: {
 *         value: "#ffffff",
 *         opacity: "1"
 *       },
 *       colorName2: {
 *         value: "#ffffff",
 *         opacity: "1"
 *       },
 *     }
 *   }
 * }
 * @param {object} theme El tema
 */

const parseColors = (theme) => {
	try {
		const colors = theme.color.colors;
		const color = Object.keys(colors).reduce((acc, label) => {
			const isOpaque = (entry) => entry.opacity === 1 || entry.opacity === undefined || entry.opacity === null;
			const _isHEX = (entry) => {
				try {
					return entry.value[0] === "#";
				} catch (e) {
					return false;
				}
			};
			const _isHSL = (entry) => {
				try {
					return entry.type.toLowerCase() === "hsl";
				} catch (e) {
					return false;
				}
			};
			const _isRGB = (entry) => {
				try {
					return entry.type.toLowerCase() === "rgb";
				} catch (e) {
					return false;
				}
			};
			const isHSL = (entry) => _isHSL(entry) && isOpaque(entry);
			const isHSLA = (entry) => _isHSL(entry) && !isOpaque(entry);
			const isHEX = (entry) => _isHEX(entry) && isOpaque(entry);
			const isHEXA = (entry) => _isHEX(entry) && !isOpaque(entry);
			const isRGB = (entry) => _isRGB(entry) && isOpaque(entry);
			const isRGBA = (entry) => _isRGB(entry) && !isOpaque(entry);

			if (isHSLA(colors[label])) {
				acc[label] = `hsla(${colors[label].value
					.map((v, idx) => `${idx === 0 ? v : typeof v === "string" ? (v.indexOf("%") === -1 ? v + "%" : v) : v + "%"}`)
					.join(", ")}, ${colors[label].opacity})`;
			}
			if (isHSL(colors[label])) {
				acc[label] = `hsl(${colors[label].value
					.map((v, idx) => `${idx === 0 ? v : typeof v === "string" ? (v.indexOf("%") === -1 ? v + "%" : v) : v + "%"}`)
					.join(", ")})`;
			}
			if (isHEX(colors[label])) {
				acc[label] = `${colors[label].value}`;
			}
			if (isHEXA(colors[label])) {
				acc[label] = `${hexToRGBA(colors[label].value, colors[label].opacity)}`;
			}
			if (isRGB(colors[label])) {
				acc[label] = `rgb(${colors[label].value.join(", ")})`;
			}
			if (isRGBA(colors[label])) {
				acc[label] = `rgba(${colors[label].value.join(", ")}, ${colors[label].opacity})`;
			}
			return acc;
		}, {});

		// Autogenerador
		// color.interactiveMain = color.interactiveMain ? "#fff" : "#000"

		return color;
	} catch (e) {
		return null;
	}
};

const parseGradients = (theme) => {
	try {
		const gradients = theme.gradient.gradients;
		const gradient = Object.keys(gradients).reduce((acc, label) => {
			acc[label] = `linear-gradient(${gradients[label].direction}, ${gradients[label].stops
				.map((stop) => `${hexToRGBA(stop.color, stop.opacity)} ${stop.position}`)
				.join(",")})`;
			return acc;
		}, {});
		return gradient;
	} catch (e) {
		return null;
	}
};

const parseShadows = (theme) => {
	try {
		const shadows = theme.shadow.shadows;
		const shadow = Object.keys(shadows).reduce((acc, label) => {
			acc[label] = `${shadows[label].x}px ${shadows[label].y}px ${shadows[label].blur}px ${hexToRGBA(
				shadows[label].color,
				shadows[label].opacity,
			)}`;
			return acc;
		}, {});
		return shadow;
	} catch (e) {
		return null;
	}
};

const parseMediaqueries = (theme) => {
	try {
		const mediaqueries = theme.mediaquery.mediaqueries;
		let mqObject = {};
		mediaqueries.forEach((mq) => {
			if (mq.label !== "default") {
				const minWidth = mq.minWidth ? `and (min-width: ${mq.minWidth})` : "";
				const maxWidth = mq.maxWidth ? `and (max-width: ${mq.maxWidth})` : "";
				mqObject[mq.label] = `@media only screen ${minWidth} ${maxWidth}`;
			}
		});

		return mqObject;
	} catch (e) {
		return null;
	}
};

const parseFontFamilies = (theme) => {
	try {
		return theme.fontFamily.fontFamilies;
	} catch (e) {
		return null;
	}
};

const getUnitValue = (x) => {
	if (typeof x === "string") {
		return x.match(/\d*\.?\d+?/)[0];
	}
	if (typeof x === "number") {
		return x;
	}
};

const cropText = (top, bottom) => {
	return `
	&::before, &::after {
	  content: "";
	  display: block;
	}
	&::before {
	  margin-bottom: ${bottom}em;
	}
	&::after {
	  margin-top: ${top}em;
	}
	`;
};

export const parseTextStyles = (theme) => {
	try {
		// Space object for rebass grid.
		const mq = parseMediaqueries(theme);
		const fontFamily = parseFontFamilies(theme);

		// With process
		const defaultStyles = theme.textStyle.defaultStyles;
		const textStyles = theme.textStyle.textStyles;

		// fontRes
		let textStyleObject = {};
		textStyles.forEach((style) => {
			let FONTSIZE = getUnitValue(style.fontSize);
			let CROPTOP = style.croptop !== undefined ? style.croptop : 0;
			let CROPBOTTOM = style.cropbottom !== undefined ? style.cropbottom : 0;
			let dynCropTop = (Math.max(CROPTOP + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
			let dynCropBottom = (Math.max(CROPBOTTOM + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
			textStyleObject[style.name] = `
      
	  ${has("font-family", fontFamily[style.fontFamily], fontFamily[defaultStyles.fontFamily])}
	  ${has("font-size", style.fontSize, defaultStyles.fontSize)}
	  ${has("font-weight", style.fontWeight, defaultStyles.fontWeight)}
	  ${has("letter-spacing", style.letterSpacing, defaultStyles.letterSpacing)}
	  ${has("line-height", style.lineHeight, defaultStyles.lineHeight)}
	  ${has("font-style", style.fontStyle, defaultStyles.fontStyle)}
	  ${has("text-transform", style.textTransform, defaultStyles.textTransform)}
	  ${CROPTOP && CROPBOTTOM ? cropText(dynCropTop, dynCropBottom) : ""}
	  
	  ${
			style.responsive
				? style.responsive
						.map((bp, idx) => {
							FONTSIZE = getUnitValue(bp.fontSize);
							CROPTOP = bp.croptop !== undefined ? bp.croptop : 0;
							CROPBOTTOM = bp.cropbottom !== undefined ? bp.cropbottom : 0;
							dynCropTop = (Math.max(CROPTOP + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
							dynCropBottom = (Math.max(CROPBOTTOM + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
							if (idx < 1) {
								// En el primer ciclo se compara con el breakpoint base: style
								return `
		${mq[bp.breakpoint]} {
		  ${isDifferent("font-family", fontFamily[style.fontFamily], fontFamily[bp.fontFamily])}
		  ${isDifferent("font-size", style.fontSize, bp.fontSize)}
		  ${isDifferent("font-weight", style.fontWeight, bp.fontWeight)}
		  ${isDifferent("line-height", style.lineHeight, bp.lineHeight)}
		  ${isDifferent("letter-spacing", style.letterSpacing, bp.letterSpacing)}
		  ${isDifferent("font-style", style.fontStyle, bp.fontStyle)}
		  ${isDifferent("text-transform", style.textTransform, bp.textTransform)}
		  ${CROPTOP && CROPBOTTOM ? cropText(dynCropTop, dynCropBottom) : ""}
		}
	      `;
							} else {
								// A partir del segundo ciclo se compara con el breakpoint anterior
								FONTSIZE = getUnitValue(bp.fontSize);
								CROPTOP = bp.croptop !== undefined ? bp.croptop : 0;
								CROPBOTTOM = bp.cropbottom !== undefined ? bp.cropbottom : 0;
								dynCropTop = (Math.max(CROPTOP + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
								dynCropBottom = (Math.max(CROPBOTTOM + (1 - 1) * (FONTSIZE / 2), 0) / FONTSIZE) * -1;
								return `
		${mq[bp.breakpoint]} {
		  ${isDifferent("font-family", fontFamily[style.responsive[idx - 1].fontFamily], fontFamily[bp.fontFamily])}
		  ${isDifferent("font-size", style.responsive[idx - 1].fontSize, bp.fontSize)}
		  ${isDifferent("font-weight", style.responsive[idx - 1].fontWeight, bp.fontWeight)}
		  ${isDifferent("line-height", style.responsive[idx - 1].lineHeight, bp.lineHeight)}
		  ${isDifferent("letter-spacing", style.responsive[idx - 1].letterSpacing, bp.letterSpacing)}
		  ${isDifferent("font-style", style.responsive[idx - 1].fontStyle, bp.fontStyle)}
		  ${isDifferent("text-transform", style.responsive[idx - 1].textTransform, bp.textTransform)}
		  ${CROPTOP && CROPBOTTOM ? cropText(dynCropTop, dynCropBottom) : ""}
		}
	      `;
							}
						})
						.join("")
				: ""
		}
	  `;
			// End
		});

		return textStyleObject;
	} catch (e) {
		return null;
	}
};

const parseBreakpoints = (theme) => {
	try {
		// return Object.fromEntries(Object.entries(theme.mediaquery.mediaqueries.map(bp => bp.minWidth).filter(Boolean)))
		return theme.mediaquery.mediaqueries.map((bp) => bp.minWidth).filter(Boolean);
	} catch (e) {
		return null;
	}
};

const parseFontSizes = (theme) => {
	try {
		return [
			...new Set(
				theme.textStyle.textStyles
					.map((entry) => entry.fontSize)
					.filter(Boolean)
					.reverse()
					.map((entry) => Number(entry.split("px")[0]))
					.sort((a, b) => a - b),
			),
		];
	} catch (e) {
		return null;
	}
};

const parseNegativeSpacing = (theme) => {
	const getNegative = (v) => `-${v}`;
	if (theme.spacing) {
		if (Object.keys(theme.spacing).length) {
			return Object.entries(theme.spacing).reduce(
				(prev, [key, value]) => ({
					...prev,
					[key]: typeof value === "string" ? getNegative(value) : undefined,
				}),
				{},
			);
		}
	}
};

const parseHalfSpacing = (theme) => {
	if (theme.spacing) {
		if (Object.keys(theme.spacing).length) {
			return Object.entries(theme.spacing).reduce(
				(prev, [key, value]) => ({
					...prev,
					[key]: typeof value === "string" ? halfUnit(value) : undefined,
				}),
				{},
			);
		}
	}
};

const parseNegativeHalfSpacing = (theme) => {
	const getNegative = (v) => `-${v}`;
	if (theme.spacing) {
		if (Object.keys(theme.spacing).length) {
			return Object.entries(theme.spacing).reduce(
				(prev, [key, value]) => ({
					...prev,
					[key]: typeof value === "string" ? getNegative(halfUnit(value)) : undefined,
				}),
				{},
			);
		}
	}
};

const parseTheme = (theme) => {
	// Añadir también al parseo de temas todos los elementos que no necesiten ser
	// paseados.

	const color = parseColors(theme);
	const gradient = parseGradients(theme);
	const shadow = parseShadows(theme);
	const mq = parseMediaqueries(theme);
	const textStyle = parseTextStyles(theme);
	const breakpoints = parseBreakpoints(theme);
	const fontFamily = parseFontFamilies(theme);

	// Breakpoints alias.
	// De esta manera se pueden utilizar las props responsive así:
	// <Text display={{small:"none", xlarge:"block"}} >...</Text>
	try {
		theme.mediaquery.mediaqueries.forEach((mq) => {
			breakpoints[mq.label] = mq.minWidth;
		});
	} catch (e) {
		//
	}

	const returnObject = {
		...theme,
		fontFamily,
		color,
		colors: color,
		gradient,
		shadow,
		mq,
		textStyle,
		breakpoints,
		fontSizes: parseFontSizes(theme),
		negateSpacing: parseNegativeSpacing(theme),
		halfSpacing: parseHalfSpacing(theme),
		negateHalfSpacing: parseNegativeHalfSpacing(theme),
	};
	return returnObject;
};

export { parseTheme };
