@use 'sass:list';
@use 'sass:string';
////
/// @group utils.bem
////

@use 'units' as *;

$-block-prefix: '' !default;
$-element-prefix: '__' !default;
$-modifier-prefix: '--' !default;

// ----------------------------------------------------------------------------
// PRIVATE
// ----------------------------------------------------------------------------

@function -bem-class($blockOrElement: false, $modifier: '', $pseudo: '', $prefix: '') {
    @if (not $blockOrElement) {
        @error "Missing block or element name.";
    }

    @if ($modifier != '') {
        $modifier: #{$-modifier-prefix + $modifier};
    }

    @if ($pseudo != '') {
        $pseudo: #{':' + $pseudo};
    }

    @return #{'.' + $prefix + $blockOrElement + $modifier + $pseudo};
}

@mixin -bem-class($nameOrNames: false, $modifier: '', $pseudo: '', $prefix: '') {
    $selector: false;

    @if is-list($nameOrNames) {
        $selector: ();

        @each $n in $nameOrNames {
            $selector: list.append($selector, -bem-class($n, $modifier, $pseudo, $prefix), comma);
        }
    } @else {
        $selector: -bem-class($nameOrNames, $modifier, $pseudo, $prefix);
    }
    #{$selector} {
        @content;
    }
}

// ----------------------------------------------------------------------------
// PUBLIC
// ----------------------------------------------------------------------------

$--block: null;

/// Establishes a block context for the bem mixin. The bem mixins will still work with regular
/// nesting but they will create deeply nested classnames (e.g. `.block .block__element` instead of
/// just `.block__element`) which may create more specificity than desired.
///
/// ```scss
/// @@include block('block') {
///     @include bem('element') {
///         ...
///     }
/// }
/// ```

@mixin block($name) {
    $--block: $name !global;
    @content;
    $--block: null !global;
}

/// Extracts the block name from a selector. So if $selector is something like
/// `.block__element--mod` the function would return `block`.
///
/// @param {*} $selector
/// @example get-block-name('.block__element--mod') => block
/// @example get-block-name(&) => [name of block as extracted from current path]

@function get-block-name($selector) {
    @if ($--block != null) {
        @return $--block;
    }
    $selector: #{$selector};
    $inf: 9999999;
    $dot-index: if-null(string.index($selector, '.'), 1);
    $space-index: if-null(string.index($selector, ' '), $inf);
    $modifier-index: if-null(string.index($selector, $-modifier-prefix), $inf);
    $element-index: if-null(string.index($selector, $-element-prefix), $inf);
    $pseudo-index: if-null(string.index($selector, ':'), $inf);
    $min-index: min($space-index, $modifier-index, $element-index, $pseudo-index);
    @return string.slice($selector, $dot-index + 1, $min-index - 1);
}

/// Function version of the bem mixin (see mixin for details).

@function get-bem($name, $modifier: '', $pseudo: '', $block: null) {
    $block: if-null($block, get-block-name(&));
    @return -bem-class($name, $modifier, $pseudo, #{$-block-prefix + $block + $-element-prefix});
}

/// Creates a element--modifier class name using the bem approach to class naming. Can be used
/// inside of the block mixin or just within a class declartion. Should also work with media media
/// queries and other complex scnearios.
///
/// Here's a pretty thorough example that demonstrates why this might be useful
///
/// ```scss
///
/// @@include block('NEW') {
///     @include element(element) {
///         content: 'Basic Element';
///     }
///     @include element(element, blue) {
///         content: 'Blue Modifier';
///
///         @include element(child-of-blue) {
///             content: 'Child (in Blue Modified)';
///
///             @include element(deep-nested-element) {
///                 content: 'Deep Nested Element (RIGHT)';
///             }
///         }
///     }
/// }
///
/// .OLD {
///     &__element {
///         content: 'Basic Element';
///     }
///
///     &__element--blue {
///         content: 'Blue Modifier';
///     }
///
///     &__element--blue & {
///         &__child-of-blue {
///             content: 'Child in Blue Modified';
///
///             &__deep-nested-element {
///                 content: 'Deep Nested Element (WRONG)';
///             }
///         }
///
///         &__deep-nested-element {
///             content: 'Deep Nested Element (ALSO WRONG)';
///         }
///
///         &__child-of-blue & {
///             &__deep-nested-element {
///                 content: 'Deep Nested Element (THIS IS WRONG TOO)';
///             }
///         }
///     }
/// }
/// ```
///
/// And some more basic examples:
///
/// @param {string | list} $name - The element name string (if a list is provided the selector will
/// include a selector for each item in the list)
///
/// @param {string} $modifier [''] - An optional modifier string. Use this to indicate that the
/// block is in a modified state (e.g. selected).
///
/// @param {string} $pseudo [''] - An optional pseudo class that should be appended to the selector
/// (e.g. hover or after)
///
/// @example
///     .block { @include bem(element) { ... } }
///     // => .block__element { ... }
///
/// @example
///     .block { @include bem,(element, modifier) { ... } }
///     // => block__element--modifier { ... }
///
/// @example
///     .block { @include bem(element, $pseudo::after) { ... } }
///     // => block__element::after { ... }
///
/// @example
///     .block { @include bem,(element, modifier) { @include bem(child) { ... } } }
///     // => .block__element--modifier { .block__child { ... } }
///
/// @example
///     .block { @include bem((element1 element2)) { ... } }
///     // => .block__element1, block__element2 { ... }

@mixin bem($name, $modifier: '', $pseudo: '', $block: null) {
    $block: if-null($block, get-block-name(&));
    @include -bem-class($name, $modifier, $pseudo, #{$-block-prefix + $block + $-element-prefix}) {
        @content;
    }
}

/// Wrapper for bem mixin with more contextual naming

@mixin element($name, $modifier: '', $pseudo: '', $block: null) {
    @include bem($name, $modifier, $pseudo, $block) {
        @content;
    }
}
