/// Interpolates lists of strings to generate dynamic class names.
/// Using a specific format (_see below_) and a set of placeholders provided by functions, a class name can be generated in a more flexible fashion.
/// Will also filter out optional strings if they appear at the beginning or end of a string (such as values between two placeholders).
/// Using lists allows for better caching and creates inherent dependencies between optional characters and their placeholders. For instance, `'{%var%}{_}'` implies that the `_` is dependent on `{%var%}` (if `{%var%}` is evaluated to be blank, then `_` is also blank).
///
/// **Format Syntax**
/// * `{%key%}` - **Variable**: Expects to be replaced by a value provided by a map.
/// * `{_}` - **Placeholder**: Optional spacing value. Will be filtered out if not surrounded by non-blank characters.
///
/// @group core
///
/// @param {String} $string [] - The string to format.
/// @param {Map} $values-map [] - map with keys cooresponding to the variables in the string.
/// @param {boolean} $stringify [false] - Convert the list to a string before returning?
///
/// @example scss - Simple example
///  $class-format: '.{%breakpoint%}_hidden';
///  format-class-list($class-format, ('breakpoint': 'small'));
///  // Out: '.small_hidden'
///
/// @example scss - Complex example with lists and optional values left blank
///  $class-format: '.{%breakpoint%}{_}''{%display%}';
///  format-class-list(
///    $class-format,
///    ('display': 'flex', 'breakpoint': '')
///  );
/// // Out: '.flex'
///
@function format-class-name($list, $values-map, $stringify: false) {
    $_return: ();
    @each $group in $list {
        @if (str-index('#{$group}', '%')) {
            @each $word, $value in $values-map {
                @if ((str-index($group, '{%#{$word}%}'))) {
                    $group: cache(
                        str-replace,
                        $group,
                        '{%#{$word}%}',
                        '#{$value}'
                    );
                    $group: if(
                        str-index($group, '{'),
                        cache(_interpolate-placeholders, $group),
                        $group
                    );
                }
            }
        }
        $_return: if(length($group), append($_return, $group), $_return);
    }
    @return if($stringify, _fast-str($_return), $_return);
}

/// @alias format-class-name
///
@function format-class-list($args...) {
    @return format-class-name($args...);
}

/// Automatically makes $stringify `true`
/// @group core
/// @alias format-class-name
///
@function _class($args...) {
    $args: append($args, true);
    @return format-class-name($args...);
}

/// Determines if a placeholder (`{_}`) value in a format is needed based on characters in its surrounding.
/// If two placeholders are next to eachother, both will be deleted. If a placeholder is surrounded by blank values, it will also be deleted.
/// **Note that this returns a list, and not a fully formatted string.** This allows the return value to be recursed without incuring the performance penalty of re-casting the string to a list.
///
/// @group utils
///
/// @param {string} $string - String to be parsed and filtered.
/// @param {boolean} $reverse [false] - Whether to parse the values from right to left or left to right. Reverse typically provides more accurate results.
///
/// @example scss
///   $foo: _filter-placeholders('{-}{%var%}{_}{_}')
///   @debug inspect($foo) // ">-""{%var%}"
///
@function _filter-placeholders($string, $reverse: false) {
    $list: $string;

    // If we recieved a string, we know this is from a user,
    // A list will only be received by another function,
    // In which case, we do not need to do the following process
    @if type-of($list) == 'string' {
        @if (str-index($list, '{') == null) {
            @return $list;
        }
        // Change '{}' to '|>|'
        // so we can split by one character,
        // while also maintaining a variable indicator
        $string: str-replace($string, '{', '|>');
        $string: str-replace($string, '}', '|');
        @if (str-index($string, '%')) {
            $string: str-replace($string, '|>%', '{%');
            $string: str-replace($string, '%|', '%}');
        }
        $list: str-split($string, '|');
        $list: list-filter($list, _is-not-blank);
    }

    // To keep things in order, we'll just replace values in the current list
    $_return: $list;
    $start-i: if($reverse, length($list), 1);
    $end-i: if($reverse, 1, length($list));

    @for $i from $start-i through $end-i {
        $item: nth($list, $i);

        // We only need to process items that start with a '>'
        // since this indicates that it's a placeholder
        @if (str-index('#{$item}', '>') != null) {
            $next-index: min(length($list), $i + 1);
            $next-item: nth($list, $next-index);

            $previous-index: max(1, $i - 1);
            $previous-item: nth($list, $previous-index);

            // If the next or previous item's index is the current index,
            // then we'll just make them blank values to simplify comparisons
            $next-item: if($next-index == $i, '', $next-item);
            $previous-item: if($previous-index == $i, '', $previous-item);

            // Combined values for simpler comparison
            $item-str: '#{$previous-item}#{$next-item}';

            // If a either surrounding values are a variable (%), keep it.
            // If we're just looking at our selves, or another placeholder, throw it out.
            @if (str-index($item-str, '%')) or
                (
                    ($previous-index != $next-index) and
                        (str-index($item-str, '>') == null)
                )
            {
                // Placeholder should exist
                $_return: set-nth($_return, $i, $item);
            } @else {
                // Set this to placeholder to null
                $_return: set-nth($_return, $i, null);
            }
        } @else {
            // Anything not a placeholder goes through
            $_return: if(
                _is-not-blank($item),
                set-nth($_return, $i, $item),
                set-nth($_return, $i, null)
            );
        }
    }

    @return $_return;
}

/// Container funtion for `_filter-placeholders`.
/// Takes a string and gives back a string (and not a list).
/// @group utils
///
/// @param {string} $string - string to be interpolated
/// @returns {string}
/// @see _filter-placeholders
///
@function _interpolate-placeholders($string) {
    @if type-of($string) == 'list' {
        $string: _fast-str($string);
    }

    $split-str: _filter-placeholders($string, true);

    @if length($split-str) == 0 {
        @return '';
    }

    $merged-list: _fast-str($split-str);

    // Remove remaining '>'
    @return if(
        str-index($merged-list, '>'),
        str-replace($merged-list, '>', ''),
        $merged-list
    );
}

/// Checks if a value is blank, used specifically for `list-filter`
/// @param {string} $value - the value to check
/// @returns {boolean}
///
@function _is-not-blank($value, $args...) {
    @return ($value != '' and $value != null);
}

/// A faster list-to-string function. Should only be used in cases where speed is very important. Does very little error protection.
/// @group utils
///
/// @param {list} $list - list to be converted
///
@function _fast-str($list) {
    $_return: '';
    @each $item in $list {
        $_return: if($item, $_return + $item, $_return);
    }
    @return $_return;
}
