// Creates styles for a root element contained space
// -----------------------------------------------------------------------------
@use "sass:math";

@mixin container() {
  $latest-inset: 0em;

  @each $bp, $bp-width, $bp-inset in $g-breakpoints {
    // on each pass through, if we have not hit an "auto" inset then store
    // the inset value for use when we do have an auto inset
    @if $bp-inset != auto {
      $latest-inset: $bp-inset;
    }

    @if $bp == "base" {
      margin-left: $bp-inset;
      margin-right: $bp-inset;
    } @else if $bp == "max-body" {
      @media (min-width: bp(#{$bp})) {
        margin-left: auto !important;
        margin-right: auto !important;
        width: calc(100% - #{$latest-inset * 2});
        max-width: $g-max-body-width;
      }
    } @else {
      @media (min-width: bp(#{$bp})) {
        margin-left: $bp-inset;
        margin-right: $bp-inset;
      }
    }
  }
}

// Creates styles for the griddle visual overlay
// -----------------------------------------------------------------------------
@mixin overlay() {
  $latest-inset: 0em;
  @each $bp, $bp-width, $bp-inset in $g-breakpoints {
    // on each pass through, if we have not hit an "auto" inset then store
    // the inset value for use when we do have an auto inset
    @if $bp-inset != auto {
      $latest-inset: $bp-inset;
    }

    @if $bp == "base" {
      display: grid;
      grid-template-columns: $g-column-template;
      grid-column-gap: $g-column-gap;
      pointer-events: none;
      z-index: 200; // reasonably safe high z-index
      position: fixed;
      height: 100%;
      top: 0;
      bottom: 0;
      right: $bp-inset;
      left: $bp-inset;
    } @else if $bp == "max-body" {
      @media (min-width: bp(#{$bp})) {
        left: 50% !important;
        right: auto !important;
        transform: translateX(-50%);
        width: calc(100% - #{$latest-inset * 2});
        max-width: $g-max-body-width;
      }
    } @else {
      @media (min-width: bp(#{$bp})) {
        @if $bp-inset == auto {
          left: 50%;
          right: auto;
          transform: translateX(-50%);
          width: calc(100% - #{$latest-inset * 2});
          max-width: $g-max-body-width;
        } @else {
          left: $bp-inset;
          right: $bp-inset;
        }
      }
    }
  }
}

// Function to create a percentage width spanning n columns.
// -----------------------------------------------------------------------------
@function span($columns, $args...) {
  @return calcSpan($columns, $args, "percent");
}

// Function to create a decimal width spanning n columns.
// -----------------------------------------------------------------------------
@function span-decimal($columns, $args...) {
  @return calcSpan($columns, $args, "decimal");
}

// Shared logic to determine a span value for both percent and decimal spans
// -----------------------------------------------------------------------------
@function calcSpan($columns, $args, $returnValue) {
  $processedArgs: calculateSpanArgs($args);
  $gutters: 0;
  $base-column: 0;
  $base-gutter: 0;
  $modifier: 0;
  $extra_gutters: nth($processedArgs, 1);
  $context: nth($processedArgs, 2);

  // set variables to calculate the proper return value for either
  // span() or span-decimal()
  @if $returnValue == "percent" {
    $base-column: $g-column;
    $base-gutter: $g-gutter;
    $modifier: 1;
  } @else if $returnValue == "decimal" {
    $base-column: $g-column-decimal;
    $base-gutter: $g-gutter-decimal;
    $modifier: 1.0000000001; // coerce final value to float, but do not modify
  }

  // determine how many total gutters we need to account for
  @if $columns == 0 {
    $gutters: $extra_gutters;
  } @else {
    $gutters: $extra_gutters + ($columns - 1);
  }

  // calculate our possibly modified by a provided context
  $value: 0;
  @if $context != 0 {
    $value: math.div(
        ($columns * $base-column) + ($gutters * $base-gutter),
        $context
      ) *
      $modifier;
  } @else {
    $value: (($columns * $base-column) + ($gutters * $base-gutter)) * $modifier;
  }

  // return the correct value type
  @if $returnValue == "percent" {
    @if getUnit($value) == "%" {
      @return $value;
    } @else {
      @return percentage($value);
    }
  } @else if $returnValue == "decimal" {
    @return $value;
  }
}

// helper function to determine if span() args are gutters or context
// -----------------------------------------------------------------------------
@function calculateSpanArgs($args) {
  $extra_gutters: 0;
  $context: 0;

  @if length($args) {
    @for $i from 1 to length($args) + 1 {
      // if the first argument is not a % or a float then assume it's the extra gutters value
      // if it is a % value or a float then it is a context
      @if (
        $i ==
          1 and
          type-of(nth($args, $i)) ==
          "number" and
          getUnit(nth($args, $i)) !=
          "%" and
          round(nth($args, $i)) ==
          nth($args, $i)
      ) {
        $extra_gutters: nth($args, $i);
      } @else if ($i == 1 and type-of(nth($args, $i)) == "number") {
        $context: nth($args, $i);
      }
      // if there is a 2nd argument it should be the context
      @if ($i == 2 and type-of(nth($args, $i)) == "number") {
        $context: nth($args, $i);
      }
    }
  }

  @return $extra_gutters, $context;
}

// adds bleeds up to current bp inset or full viewport edge depnding on supplied direction.
// assumes that your element is already positioned on the edge of the grid
// within a container context
// -----------------------------------------------------------------------------
@mixin bleed(
  $direction: "left",
  $start-at: "base",
  $end-at: false,
  $unset-child: false
) {
  $target-bp: nested-map-get($g-breakpoints, 1, $start-at);
  $target-bp-width: false;
  $target-end-bp: false;
  $target-end-bp-width: false;
  $calc-direction: $direction;
  $hit-auto-bp: false; // track if we've hit the max-body breakpoint for full bleed options

  // if we have an end bp then define the targets
  @if ($end-at) {
    $target-end-bp: nested-map-get($g-breakpoints, 1, $end-at);
    @if ($target-end-bp) {
      $target-end-bp-width: nth($target-end-bp, 2);
    }
  }

  @if $direction == "left-full" {
    $calc-direction: "left";
  } @else if $direction == "right-full" {
    $calc-direction: "right";
  } @else if $direction == "both-full" {
    $calc-direction: "both";
  }

  @if ($target-bp) {
    $target-bp-width: nth($target-bp, 2);

    @each $bp, $bp-width, $bp-inset in $g-breakpoints {
      @if $bp == $start-at {
        @media (min-width: bp(#{$bp})) {
          body & {
            box-sizing: content-box;
          }
        }
      }

      // define the checks we need to do based on the existence of an end bp
      $bp-match: false;
      @if ($target-end-bp-width) {
        $bp-match: ($bp-width >= $target-bp-width) and
          ($bp-width < $target-end-bp-width);
      } @else {
        $bp-match: ($bp-width >= $target-bp-width);
      }

      // check if we're past the auto-bp
      @if $bp-inset ==
        "auto" and
        (
          $direction ==
            "left-full" or
            $direction ==
            "right-full" or
            $direction ==
            "both-full"
        )
      {
        $hit-auto-bp: true; // if we're full bleed, stop
      }

      @if $bp-match {
        // determine appropriate media query range
        $mq: false;
        @if ($target-end-bp-width) {
          $mq: "(min-width: #{bp($bp)}) and (max-width: #{$target-end-bp-width - 0.0625em})";
        } @else {
          $mq: "(min-width: #{bp($bp)})";
        }

        @if $bp-inset != "auto" and $hit-auto-bp == false {
          @media #{$mq} {
            body & {
              @if ($calc-direction == "both") {
                padding-right: $bp-inset;
                padding-left: $bp-inset;
                margin-right: -$bp-inset;
                margin-left: -$bp-inset;
              } @else {
                padding-#{$calc-direction}: $bp-inset;
                margin-#{$calc-direction}: -$bp-inset;
              }

              @if $unset-child {
                & > *:first-child {
                  width: calc(100% + #{$bp-inset});
                }
              }
            }
          }
        } @else {
          @media #{$mq} {
            body & {
              @if ($calc-direction == "both") {
                padding-left: calc(
                  (100vw - var(--scrollbar-width) - #{$g-max-body-width}) / 2
                );
                padding-right: calc(
                  (100vw - var(--scrollbar-width) - #{$g-max-body-width}) / 2
                );
                margin-left: calc(
                  (
                      (100vw - var(--scrollbar-width) - #{$g-max-body-width}) /
                        2
                    ) *
                    -1
                );
                margin-right: calc(
                  (
                      (100vw - var(--scrollbar-width) - #{$g-max-body-width}) /
                        2
                    ) *
                    -1
                );
              } @else {
                padding-#{$calc-direction}: calc(
                  (100vw - var(--scrollbar-width) - #{$g-max-body-width}) / 2
                );
                margin-#{$calc-direction}: calc(
                  (
                      (100vw - var(--scrollbar-width) - #{$g-max-body-width}) /
                        2
                    ) *
                    -1
                );
              }
            }
          }
        }
      }
    }
  }
}

// Generates a CSS grid of equal-width elements.
// Useful for "card grids" across a series of breakpoints.
// -----------------------------------------------------------------------------
@mixin css-grid-grid(
  $layout: (
    // list of grid configuration items: 'start, end (optional), column count, target grid element width, target grid element column spacing, target grid element row spacing
    // EG:
    // 'm' 'l' 2 span(6) span(0, 1) 1em,
    // 'l' 3 span(3) span(1, 2) 2em,,,,,
  ),
  $align: start,
  $justify: center,
  $debug: false
) {
  display: grid;
  justify-items: #{$justify};
  align-items: #{$align};
  justify-content: #{$justify};
  align-content: #{$align};
  width: 100%;

  @each $entry in $layout {
    // set up base variable holders with default values
    $start: nth($entry, 1);
    $end: false;
    $count: 1;
    $width: 100%;
    $col-gap: false;
    $row-gap: false;
    $has-end: true;
    $mq: false;

    // check if we have an ending bp declarud
    @if (type-of(nth($entry, 2)) != "string") {
      $has-end: false;
    }

    // set the actual values depending on if we were provided
    // an end value
    @if ($has-end) {
      $end: if(length($entry) >= 2, nth($entry, 2), $end);
      $count: if(length($entry) >= 3, nth($entry, 3), $count);
      $width: if(length($entry) >= 4, nth($entry, 4), $width);
      $col-gap: if(length($entry) >= 5, nth($entry, 5), $col-gap);
      $row-gap: if(length($entry) >= 6, nth($entry, 6), $row-gap);
      $mq: "(min-width: #{bp($start)}) and (max-width: #{bp($end) - 0.0625em})";
    } @else {
      $count: if(length($entry) >= 2, nth($entry, 2), $count);
      $width: if(length($entry) >= 3, nth($entry, 3), $width);
      $col-gap: if(length($entry) >= 4, nth($entry, 4), $col-gap);
      $row-gap: if(length($entry) >= 5, nth($entry, 5), $row-gap);
      $mq: "(min-width: #{bp($start)})";
    }

    @media #{$mq} {
      grid-template-columns: repeat($count, $width);
      @if ($row-gap) {
        row-gap: $row-gap;
      }
      @if ($col-gap) {
        column-gap: $col-gap;
      }
    }
  }

  @if ($debug) {
    & > * {
      width: 100%;
      outline: 1px solid red;
    }
  }
}

// Generates a flex grid of equal-width elements.
// Useful for "card grids" across a series of breakpoints.
// -----------------------------------------------------------------------------
@mixin grid(
  $layout: false,
  // ^ list of grid configuration items: 'start, column count, target grid element width, target grid element column spacing, target grid element row spacing
  // EG:
  // (
  //   'base' 2 span(6) span(0, 1) 1em,
  //   'm' 3 span(3) span(1, 2) 2em,
  // )
  $align: flex-start,
  $justify: center,
  $debug: false
) {
  display: flex;
  flex-wrap: wrap;
  flex-direction: row;
  justify-content: #{$justify};
  align-items: #{$align};

  @if (($layout != false) and (length($layout) > 0)) {
    $list-index: 1;

    @each $entry in $layout {
      // set a default bp that may be overriden.
      $start: nth($entry, 1);
      $mq: "(min-width: #{bp($start)})";

      // check if we have a "next layout" and grab the starting bp
      // in order to isolate styles to a specific range. The rules defined in
      // the last layout entry will continue indefinitely.
      @if (length($layout) > $list-index) {
        $next-layout: nth($layout, $list-index + 1);
        $end: nth($next-layout, 1);
        $mq: "(min-width: #{bp($start)}) and (max-width: #{bp($end) - 0.0625em})";
      }

      // set up base variable holders with default values
      $count: if(length($entry) >= 2, nth($entry, 2), 1);
      $width: if(length($entry) >= 3, nth($entry, 3), 100%);
      $col-gap: if(length($entry) >= 4, nth($entry, 4), false);
      $row-gap: if(length($entry) >= 5, nth($entry, 5), false);
      $max-width: ($count * $width) +
        (($count - 1) * if($col-gap, $col-gap, 0));

      @media #{$mq} {
        max-width: $max-width;

        @if ($row-gap) {
          margin-bottom: -$row-gap;
        }

        & > * {
          width: math.div($width, $max-width) * 100%;

          @if ($row-gap) {
            margin-bottom: $row-gap;
          }
          @if ($col-gap) {
            margin-right: (math.div($col-gap, $max-width) * 0.5) * 100%;
            margin-left: (math.div($col-gap, $max-width) * 0.5) * 100%;
          }

          &:nth-child(#{$count}n),
          &:last-child {
            margin-right: 0;
          }
          &:nth-child(#{$count}n + 1) {
            margin-left: 0;
          }
        }
      }

      // increment the list index for fetching the next bp start
      $list-index: $list-index + 1;
    }
  } @else {
    // default fallback of full width if no layouts were supplied
    & > * {
      width: 100%;
    }
  }

  @if ($debug) {
    outline: 2px dashed blue;

    & > * {
      outline: 1px solid red;
    }
  }
}
