////
/// @group Utilities
/// @author <a href="https://github.com/simeonoff" target="_blank">Simeon Simeonoff</a>
////

/// Converts pixels to relative values (em).
/// @access public
/// @param {number|string} $pixels - The pixel value to be converted.
/// @param {number|string} $context [$browser-context] - The base context to convert against.
@function em($pixels, $context: $browser-context) {
    @if (unitless($pixels)) {
        $pixels: $pixels * 1px;
    }
    @if (unitless($context)) {
        $context: $context * 1px;
    }
    @return ($pixels / $context) * 1em;
}

/// Pixels to root relative values (rem).
/// @access public
/// @param {number|string} $pixels - The pixel value to be converted.
/// @param {number|string} $context [$browser-context] - The base context to convert against.
@function rem($pixels, $context: $browser-context) {
    @if (unitless($pixels)) {
        $pixels: $pixels * 1px;
    }
    @if (unitless($context)) {
        $context: $context * 1px;
    }
    @return ($pixels / $context) * 1rem;
}

/// Calculates the luminance for a given color.
/// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests.
///
/// @param {Color} $color - The color to calculate luminance for.
@function luminance($color) {
    @if type-of($color) == 'color' {
        $red: nth($linear-channel-values, red($color) + 1);
        $green: nth($linear-channel-values, green($color) + 1);
        $blue: nth($linear-channel-values, blue($color) + 1);

        @return .2126 * $red + .7152 * $green + .0722 * $blue;
    }
    @return $color;
}

/// Calculates the contrast ratio between two colors.
/// See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests
///
/// @param {Color} $background - The background color.
/// @param {Color} $foreground - The foreground color.
/// @returns {Number} - The contrast ratio between the background and foreground colors.
@function contrast($background, $foreground) {
    $backLum: luminance($background) + .05;
    $foreLum: luminance($foreground) + .05;

    @return max($backLum, $foreLum) / min($backLum, $foreLum);
}

/// Generates a color shade from base color and saturation level.
/// @access private
/// @group Palettes
/// @param {Color} $color - The base color to be used to generate a color shade.
/// @param {number|string} $saturation - The saturation level used to create the color shade.
@function gen-color($color, $saturation) {
    @if ($saturation == 50) {
        @return lighten(saturate($color, 10.4), 37.7);
    } @else if ($saturation == 100) {
        @return lighten(desaturate($color, 10.4), 31.8);
    } @else if ($saturation == 200) {
        @return lighten(desaturate($color, 17), 18.7);
    } @else if ($saturation == 300) {
        @return lighten(desaturate($color, 12.9), 9.1);
    } @else if ($saturation == 400) {
        @return lighten(desaturate($color, 6.6), 4.1);
    } @else if ($saturation == 500) {
        @return $color;
    } @else if ($saturation == 600) {
        @return darken(saturate($color, 12.4), 5.1);
    } @else if ($saturation == 700) {
        @return darken(saturate($color, 24.5), 8.8);
    } @else if ($saturation == 800) {
        @return darken(saturate($color, 23.2), 12.7);
    } @else if ($saturation == 900) {
        @return darken(saturate($color, 16.1), 17);
    } @else if ($saturation == 'A100') {
        @return lighten(saturate($color, 10.4), 16.7);
    } @else if ($saturation == 'A200') {
        @return lighten(saturate($color, 10.4), 7.7);
    } @else if ($saturation == 'A400') {
        @return darken(saturate($color, 10.4), 3.9);
    } @else if ($saturation == 'A700') {
        @return darken(saturate($color, 10.4), 16.6);
    }
}

/// Retrieves a color from a color palette.
/// @access public
/// @group Palettes
/// @param {Map} $palette - The source palette map.
/// @param {string} $color - The target color from the color palette.
/// @param {number|string} $variant [500] - The target color shade from the color palette.
/// @returns {Color} White if no palette, color, and variant matches found.
@function igx-color($palette, $color, $variant: 500) {
    @if type-of($palette) == 'map' and map-has-key($palette, $color) {
        @return map-get(map-get($palette, $color), $variant);
    }

    @return #fff;
}

/// Converts a rgba color to a hexidecimal color.
/// @access public
/// @requires {function} to-string
/// @param {Color} $rgba - The rgba color to convert.
/// @param {Color} $background - The background color to convert against.
/// @return {Color} - The hexidecimal representation of the rgba value.
@function hexrgba($rgba, $background: #fff) {
    @if type-of($rgba) == color {
        $red: red($rgba);
        $green: green($rgba);
        $blue: blue($rgba);
        $a: alpha($rgba);
        $r: floor($a * $red + (1 - $a) * red($background));
        $g: floor($a * $green + (1 - $a) * green($background));
        $b: floor($a * $blue + (1 - $a) * blue($background));
        @return rgb($r, $g, $b);
    }
    @return $rgba;
}

/// Returns a contrast color for a passed color.
/// @access public
/// @group Palettes
/// @param {Color} $color - The color used to return a contrast color for.
/// @returns {Color} - Returns either white or black depending on the luminance of the input color.
@function text-contrast($color) {
    @if type-of($color) == 'color' {
        $lightContrast: contrast($color, white);
        $darkContrast: contrast($color, black);

        @if ($lightContrast > $darkContrast) {
            @return white;
        } @else {
            @return black;
        }
    }
    @return $color;
}

/// Retrieves a contrast text color for a given color from a color palette.
/// @access public
/// @group Palettes
/// @param {Map} $palette - The source palette map.
/// @param {string} $color - The target color from the color palette.
/// @param {number|variant} $variant - The target color shade from the color palette.
/// @requires igx-color
/// @requires text-contrast
/// @requires hexrgba
/// @returns {Color} [#fff] - Returns white if now palette, color and/or variant matches found.
@function igx-contrast-color($palette, $color, $variant: 500) {
    $_color: igx-color($palette, $color, $variant);
    @if $_color {
        @return text-contrast(hexrgba($_color));
    }
    @return #fff;
}

/// Test if `$value` is a valid direction.
/// @access private
/// @param {*} $value - The value to test.
/// @return {Bool}
@function is-direction($value) {
    $is-keyword: index( ( to top, to top right, to right top, to right, to bottom right, to right bottom, to bottom, to bottom left, to left bottom, to left, to left top, to top left), $value);
    $is-angle: type-of($value)=='number' and index('deg' 'grad' 'turn' 'rad', unit($value));
    @return $is-keyword or $is-angle;
}

/// Test if a component, or list of components
/// is in the list of known components.
/// @access private
/// @param {String|List} $excludes - The components list to check in.
/// @return {List} - The list of passed items.
@function is-component($items) {
    $register: map-keys($components);
    @each $item in $items {
        @if not(index($register, $item)) {
            @error unquote('Can\'t exclude "#{$item}" because it is not in the list of known components.');
        }
    }
    @return $items;
}

/// Converts numbers to HEX value strings.
/// @access private
/// @param {number} $num - The number to convert.
/// @param {radix} $radix - The base radix to use for the conversion.
/// @return {String} - The resulting string.
@function to-string($num, $radix: 16) {
    $chars: '0123456789abcdef';
    $result: '';
    $sign: '';
    @if $num < 0 {
        $sign: '-';
        $num: abs($num);
    }
    @if $num >= 0 and $num < $radix {
        @return $sign + str-slice($chars, ($num + 1), ($num + 1));
    }
    $q: $num;
    @while $q !=0 {
        $r: $q % $radix;
        $q: floor($q / $radix);
        $result: str-slice($chars, ($r + 1), ($r + 1)) + $result;
    }
    @return $sign + $result;
}

/// Removes all null key-value pairs from the map
/// @access private
/// @param {Map} $map - The target map to be cleaned.
/// @returns {Map} - Returns a clean map.
@function map-clean($map) {
    $result: ();

    @each $key, $value in $map {
        @if($value) != null {
            $result: map-merge($result, (
                #{$key}: $value
            ));
        }
    }

    @return $result;
}

/// Extends a Map object with the properties of another Map object.
/// @access private
/// @param {Map...} $maps - The source map to get extended.
/// @returns {Map} - Returns the merged maps.
@function extend($maps...) {
    $result: ();

    @each $map in $maps {
        $result: map-merge($result, map-clean($map));
    }

    @return $result;
}

/// Generates a Material-like color palette for a single color.
/// @access private
/// @group Palettes
/// @requires {function} gen-color
/// @requires {function} text-contrast
/// @param {Color} $color - The base color used to generate the palette.
/// @param {List} $saturations - The saturation list of color variants.
//// Based on the Material color system.
/// @returns {Map} - A map consisting of 14 color variations and 14
/// text contrast colors for each variation.
@function generate-palette($color, $saturations) {
    $result: ();
    @each $saturation in $saturations {
        $shade: gen-color($color, $saturation);
        $result: map-merge($result, ($saturation: $shade));
    }
    @return $result;
}

/// Generates grayscale color palette from a color.
/// @access private
/// @group Palettes
/// @requires {function} text-contrast
/// @param {Color} $color - The base color used to generate the palette.
/// @param {Map} $shades - A map of variations as keys and opacities as values.
/// Based on the Material color system.
/// @returns {Map} - A map consisting of 10 grayscale color variations and 10
/// text contrast colors for each variation.
@function grayscale-palette($color, $shades) {
    $result: ();
    @each $saturation, $opacity in $shades {
        $shade: rgba(grayscale($color), $opacity);
        $result: map-merge($result, ($saturation: $shade));
    }
    @return $result;
}

/// Generates a color palette.
/// @access public
/// @group Palettes
/// @requires {function} generate-palette
/// @param {Color} $primary - The primary color used to generate the primary color palette.
/// @param {Color} $secondary - The secondary color used to generate the secondary color palette.
/// @param {Color} $info [#1377d5] - The information color used throughout the application.
/// @param {Color} $success [#4eb862] - The success color used throughout the application.
/// @param {Color} $warn [#fbb13c] - The warning color used throughout the application.
/// @param {Color} $error [#ff134a] - The error color used throughout the application.
/// @param {Color} $grays [#000] - The color used for generating the grayscale palette.
/// @param {Color} $surface [#fff] - The color used as a background in components, such as cards, sheets, and menus.
/// @returns {Map} - A map consisting of 74 color variations, including the `primary`, `secondary`, `grays`,
/// `info`, `success`, `warn`, and `error` colors.
@function igx-palette(
    $primary,
    $secondary,
    $info: #1377d5,
    $success: #4eb862,
    $warn: #fbb13c,
    $error: #ff134a,
    $grays: #000,
    $surface: #fff
) {
    $saturations: (50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 'A100', 'A200', 'A400', 'A700');
    $shades: (50: .02, 100: .04, 200: .08, 300: .12, 400: .26, 500: .38, 600: .54, 700: .62, 800: .74, 900: .87);

    $primary-palette: generate-palette($primary, $saturations);
    $secondary-palette: generate-palette($secondary, $saturations);
    $grayscale-palette: grayscale-palette($grays, $shades);

    // @debug 'Primary Colors Palette: #{$primary-palette}';
    // @debug 'secondary Colors Palette: #{$secondary-palette}';
    // @debug 'Warn Colors Palette: #{$warn-palette}';

    @return (
        primary: $primary-palette,
        secondary: $secondary-palette,
        grays: $grayscale-palette,
        info: (500: $info),
        success: (500: $success),
        warn: (500: $warn),
        error: (500: $error),
        surface: (500: $surface)
    );
}

/// Returns a CSS property value. It could return either css '--var' or
/// a plain string value based on theme configuration.
/// @access public
/// @param {map} $map - The source theme to be used to read values from.
/// @param {string} $key - A key from the theme map to assign as value to the property.
/// @example scss Assign the color property in a rule-set to a value from the default avatar theme.
///   %igx-avatar-icon {
///     color: --var($avatar-theme, 'icon-color');
///   }
///   // If legacy-support is off, it will produce the following:
///   // %igx-avatar-icon {
///   //   color: var(--igx-avatar-icon-color);
///   // }
///   // otherwise, it will return the value for key 'icon-color' in the '$avatar-theme';
/// @returns {String} - The value of the key in the passed map, or the name of key concatenated with the key name.
@function --var($map, $key, $fallback: null) {
    $igx-legacy-support: if(global-variable-exists('igx-legacy-support'), $igx-legacy-support, true);

    @if map-has-key($map, $key) {
        @if $igx-legacy-support == false {
            @if not($fallback) {
                @return var(--#{map-get($map, 'name')}-#{$key})
            }
            @return var(--#{map-get($map, 'name')}-#{$key}, #{$fallback})
        } @else {
            @return map-get($map, $key);
        }
    } @else {
        @error unquote('The map you passed does not contain a key #{$key}');
    }
}

/// Splits a string into a list by a given separator.
/// @access private
/// @param {string} $string - The string to split.
/// @param {string} $separator - The string separator to split the string by.
@function str-split($string, $separator) {
    $split-arr: ();
    $index : str-index($string, $separator);
    @while $index != null {
        $item: str-slice($string, 1, $index - 1);
        $split-arr: append($split-arr, $item);
        $string: str-slice($string, $index + 1);
        $index : str-index($string, $separator);
    }
    $split-arr: append($split-arr, $string);
    @return $split-arr;
}

/// Returns a value for a given instruction map, where the
/// keys of the map are the names of the functions to be called,
/// and the value for a given key is the arguments the function
/// should be called with.
/// The instruction set is executed left-to-right. The output of
/// the first instruction is then passed as input to the next, and so on.
/// @access private
/// @param {Map} $ctx - The instruction map to be used.
/// @param {List | Map} $extra [null] - Additional arguments to be passed during function calls.
/// Will only be applied for `igx-color` and `igx-contrast-color` functions.
/// @example scss Resolve `igx-color` and apply 80% opacity
/// $instructions: (
///   igx-color: ('primary', 500),
///   rgba: .2
/// );
/// // $some-palette is a palette we pass as extra arguments
/// $resolved-color: resolve-value($instructions, $some-palette);
///
@function resolve-value($ctx, $extra: null) {
    $result: null;
    @each $fn, $args in $ctx {
        @if function-exists($fn) {
            @if $result == null and ($fn == 'igx-color' or $fn == 'igx-contrast-color') {
                $result: call(get-function($fn), $extra, $args...);
            } @else if $result != null {
                $result: call(get-function($fn), $result, $args...)
            } @else {
                $result: call(get-function($fn), $args...)
            }
        }
    }
    @return $result;
}

/// Returns a random color.
/// @access private
@function random-color() {
    @return hsl(random(360), 100%, 50%);
}

/// Applies an `igx-palette` to a given theme schema.
/// @access private
/// @param {Map} $schema - A theme schema.
/// @param {Map} $palette - An igx-palette map.
/// @requires {function} extend
/// @requires {function} resolve-value
/// @example scss Apply an `igx-palette` to theme schema
///   $custom-palette: igx-palette($primary: red, $secondary: blue);
///   $custom-schema: (
///     my-color: (
///        igx-color: ('primary', 800),
///        rgba: .5
///     ),
///     roundness: 5px
///   );
///   $theme: apply-palette($custom-schema, $custom-palette); // A map with palette values resolved.
/// @returns {Map} - A map with the resolved palette values.
@function apply-palette($schema, $palette) {
    $result: ();
    @each $key, $value in $schema {
        @if type-of($value) == 'map' {
            $result: extend($result, (#{$key}: resolve-value($value, $palette)));
        } @else {
            $result: extend($result, (#{$key}: $value));
        }
    }
    @return $result;
}

/// Returns true if the scope where it's called
/// is the root of the document.
/// @access private
/// @example scss Check if the current scope is root
///   @mixin smart-mixin() {
///     $scope: if(is-root(), ':root', '&');
///
///     #{$scope} {
///       /* style rules here */
///     }
///   }
///
@function is-root() {
    @each $selector in & {
        @return if($selector == null, true, false);
    }
}


/// Returns calc value between given min and max values
/// @access private
@function fraction-clamp($factor, $min, $max) {
    $min: if(unitless($min), $min * 1px, $min);
    $max: if(unitless($max), $max * 1px, $max);

    @if $min > $max {
        @error 'Min boundary should be less than max boundary.';
    }

    @return ($max - $min) * $factor + $min;
}


/// If the value is unitless, returns value or list of values between 0 and 1
/// where the 0 is the minimum and 1 is the maximum
/// If the value is NOT unitless return the value or list of values with the unit you pass
/// @access private
@function round-borders($value, $min: 0, $max: 0) {
    @if type-of($value) == 'number' and unitless($value) {

        @if $value < 0 or $value > 1 {
            @error 'Roundness factor should be between 0 and 1';
        }

        @return round(fraction-clamp($value, $min, $max));
    }

    @if type-of($value) == 'list' and length($value) <= 4 {
        $result: ();
        @each $i in $value {
            $result: append($result, round-borders($i, $min, $max), auto);
        }
        @return $result;
    }

    @return $value;
}

/// A thin wrapper around Sass' lighten function
/// to allow pass-through for values other than those
/// of type color.
/// @access private
/// @param {Color} $color - The color to be lightened.
/// @param {Percent} $amount - The percent value by which the color will be lightened.
/// @returns {Color | any} - The modified color as produced by the lighten function.
/// If the value passed is not a color, then the passed value will be returned.
@function lighten-color($color, $amount) {
    @if type-of($color) == 'color' {
        @return lighten($color, $amount);
    }
    @return $color;
}

/// A thin wrapper around Sass' darken function
/// to allow pass-through for values other than those
/// of type color.
/// @access private
/// @param {Color} $color - The color to be lightened.
/// @param {Percent} $amount - The percent value by which the color will be lightened.
/// @returns {Color | any} - The modified color as produced by the lighten function.
/// If the value passed is not a color, then the passed value will be returned.
@function darken-color($color, $amount) {
    @if type-of($color) == 'color' {
        @return darken($color, $amount);
    }
    @return $color;
}

/// Returns $if param when global variable $directory
/// is set to ltr otherwise returns $else.
/// @param {*} $if - The value to be returned when $direction is set to ltr;
/// @param {*} $else - The value to be returned when $direction is set to rtl;
/// @example scss Set variable values based on $direction
///    $left: if-ltr(left, right);
///    $right: if-ltr(right, left);
/// @requires $direction
@function if-ltr($if, $else: null) {
    $dir: if(global-variable-exists('direction'), $direction, 'ltr');

    @if $dir != rtl {
        @return $if;
    } @else {
        @return $else;
    }
}

/// Returns $if param when global variable $directory
/// is set to rtl otherwise returns $else.
/// @param {*} $if - The value to be returned when $direction is set to rtl;
/// @param {*} $else - The value to be returned when $direction is set to ltr;
/// @example scss Set variable values based on $direction
///    $left: if-rtl(left, right);
///    $right: if-rtl(right, left);
/// @requires $direction
@function if-rtl($if, $else: null) {
    @return if-ltr($else, $if);
}
