/* stylelint-disable max-nesting-depth */

@use "sass:map";
@use "sass:meta";
@use "sass:string";
@use "sass:list";

@use "../settings" as *;
@use "../properties" as *;
@use "../functions" as *;
@use "../variables/separator" as *;
@use "./helpers" as *;

/*
----------------------------------------
@render-pseudoclass
----------------------------------------
Build a pseucoclass utiliy from values
calculated in the @render-utilities-in
loop
----------------------------------------
*/

@mixin render-pseudoclass(
  $utility,
  $pseudoclass,
  $selector,
  $property,
  $value,
  $media-prefix
) {
  $important: if($utilities-use-important, " !important", null);
  $this-mq: null;

  .#{$media-prefix}#{$pseudoclass}#{$separator}#{ns("utility")}#{$selector}:#{$pseudoclass} {
    @each $this-property in $property {
      #{$this-property}: string.unquote("#{$value}#{$important}");
    }
  }
}

// utility-feature? utility-property
@mixin add-utility-declaration($declaration, $utility-type, $important) {
  @each $ext-prop, $ext-value in map.get($declaration, $utility-type) {
    #{$ext-prop}: string.unquote("#{$ext-value}#{$important}");
  }
}

/*
----------------------------------------
@render-utility
----------------------------------------
Build a utility from values calculated
in the @render-utilities-in loop
----------------------------------------
TODO: Determine the proper use of
unquote() in the following. Changed to
account for a 'interpolation near
operators will be simplified in a
future version of Sass' warning.
----------------------------------------
*/

@mixin render-utility(
  $utility,
  $selector,
  $property,
  $value,
  $val-props,
  $media-key
) {
  $important: if($utilities-use-important, " !important", null);
  $media-prefix: null;
  $value-is-map: if(meta.type-of($val-props) == "map", true, false);

  @if $media-key {
    $media-prefix: #{$media-key}#{$separator};
  }

  .#{$media-prefix}#{ns("utility")}#{$selector} {
    @if $value-is-map and map.has-key($val-props, extend) {
      @include add-utility-declaration($val-props, extend, $important);
    }

    @if $value-is-map and map.has-key($val-props, extends) {
      @extend %#{map.get($val-props, extends)};
    }

    @each $this-property in $property {
      #{$this-property}: string.unquote("#{$value}#{$important}");
    }

    @if map.has-key($utility, extend) {
      @include add-utility-declaration($utility, extend, $important);
    }
  }

  // Add the pseudoclass variants, if applicable

  @if map-deep-get($utility, settings, hover) {
    @include render-pseudoclass(
      $utility,
      hover,
      $selector,
      $property,
      $value,
      $media-prefix
    );
  }

  @if map-deep-get($utility, settings, active) {
    @include render-pseudoclass(
      $utility,
      active,
      $selector,
      $property,
      $value,
      $media-prefix
    );
  }

  @if map-deep-get($utility, settings, visited) {
    @include render-pseudoclass(
      $utility,
      visited,
      $selector,
      $property,
      $value,
      $media-prefix
    );
  }

  @if map-deep-get($utility, settings, focus) {
    @include render-pseudoclass(
      $utility,
      focus,
      $selector,
      $property,
      $value,
      $media-prefix
    );
  }
}

/*
----------------------------------------
@render-utilities-in
----------------------------------------
The master loop that sets the building
blocks of utilities from the values
in individual rule settings and loops
through all possible variants
----------------------------------------
*/

@mixin these-utilities($utilities, $media-key: false) {
  // loop through the $utilities
  @each $utility-name, $utility in $utilities {
    // Check to see if the utility is in the output uselist
    @if ($output-these-utilities == "default") or
      list.index($output-these-utilities, $utility-name)
    {
      // Only do this if the the utility is meant to output
      @if not($media-key) or
        ($media-key and map-deep-get($utility, settings, responsive))
      {
        @if map-deep-get($utility, settings, output) {
          // set intital variants
          // $property-default is a single value for all these utilities

          $base-props: null;
          $modifier: null;
          $selector: null;
          $property-default: map.get($utility, property);
          $property: null;
          $value: null;
          $our-modifiers: ();
          $b: null;
          $v: null;
          $mv: null;
          $val-props: ();
          $no-value: false;

          $b: map.get($utility, base);

          // Each utility rule takes a value, so let's start here
          // and begin building.

          // -------- For each value in utility.values ----------

          @each $val-key, $val-value in map.get($utility, values) {
            // If $val-value == null, or if $val-value is a map and
            // the content key or the dependency key has a null value
            // set $val-value to `false`...

            @if meta.type-of($val-value) == "map" {
              @if not map.get($val-value, content) {
                $val-value: false;
              } @else if
                map.has-key($val-value, dependency) and not
                map.get($val-value, dependency)
              {
                $val-value: false;
              }
            }

            // ...so we can skip building this rule altogether.
            // So, if $val-value is _not_ false...

            @if $val-value {
              // Set the value of our rule.
              // If its a map, use val-value.content.

              $val-slug: if(
                meta.type-of($val-value) == "map",
                map.get($val-value, "slug"),
                $val-key
              );

              $value: if(
                meta.type-of($val-value) == "map",
                map.get($val-value, "content"),
                $val-value
              );

              @if $val-slug == "" or smart-quote($val-slug) == "noValue" {
                $no-value: true;
              }

              // Add any appended values...

              @if map.get($utility, valueAppend) {
                $value: $value + map.get($utility, valueAppend);
              }

              // ...or prepended values.

              @if map.get($utility, valuePrepend) {
                $value: map.get($utility, valuePrepend) + $value;
              }

              // And we'll set the $v as $val-slug for use in
              // constructing the selector (.$b-$m-$v).

              $v: $val-slug;

              // -------- Start of Modifiers ----------

              // Now we'll check for modifiers and loop through them
              // to get the props we need to build our rule.

              // Modifiers are held in a MAP,
              // where each individual modifer has the keypair
              // [slug]:[value]

              // So, check for modifiers.

              @if map.get($utility, modifiers) {
                // If there are modifiers, capture them as $our-modifiers.

                $our-modifiers: map.get($utility, modifiers);
              } @else {
                // If there aren't, build a dummy so we can keep
                // all our build in the same loop.

                $our-modifiers: (
                  "slug": null,
                );
              }

              // OK! C'mon, let's loop!
              // https://www.youtube.com/watch?v=X9i2i07wPUw

              // -------- For each modifier in $our-modifiers ----------

              @each $mod-key, $mod-val in $our-modifiers {
                $property: if(
                  $mod-val == null or $mod-val == "",
                  $property-default,
                  multi-cat($property-default, $mod-val)
                );

                // Now we go through to set the $selector.

                // If mod-props.slug is noModifier...

                @if $mod-key ==
                  "" or
                  $mod-key ==
                  slug or
                  smart-quote($mod-key) ==
                  "noModifier"
                {
                  // First, we can test to see if the base $b is null

                  @if not $b {
                    // If it _is_ null, the rule's selector is $v.

                    $selector: $v;

                    // if the value is noValue ('')
                  } @else if $no-value {
                    // selector is the base only

                    $selector: $b;
                  } @else {
                    // otherwise, selctor is joined with a hyphen.

                    $selector: $b + "-" + $v;

                    // Nice! We just took care of the non-modifier cases!
                  }
                }

                // If there _is_ a modifier...

                @else {
                  $mv: if($no-value, $mod-key, $mod-key + "-" + $v);

                  // Once we have $mv, test for $b
                  // and build the selector as before.

                  $selector: if($b == null, $mv, $b + "-" + $mv);
                }

                // finished setting modifier vars

                // Hey. Did we just finish $selector?
                // And do we also have $property and $value?
                // We do?!?!?! We do!

                // FINALLY, 'BUILD THE RULE, MAX!'
                // https://www.youtube.com/watch?v=R3Igz5SfBCE

                @include render-utility(
                  $utility,
                  $selector,
                  $property,
                  $value,
                  $val-value,
                  $media-key
                );
              } // end the modifier loop
            } // end the null value conditional
          } // end the value loop
        } // end the output conditional
      }
    } // end the uselist conditional
  } // end the utility loop
  // (ﾉ◕ヮ◕)ﾉ*:･ﾟ✧
}

@mixin render-utilities-in($utilities) {
  @include these-utilities($utilities);

  $our-breakpoints: map-deep-get($system-properties, breakpoints, standard);
  $custom-breakpoints: map-deep-get($system-properties, breakpoints, extended);
  $all-breakpoints: map-collect($our-breakpoints, $custom-breakpoints);
  @each $media-key, $media-value in $all-breakpoints {
    @if (map.get($theme-utility-breakpoints-complete, $media-key)) {
      @include at-media($media-key) {
        @include these-utilities($utilities, $media-key);
      }
    }
  }
}

/* stylelint-enable */
