@import "./utils.less";
@import "./variables.less";

// Disabled elements
.disabled(@rules; @target) when (isruleset(@rules)) and (@target = parent) {
	[disabled] {
		@rules();
	}
}
.disabled(@rules) when (isruleset(@rules)) {
	&[disabled] {
		@rules();
	}
}

.enact-composite() {
	transform: translateZ(0);
	will-change: transform;
}

// Applies RTL-compatible start and end position to a selector
// Simple "when" here just assumes that if the first argument is a list with more than 1 entry,
// then units are correct. If it's just 1, then we varify the value is a number (measurement).
.position-start-end (@trbl; @target: "") when (length(@trbl) > 1) or (@target = parent) or (@target = self) or (@target = "") {
	.position(@trbl);

	.enact-locale-rtl({
		.position(
			// Note: swapping left and right values
			.extract(@trbl; top)[]
			.extract(@trbl; left)[]
			.extract(@trbl; bottom)[]
			.extract(@trbl; right)[]
		);
	}; @target);
}

.position-start-end (@start; @end; @target: "") when (default()) {
	left: @start;
	right: @end;

	.enact-locale-rtl({
		left: @end;
		right: @start;
	}, @target);
}

// Applies RTL-compatible start and end margins to a selector
.margin-start-end (@trbl; @target: "") when (length(@trbl) > 1) or (@target = parent) or (@target = self) or (@target = "") {
	margin: @trbl;

	.enact-locale-rtl({
		margin:
			// Note: swapping left and right values
			.extract(@trbl; top)[]
			.extract(@trbl; left)[]
			.extract(@trbl; bottom)[]
			.extract(@trbl; right)[]
		;
	}; @target);
}
.margin-start-end(@start; @end; @target: "") when (default()) {
	margin-left: @start;
	margin-right: @end;

	.enact-locale-rtl({
		margin-left: @end;
		margin-right: @start;
	}, @target);
}

// Applies RTL-compatible start and end padding to a selector
.padding-start-end (@trbl; @target: "") when (length(@trbl) > 1) or (@target = parent) or (@target = self) or (@target = "") {
	padding: @trbl;

	.enact-locale-rtl({
		padding:
			// Note: swapping left and right values
			.extract(@trbl; top)[]
			.extract(@trbl; left)[]
			.extract(@trbl; bottom)[]
			.extract(@trbl; right)[]
		;
	}; @target);
}
.padding-start-end(@start; @end; @target: "") when (default()) {
	padding-left: @start;
	padding-right: @end;

	.enact-locale-rtl({
		padding-left: @end;
		padding-right: @start;
	}, @target);
}

// NOTE: Until we are able to automatically remove these JSDoc-style comments, they should remain LESS-commented
// /**
//  * Removes the margin from the appropriate side of the child components that touch the edges of the
//  * component this is applied to. This respects both LTR and RTL modes.
//  */
.remove-margin-on-edge-children() {
	> :first-child {
		margin-inline-start: 0;
	}

	> :last-child {
		margin-inline-end: 0;
	}
}

// /**
//  * Removes the padding from the appropriate side of the child components that touch the edges of the
//  * component this is applied to. This respects both LTR and RTL modes.
//  */
.remove-padding-on-edge-children() {
	> :first-child {
		padding-inline-start: 0;
	}

	> :last-child {
		padding-inline-end: 0;
	}
}


.full-screen-video-player() {
	position: static !important;
	display: block !important;
	margin: 0;
}

.hide-full-screen-ancestor() {
	position: absolute !important;
	overflow: visible !important;
	padding: 0 !important;
	margin: 0 !important;
	width: 100% !important;
	height: 100% !important;
}

.input-placeholder(@rule) {
	&::placeholder {
		@rule();
	}
}

// Assign font-kerning rules in a non-proprietary way. Default value being "normal", to enable kerning.
.font-kerning(@val: normal) {
	font-kerning: @val;
}

.vendor-fullscreen(@rule) {
	&:fullscreen { @rule(); }
}

.vendor-opacity(@opacity) {
	@opacity-ie: (@opacity * 100);	// Less doesn't like math inside `alpha`
	opacity: @opacity;
	filter: alpha(opacity=@opacity-ie);
}

// Shorthand for positioning code
.position (@t, @r, @b, @l) {
	top: @t;
	right: @r;
	bottom: @b;
	left: @l;
}
.position (@t, @rl, @b) {
	.position(@t, @rl, @b, @rl);
}
.position (@tb, @rl) {
	.position(@tb, @rl, @tb, @rl);
}
.position (@trbl) when (length(@trbl) = 4) {
	.position(
		extract(@trbl, 1),
		extract(@trbl, 2),
		extract(@trbl, 3),
		extract(@trbl, 4)
	);
}
.position (@trbl) when (length(@trbl) = 3) {
	.position(
		extract(@trbl, 1),
		extract(@trbl, 2),
		extract(@trbl, 3)
	);
}
.position (@trbl) when (length(@trbl) = 2) {
	.position(
		extract(@trbl, 1),
		extract(@trbl, 2)
	);
}
.position (@trbl) when (length(@trbl) = 1) {
	.position(@trbl, @trbl, @trbl, @trbl);
}

.border-box() {
	box-sizing: border-box;
}

// Helpful debugging way to understand how LESS variables are being interpreted
.debugLessTypes(@value) {
	:global(.debugLessTypes) {
		value: @value;
		isnumber: isnumber(@value);
		isstring: isstring(@value);
		iscolor: iscolor(@value);
		iskeyword: iskeyword(@value);
		isurl: isurl(@value);
		ispixel: ispixel(@value);
		isem: isem(@value);
		ispercentage: ispercentage(@value);
		isruleset: isruleset(@value);
	}
}

//
// Mixin classes supporting advanced text
//

// /**
//  * Generate a pair of @font-face rules for a given name and "collection" of locale fonts.
//  *
//  * This creates two (2) referenceable font-family names, one that "stacks" onto the provided "base"
//  * name, the other suffixed with the locale name. E.g. "FontName" and "FontName Locale".
//  *
//  * Example:
//  * ```
//  * @fonts: {
//  * 	as: local("LG Smart UI Bengali"); // same as `bn`
//  * 	bn: local("LG Smart UI Bengali");
//  * 	en-JP: local("LG Smart UI JP"), local("LG Display_JP_Bold") 700; // same as `ja`
//  * 	ja: local("LG Smart UI JP"), local("LG Display_JP_Bold") 700; // Generates 2 entries, one for JP set to "normal" (default) weight and another for JP_Bold set to 700 weight
//  * 	ur: local("LG Smart UI_Urdu") normal 300 600 700; // Generates 4 entries, one for each weight
//  * };
//  * ```
//  *
//  * List of supported "options" per locale key, in the following order:
//  * * single font source - Typically a reference to a local() font or a url() font.
//  * * font-weight - Optional, Numbers (100, 500, 700, etc) and keywords (normal, bold, etc) are supported.
//  * 	Empty-string represents "null". This excludes the font-weight rule from the generated code.
//  * 	Multiple weights can be supplied to generate multiple sets of face rules for a single source file
//  * * font-style - Optional, Keywords (normal, italic, etc) are supported.
//  *
//  * OPTIONALLY: repeat the options above after a comma (,) for multiple weights/styles/options based on the same name.
//  *
//  * @param  {String} @baseName The name used as the basis for the generated font-family.
//  * @param  {Object} @f        Object of keys with values detailing the font variants and options.
//  */

.buildLocaleFont(@fontName; @locale; @src; @rules) when (isruleset(@rules)) {
	.buildFontFace("@{fontName} @{locale}"; @src; @rules);
	.buildFontFace(@fontName;               @src; @rules);
}
.buildLocaleFont(@fontName; @locale; @args) when (default()) {
	@src: extract(@args, 1);
	@argLast: extract(@args, length(@args));  // optional style value

	@styleExists: boolean((@argLast = italic) or (@argLast = oblique));  // List all valid font-style values here (except for "normal" since that is a shared keyword with font-weight)
	@style: if(@styleExists, @argLast);
	@lastWeightArgIndex: if(@styleExists, length(@args) - 1, length(@args));  // It's possible this could get confused if "normal" or "bold" is used as the last weight. Please use a number as the last weight.

	// Please retain the following comment for future debugging purposes. This gives insight into how the definition values are being interpreted.
	// :global(.debug-buildLocaleFont@{fontName}-@{locale}) {
	// 	fontName: @fontName;
	// 	locale: @locale;
	// 	args: @args;
	// 	args-count: length(@args);
	// 	last-weight-arg-index: @lastWeightArgIndex;
	// 	src: @src;
	// 	styleExists: @styleExists;
	// 	style: @style;
	// }

	// Run when there are 2 or more arguments
	each(range(2, @lastWeightArgIndex), .(@argIndex) {
		@weight: extract(@args, @argIndex);
		.buildFontFace("@{fontName} @{locale}"; @src; @weight; @style);
		.buildFontFace(@fontName;               @src; @weight; @style);
	});

	// Run when there are 1 or fewer arguments
	& when (length(@args) <= 1) {
		// There aren't any additional arguments, so weight and style can be safely omitted from the following calls
		.buildFontFace("@{fontName} @{locale}"; @src);
		.buildFontFace(@fontName;               @src);
	}
};

.buildLocaleFonts(@fontName; @f) {
	each(@f, .(@specs, @locale) {
		// Determine if we're working with a collection of font definitions or an individual definition
		@isCollection: if(((length(@specs) > 1) and ((length(extract(@specs, 1)) > 1) or (length(extract(@specs, 2)) > 1))), true, false);

		// :global(.debug-buildLocaleFonts-@{locale}) {
		// 	is-collection: @isCollection;
		// }

		// Interpret collections of font definitions: [(set1) (set2) (set3)]
		& when (@isCollection) {
			each(@specs, {
				.buildLocaleFont(@fontName; @locale; @value);
			});
		}
		// Interpret individual font defiintions: (set1)
		& when (not(@isCollection)) {
			.buildLocaleFont(@fontName; @locale; @specs);
		}
		// .debugLessTypes(@specs);

		// Note: The above `when` code will ignore defiintions like the following, because the way
		// LESS works, it can't (at this time) differentiate between a space separated list and a
		// comma separated list, making it impossible to differentiate a single set of font rules
		// with multiple values vs multiple sets with a single value in each.
		//   {
		//     nameRepeat: local("name"), local("name2");
		//   }
		//   Ex: `local("name") 400` vs `local("name"), local("name2")`
		// In this case, only the first value will be used, second, ignored.
	});
};

// /**
//  * Generate a single @font-face rule.
//  *
//  * List of supported "options" per locale key, in the following order:
//  * @param  {String} font-family name   The generated font name, referenceable via
//  *                                     `font-family: <name>;` in normal CSS.
//  * @param  {URL} single font source    Typically a reference to a local() font or a url() font.
//  * @param  {Number|Keyword|""} font-weight  Optional, Numbers (100, 500, 700, etc) and keywords
//  *                                     (normal, bold, etc) are supported. Empty-string represents
//  *                                     "null" or default. This excludes the weight rule from the generated code.
//  * @param  {Keyword} font-style        Optional, Keywords (normal, italic, etc) are supported.
//  */

.buildFontFace(@name; @src; @rules) when (isruleset(@rules)) {
	@font-face {
		font-family: @name;
		src: @src;
		@rules();
	}
}

// `length()` below refers to specifying font-weight ranges: `600 900`.
// Ex: 
//   .buildFontFace("fontName"; local("name"); 400 600); 
//   -> .buildFontFace("fontName"; local("name"); 400); .buildFontFace("fontName"; local("name"); 600);
//   .buildFontFace("fontName"; local("name"); 400 600; normal);
//   -> .buildFontFace("fontName"; local("name"); 400; normal); .buildFontFace("fontName"; local("name"); 600; normal);
.buildFontFace(@name; @src; @weight: normal; @style: "") when (length(@weight) > 1) {
	each(@weight, {
		.buildFontFace(@name; @src; @value; @style);
	});
}

// The default version of this mixin (below) is the shorthand version, which rearranges its arguments into the format the above mixin expects.
.buildFontFace(@name; @src; @weight: normal; @style: "") when (default()) {
	.buildFontFace(@name; @src; {
		// Conditionally add the following
		& when ((iskeyword(@weight)) or (isnumber(@weight))) {
			font-weight: @weight;
		}
		& when (iskeyword(@style)) {
			font-style: @style;
		}
	});
}

//
// Locale Mixins
//

// .enact-locale-line-height()
//
// Assign line-height rules specifically to the languages designated
// as needing special support for tall-glyphs.
//
// Set line-height for normal and tallglyphs with 1 list argument
// Ex:
//   .enact-locale-line-height(1.4em 1.6em);
// Or:
//   @lineheight: 1.4em 1.6em;
//   .enact-locale-line-height(@lineheight);
.enact-locale-line-height(@both; @target: normal) when (length(@both) = 2) {
	.enact-locale-tallglyph(line-height; @both; @target);
}


// .enact-locale-tallglyph()
//
// Rulename and a normal value followed by a tallglyph value (3 arguments)
// Ex:
//   .enact-locale-tallglyph(font-size; 1.4em; 1.6em);  ->  normal font-size: 1.4em; tallglyphs font-size: 1.6em;
.enact-locale-tallglyph(@rule; @normal; @tallglyph; @target) when (iskeyword(@rule)) {
	@{rule}: @normal;
	.enact-locale-tallglyph(@rule; @tallglyph; @target);
}

// Rulename and 2 value second variable (2 arguments, 2nd being a list)
// Ex:
//   .enact-locale-tallglyph(font-size; 1.4em 1.6em);  ->  normal font-size: 1.4em; tallglyphs font-size: 1.6em;
// Or:
//   @fontsize: 1.4em 1.6em;
//   .enact-locale-tallglyph(font-size, @fontsize);    ->  normal font-size: 1.4em; tallglyphs font-size: 1.6em;
.enact-locale-tallglyph(@rule; @val; @target: normal) when (iskeyword(@rule)) and (length(@val) = 2) {
	@{rule}: extract(@val, 1);
	.enact-locale-tallglyph(@rule; extract(@val, 2); @target);
}

// Rulename and a tallglyph value
// Ex:
//   .enact-locale-tallglyph(font-size; 1.6em);  ->  tallglyphs font-size: 1.6em;
.enact-locale-tallglyph(@rule; @val; @target: normal) when (iskeyword(@rule)) and (default()) {
	.enact-locale-tallglyph({
		@{rule}: @val;
	}, @target);
}

// Accept a ruleset and apply it to the list of tall-glyph languages
// Ex:
//   .enact-locale-tallglyph({
//     font-size: 1.6em;
//   });
.enact-locale-tallglyph(@rules; @target: normal) when (isruleset(@rules)) and (default()) {
	each(@locale-tallglyph-languages, {
		// Take each language in the list, apply the @rules to them.
		// @value is used to generate an appropriate selector for the @rules.
		// Ex: `.enact-locale-th & { @rules(); }`
		.enact-locale(@value; @rules; @target);
	});
}


// Assign rules specifically to RTL locales with a convenient shorthand.
// Apply these rules to the current selector, not the general parent.
.enact-locale-rtl(@rules; @target) when (isruleset(@rules)) and (@target = self) {
	.enact-locale(right-to-left; @rules; @target);
}
// Assign rules specifically to RTL locales with a convenient shorthand.
.enact-locale-rtl(@rules; @target: normal) when (isruleset(@rules)) and (default()) {
	.enact-locale(right-to-left; @rules; @target);
}

// Generates an appropriate selector given the locale-target, and injects the given rules into it.
// Apply these rules to the current selector, not the general parent.
.enact-locale(@locale-target; @rules; @target) when (iskeyword(@locale-target)) and (isruleset(@rules)) and (@target = self) {
	&:global(.enact-locale-@{locale-target}) {
		@rules();
	}
}
// Generates an appropriate selector given the locale-target, and injects the given rules into it.
.enact-locale(@locale-target; @rules; @target: normal) when (iskeyword(@locale-target)) and (isruleset(@rules)) and (default()) {
	:global(.enact-locale-@{locale-target}) & {
		@rules();
	}
}

.locale-japanese-line-break() {
	.enact-locale(ja; {
		line-break: strict;
	});
}
