@import './math';
@import './unit-conversion';
@import './class-names';

//stylelint-disable
@function _legacy-check($new-var, $old-var, $default) {
    @if ($old-var != $default) {
        @return $old-var;
    }
    @return $new-var;
}

/// Wrapper for Sass' internal `get-function` that allows for compatibility across Sass versions. If version is higher than 3.5, then the function is returned, otherwise, just the name.
/// @param {string} $name - function name
/// @link https://github.com/kaelig/sass-safe-get-function
/// @group utils
///
@function safe-get-function($name) {
    @if function-exists('get-function') {
        @return get-function($name);
    } @else {
        @return $name;
    }
}

/// Wrapper for Sass' internal `call` that allows for compatibility across Sass versions. If version is higher than 3.5, then the function is returned, otherwise, just the name.
/// @param {string} $name - function name
/// @param {any} $args... - function arguments
/// @group utils
///
@function safe-call($name, $args...) {
    @return call(safe-get-function($name), $args...);
}

/// Holds all cached function calls and their results.
/// @type Map
$_cached-values: ();

/// Memoize (cache) a function's return values and arguments. Speeds up processing on intensive functions that get called many times (with the same arguments).
/// _NOTE: This can actually **increase** processing time if used too often or on simple functions as the lookup process isn't free. Every cached value is unique._
/// @param {string} $function-name
/// @param {*} $args... - Arguments to be passed to the function
/// @group utils
///
@function cache($function-name, $args...) {
    $cache-map: '#{$function-name}, #{$args}';
    $value: map-get($_cached-values, $cache-map);

    @if $value != null and $value != '' {
        @return $value;
    } @else {
        $result: call(safe-get-function(#{$function-name}), $args...);
        $_cached-values: map-merge(
            $_cached-values,
            (
                $cache-map: $result
            )
        ) !global;
        @return $result;
    }
}

/// Remove lengths (`px`, `em`, `rem`, etc.) from a value. Usually a variable. This is sometimes required because Sass cannot calculate incompatible units (`px * rem` for instance).
/// @param {Length} $value
/// @group utils
@function strip-units($value) {
    @return $value / ($value * 0 + 1);
}

/// Converts a list to a string with an optional separator.
/// @param {list} $list
/// @param {string} $separator [',']
/// @returns {string}
/// @group utils
///
@function list-to-str($list, $separator: ",") {
    $string: "";

    @for $i from 1 through length($list) {
        $string: str-append($string, #{nth($list, $i)});

        @if $i != length($list) {
            $string: str-append($string, $separator);
        }
    }

    @return $string;
}

@function list-filter($list, $predicate) {
    $index: 1;
    $length: length($list);
    $result: ();

    @while ($index <= $length) {
        $value: nth($list, $index);
        $iteration: call(safe-get-function($predicate), $value, $index, $list);

        @if ($iteration) {
            $result: append($result, $value);
        }

        $index: $index + 1;
    }

    @return $result;
}

/// Casts a string into a number.
/// @author Hugo Giraudel
/// @group utils
///
/// @param {String | Number} $value - Value to be parsed
/// @return {Number}
///
@function str-to-number($value) {
    @if type-of($value) == "number" {
        @return $value;
    } @else if type-of($value) != "string" {
        @warn 'Value for `to-number` should be a number or a string.';
    }

    $result: 0;
    $digits: 0;
    $minus: str-slice($value, 1, 1) == "-";
    $numbers: (
        "0": 0,
        "1": 1,
        "2": 2,
        "3": 3,
        "4": 4,
        "5": 5,
        "6": 6,
        "7": 7,
        "8": 8,
        "9": 9
    );

    @for $i from if($minus, 2, 1) through str-length($value) {
        $character: str-slice($value, $i, $i);

        @if not(index(map-keys($numbers), $character) or $character == ".") {
            @return to-length(
                if($minus, -$result, $result),
                str-slice($value, $i)
            );
        }

        @if $character == "." {
            $digits: 1;
        } @else if $digits == 0 {
            $result: $result * 10 + map-get($numbers, $character);
        } @else {
            $digits: $digits * 10;
            $result: $result + map-get($numbers, $character) / $digits;
        }
    }

    @return if($minus, -$result, $result);
}

/// Add `$unit` to `$value`
/// @author Hugo Giraudel
/// @group utils
///
/// @param {Number} $value - Value to add unit to
/// @param {String} $unit - String representation of the unit
/// @return {Number} - `$value` expressed in `$unit`
///
@function to-length($value, $unit) {
    $units: (
        "px": 1px,
        "cm": 1cm,
        "mm": 1mm,
        "%": 1%,
        "ch": 1ch,
        "pc": 1pc,
        "in": 1in,
        "em": 1em,
        "rem": 1rem,
        "pt": 1pt,
        "ex": 1ex,
        "vw": 1vw,
        "vh": 1vh,
        "vmin": 1vmin,
        "vmax": 1vmax
    );

    @if not index(map-keys($units), $unit) {
        @warn 'Invalid unit `#{$unit}`.';
    }

    @return $value * map-get($units, $unit);
}

/// Convert a value to a list. `$keep` accepts `'keys'`, `'values'`, or `'both'` for map conversion.
/// @group utils
/// @param {any} $value
/// @param {string} $keep - accepts `'keys'`, `'values'`, or `'both'` for map conversion.
///
@function to-list($value, $keep: "both") {
    $keep: if(index("keys" "values" "both", $keep), $keep, "both");

    @if type-of($value) == "map" {
        $keys: ();
        $values: ();

        @each $key, $val in $value {
            $keys: append($keys, $key);
            $values: append($values, $val);
        }

        @if $keep == "keys" {
            @return $keys;
        } @else if $keep == "values" {
            @return $values;
        } @else {
            @return zip($keys, $values);
        }
    }

    @return if(type-of($value) != list, ($value), $value);
}

/// Generic `contains` function(similar to javascript's `indexOf`). Parses a variable(`$data`) for a `$value`. Returns boolean. Delegates methods to the appropriate function based on variable type.
/// @group utils
/// @param {map | list | string} $data - the data you're searching through.
/// @param {*} $value - the value you're searching for.
///
@function contains($data, $value) {
    @if type-of($data == "map") {
        @return map-has-keys($data, $value);
    } @else if type-of($data == "list") {
        @return list-contains($data, $value);
    } @else if type-of($data == "string") {
        @return str-contains($data, $value);
    }
}

/// Converts a map or list to a string (`('this', 'and', 'this')` becomes `'this,and,this'`).
/// @group utils
/// @param {list | map} $list
/// @param {string} $separator [","]
///
@function str-join($list, $separator: ",") {
    @return list-to-str($list, $separator);
}

/// Checks if a `$string` contains a `$value`. Optionally accepts a start (`$pos`) positon.
/// @group utils
/// @param {string} $string
/// @param {string} $value
/// @param {number} $pos [0]
///
@function str-contains($string, $value, $pos: 0) {
    $string: str-slice($string, $pos);

    @if str-index($string, $value) != null {
        @return true;
    } @else {
        @return false;
    }
}

/// Adds one string to another (e.g. `str-append('hello', 'world')` becomes `hello world`).
/// @group utils
/// @param {string} $string
/// @param {string} $insert
///
@function str-append($string, $insert) {
    $string: if($string == null, "", $string);

    @return str-insert($string, $insert, str-length($string) + 1);
}

/// Replaces a value in a string with another.
/// @group utils
///
@function str-replace($string, $search, $replace: "") {
    $index: str-index($string, $search);

    @if $index {
        @return str-slice($string, 1, $index - 1) + $replace +
            str-replace(
                str-slice($string, $index + str-length($search)),
                $search,
                $replace
            );
    }

    @return $string;
}

/// splits a String into a list of strings by separating the string into substrings, using a specified separator string to determine where to make each split.
/// @param {String} $string - string to split.
/// @param {String} $separator - string points at which each split should occur.
/// @returns {List}
///
@function str-split($string, $separator) {
    // empty array/list
    $split-arr: ();
    // first index of separator in string
    $index : str-index($string, $separator);
    // loop through string
    @while $index != null {
        // get the substring from the first character to the separator
        $item: str-slice($string, 1, $index - 1);
        // push item to array
        $split-arr: append($split-arr, $item);
        // remove item and separator from string
        $string: str-slice($string, $index + 1);
        // find new index of separator
        $index: str-index($string, $separator);
    }
    // add the remaining string to list (the last item)
    $split-arr: append($split-arr, $string);

    @return $split-arr;
}

/// Split a string into a list of strings
/// @link https://gist.github.com/danielpchen/3677421ea15dcf2579ff
/// @param {string} $string - the string to be split
/// @param {string} $delimiter - the boundary string
/// @return {list} - the result list
///
@function str-to-list($string, $delimiter) {
    $result: ();
    @if $delimiter == "" {
        @for $i from 1 through str-length($string) {
            $result: append($result, str-slice($string, $i, $i));
        }
        @return $result;
    }
    $exploding: true;
    @while $exploding {
        $d-index: str-index($string, $delimiter);
        @if $d-index {
            @if $d-index > 1 {
                $result: append($result, str-slice($string, 1, $d-index - 1));
                $string: str-slice($string, $d-index + str-length($delimiter));
            } @else if $d-index == 1 {
                $string: str-slice(
                    $string,
                    1,
                    $d-index + str-length($delimiter)
                );
            } @else {
                $result: append($result, $string);
                $exploding: false;
            }
        } @else {
            $result: append($result, $string);
            $exploding: false;
        }
    }
    @return $result;
}

/// @alias str-to-list
/// @group utils
///
@function explode($args...) {
    @return str-to-list($args...);
}

/// Check if a list contains a value
/// @group utils
/// @param {List} $list
/// @param {-} $value
///
@function list-contains($list, $value) {
    @return not not index($list, $value);
}

/// Reverse a List
/// @group utils
/// @param {-} $list
/// @param {-} $recursive [false]
///
@function list-reverse($list, $recursive: false) {
    $result: ();

    @for $i from length($list) * -1 through -1 {
        @if type-of(nth($list, abs($i))) == list and $recursive {
            $result: append($result, reverse(nth($list, abs($i)), $recursive));
        } @else {
            $result: append($result, nth($list, abs($i)));
        }
    }

    @return $result;
}

/// List print
/// @group utils
/// @param {-} $list
/// @param {-} $depth [1]
///
@function list-print($list, $depth: 1) {
    $output: "";

    @if $depth > 0 {
        @for $i from 1 through $depth {
            $output: $output + $list;
        }
    }

    @return $output;
}

/// Add to a deeply nested map key. Accepts any number of keys, but the final value is the new key.
/// @group utils
/// @param {map} $map
/// @param {string} $keys... - final argument is considered the value to the be set.
///
@function map-deep-set($map, $keys... /*, $value */) {
    $map-list: ($map);
    $result: null;

    @if length($keys) == 2 {
        @return map-merge(
            $map,
            (
                nth($keys, 1): nth($keys, -1)
            )
        );
    }

    @for $i from 1 through length($keys) - 2 {
        $map-list: append(
            $map-list,
            map-get(nth($map-list, -1), nth($keys, $i))
        );
    }

    @for $i from length($map-list) through 1 {
        $result: map-merge(
            nth($map-list, $i),
            (
                nth($keys, $i):
                    if($i == length($map-list), nth($keys, -1), $result)
            )
        );
    }

    @return $result;
}

/// Finds a map's depth at its deepest point. Useful for debugging maps.
/// @group utils
/// @param {map} $map
/// @returns {number}
///
@function map-depth($map) {
    $level: 1;

    @each $key, $value in $map {
        @if type-of($value) == "map" {
            $level: max(map-depth($value) + 1, $level);
        }
    }

    @return $level;
}

/// Determine whether a map contains certain keys.
/// @group utils
/// @param {map} $map
/// @param {string | list} $keys...
/// @returns {boolean}
///
@function map-has-keys($map, $keys...) {
    @each $key in $keys {
        @if not map-has-key($map, $key) {
            @return false;
        }
    }

    @return true;
}

/// @alias map-has-keys
/// @group utils
///
@function map-contains-keys($map, $keys...) {
    @return map-has-keys($map, $keys);
}

/// Check for deep map keys
/// @group utils
/// @param {map} $map
/// @param {string | list} $keys...
///
@function map-has-nested-keys($map, $keys...) {
    @each $key in $keys {
        @if not map-has-key($map, $key) {
            @return false;
        }

        $map: map-get($map, $key);
    }

    @return true;
}

/// @alias map-has-nested-keys
/// @group utils
///
@function map-contains-nested-keys($map, $keys...) {
    @return map-has-nested-keys($map, $keys);
}

/// Get map value at nested or 'deep' key, per $keys list
/// @group utils
/// @param {map} $map
/// @param {string|list} $keys...
///
@function map-get-nested($map, $keys...) {
    @each $key in $keys {
        @if type-of($map) != "map" {
            @return $map;
        }
        $map: map-get($map, $key);
    }
    @return $map;
}

/// Merges multiple maps. Similar to jQuery's `extend`.
/// Takes two maps and merges their differences.
/// @group utils
/// @param {map} $map
/// @param {map|list} $maps... - final value can be `true` for "deep" merging
///
@function map-extend($map, $maps... /*, $deep */) {
    $last: nth($maps, -1);
    $deep: $last == true;
    $max: if($deep, length($maps) - 1, length($maps));

    // Loop through all maps in $maps...
    @for $i from 1 through $max {
        // Store current map
        $current: nth($maps, $i);

        // If not in deep mode, simply merge current map with map
        @if not $deep {
            $map: map-merge($map, $current);
        } @else {
            // If in deep mode, loop through all tuples in current map
            @each $key, $value in $current {
                // If value is a nested map and same key from map is a nested map as well
                @if type-of($value) == "map" and
                    type-of(map-get($map, $key)) == "map"
                {
                    // Recursive extend
                    $value: map-extend(map-get($map, $key), $value, true);
                }

                // Merge current tuple with map
                $map: map-merge(
                    $map,
                    (
                        $key: $value
                    )
                );
            }
        }
    }

    @return $map;
}

//Map depth-adder
//-------------------------
// Adds 'base'(or whatever initial key that's passed in) to a color map.
// Used by color map generator function (colors/_colorfunctions)
//--------------------------
// 1. Check if map is less than or equal to the depth tolerance (default: 1).
// 2. loops through all keys and values in the map.
// 3. Make sure value isn't alreayd a map
//   3a. If value is a map, then check to make sure $new-key is in the map
//     3b. Duplicate first value of map (in case there are multiple)
//     3c. Create new map with new-key and duplicated value
// 4. Reformats those key-value pairs into a (key: (new-key: value ))
// 5. Merges new depthy key-key-value back into the map.
// 6. Returns the new map.

/// Adds one level to a map using the new key to a map. Used by `generate_color_varations()`. (e.g. `(key: value, key: value)` becomes `(key:(new-key: value), key:(new-key: value))` )
/// @group utils
/// @param {map} $map
/// @param {string} $new-key ['base']
///
@function map-add-depth($map, $new-key: "base") {
    $formatted-key: ();
    $new-map: "";

    @each $key, $value in $map {
        //[2]
        $formatted-key: ();
        @if type-of($value) != "map" {
            //[3]
            $formatted-key: (
                $key: (
                    $new-key: $value
                )
            ); //[4]
        } @else if not map-has-key($value, $new-key) {
            //[3a]
            $duplicate-value: map-get($value, nth(map-keys($value), 1)); //[3b]
            $duplicate-map: (
                $new-key: $duplicate-value
            ); //[3c]

            $formatted-key: (
                $key: (
                    map-merge($value, $duplicate-map)
                )
            ); //[4]
        }
        $map: map-merge($map, $formatted-key); //[5]
    }

    @return $map; //[6]
}

/// Print maps into a string
/// @group utils
/// @param {map} $map
/// Useful for debugging maps.
/// @link https://github.com/lunelson/sass-maps-plus
///
@function map-inspect($map, $level: 1) {
    $tab: "    ";
    $cr: "\
";

    @if type-of($map) != "map" {
        @return "#{inspect($map)}";
    }

    $indent: list-print($tab, $level + 1);
    $output: "{" + $cr + $indent;
    $i: 1;

    @each $key, $value in $map {
        @if type-of($value) == "map" {
            $output: $output + "#{$key}: #{map-inspect($value, $level+1)}";
        } @else {
            $output: $output + "#{$key}: #{$value}";
        }

        @if $i < length(map-keys($map)) {
            $output: $output + "," + $cr + $indent;
        }

        $i: $i + 1;
    }

    $outdent: list-print($tab, $level);

    @return $output + $cr + $outdent + "}";
}

/// @alias map-inspect
/// @group utils
///
@function map-print($map, $level: 1) {
    @return map-inspect($map, $level);
}

/// Add to silent classes registry
/// @group utils
///
@function silents-register($map, $key) {
    @if $debug-silent-classes {
        @if map-get($_silent-class-registry, $key) == null {
            $_silent-class-registry: map-merge(
                $_silent-class-registry,
                (
                    $key: ()
                )
            ) !global;
        }
        $map: map-merge(map-get($_silent-class-registry, $key), $map);
        @return map-deep-set($_silent-class-registry, $key, $map);
    } @else {
        @return null;
    }
}




// @debug _interpolate-class-namespaces(
//     format-class-names(
//         '{%breakpoint%}{\\@}{%property%}{__}{%direction%}{-}{%amount%}',
//         ('breakpoint': 'small', 'property': 'pad', 'amount': '2')
//     )
// );

//
// @debug str-replace(
//     list-to-str(
//     _format-class-namespaces(
//     interpolate-class-names(
//         '{%breakpoint%}{\\@}{%property%}{__}{%direction%}{-}{%amount%}',
//         ('breakpoint': 'bigs', 'property': 'pad', 'direction': '', 'amount': '2')
//     )
//     )
//     ,''),
// '>', '');

@function insert-nth($list, $index, $value) {
    $result: ();
    @for $i from 1 through length($list) {
        @if $i == $index {
            $result: append($result, $value);
        }

      $result: append($result, nth($list, $i));
    }
    @return $result;
}




// @function _template-string($formatting, $values) {
//     $_return: $string;
//
//     @each $val in $values {
//         @if str-index($_return, '%s') {
//             $_return: str-replace($_return, '%s', $val);
//         }
//     }
//
//     @return $_return;
// }

// @debug format-class-names(
//     '{%breakpoint%}{\\@}{%property%}{-}{%direction%}{__}{%amount%}',
//     ('breakpoint': '', 'property': 'pad', 'direction':'', 'amount': '2')
// );

// @function placeholder-filter($list, $ltr: false) {
//     $split-str: if($ltr, $list, list-reverse($list));
//     $_word-list: ('');
//     $_i: 1;
//     @debug '\A\A========================[ I N P U T ]======================== \A #{list-print($split-str)} \A';
//     @each $split in $split-str {
//         $_current: if($split != '', '{' + $split, $split);
//         $second-split: str-split($split, '}');
//         $_is-split: (type-of($second-split) == 'list' and length($second-split) > 1);
//         @if $split != '' and length($second-split) > 1 {
//             @debug "#{list-print($second-split)}";
//             $second-split: if($ltr, $second-split, list-reverse($second-split));
//             $_previous-index: max(1, $_i - 1);
//             //@debug length($second-split);
//             $_current: nth($second-split, 1);
//             $_previous: nth($split-str, $_previous-index);
//
//             $_next-index: min(length($split-str), $_i + 1);
//             $_next: nth($second-split, 2);
//
//             @if str-index($_current, '}') == null {
//                 @if (
//                     _namespace-is-needed($_current, $_next)
//                 ){
//                     // @debug '#{$_current} made it through';
//                     // $_current: if($ltr,
//                     //     str-slice($_current, 2, -1),
//                     //     str-slice($_current, 1, -1)
//                     // );
//                     $_current: ('#{$_current}}', $_next);
//                 }
//                 @else {
//                     @debug 'Not qualified: ' + $_current;
//                     $_current: if($_is-split, $_next, "");
//                 }
//             }
//             @else {
//                 $_current: if(
//                     str-index($_current, '%') > 1,
//                     '{#{$_current}}',
//                     '#{$_current}}'
//                 );
//             }
//         }
//         $_word-list: if($_current != '', join($_word-list, $_current), $_word-list);
//         // @debug $_word-list;
//         $_i: $_i + 1;
//     }
//     @debug "OUT: #{list-reverse($_word-list)}";
//     @return if($ltr, $_word-list, list-reverse($_word-list));
// }

// @function _add-brackets($check) {
//     @return if(str-index($check, '}') > 1, '{#{$check}');
// }





// $class-name: 'grid';
//
// @debug format-class-names('.u-%class%\\@%breakpoint%', ('class': $class-name, 'breakpoint': 'sm'));

// '.u-%class%\\@%breakpoint%', ('class': $class, 'breakpoint': $breakpoint)
