@use "../config";
@use "helpers";

@use 'sass:map';
@use 'sass:list';
@use 'sass:string';
@use 'sass:meta';
@use 'sass:color';
@use 'sass:math';

$breakpoints: config.$breakpoints;

@if $breakpoints == null {
  @warn '🜍 Undefined breakpoints! Please check your `_config.scss`.';
} 
@else {
  @if not map.has-key($breakpoints, '') {
    $breakpoints: helpers.breakpoints(config.$breakpoints);
  }
}

/*
 * Generates media query via provided predefined breakpoint map item without
 * @media. Used by `breakpoint` mixin.
 * @see utils/_config.scss $breakpoints. $breakpoints is a globally defined var.
 *
 * @param $size: breakpoint map item name ('', sm, md, lg, xl, xxl)
 * @param $explicit: (boolean | optional) for generating min-max queries
 *
 * @return (string) e.g. `only screen and (min-width: 1366px)` or `only screen and (min-width: 1366px) and (max-width: 1679px)`
 */
@function generate-breakpoint($size, $explicit: false) {
  $query: 'only screen';
  $breakpoint: map.get($breakpoints, $size);

  @if $breakpoint != null {
    @if list.nth($breakpoint, 1) {
      $query: '#{$query} and (min-width: #{list.nth($breakpoint, 1)})';
    }

    @if $explicit {
      /* stylelint-disable-next-line max-nesting-depth */
      @if list.nth($breakpoint, 2) {
        $query: '#{$query} and (max-width: #{list.nth($breakpoint, 2)})';
      }
    }
  }

  @else {
    @warn '🜍 Undefined breakpoint: `#{$size}`! Please check your `_config.scss`.';
  }

  @return $query;
}

/*
 * Mixin for generating media queries. Uses `generate-breakpoint` function
 * @see utils/_config.scss $breakpoints. $breakpoints is a globally defined var.
 *
 * @param $size: (string) breakpoint map item name ('', sm, md, lg, xl, xxl). Size can be `min` or `minimal` which renders as $size: '' and $explicit: true
 * @param $explicit: (boolean | optional) for generating min-max queries
 */
@mixin breakpoint($size, $explicit: false) {
  @if $size == min or $size == minimal {
    $size: '';
    $explicit: true;
  }

  @if not $explicit {
    @if $size == '' or $size == min or $size == minimal {
      @content;
    }

    @else {
      @media #{generate-breakpoint($size, $explicit)} {
        @content;
      }
    }
  }

  @else {
    @media #{generate-breakpoint($size, $explicit)} {
      @content;
    }
  }
}

@mixin generic-utility-class-generator($pref, $property, $b-name, $key, $value, $unit, $suff, $var: false) {
  $alias: '';
  $condition: ''; // if property is not the same as condition, it will be ignored
  $unit-copy: null;

  @if meta.type-of($value) == 'map' {
    @if map.has-key($value, 'prefix') {
      $pref: map.get($value, 'prefix');
    }
    @if map.has-key($value, 'suffix') {
      $suff: map.get($value, 'suffix');
    }
    @if map.has-key($value, 'alias') {
      $alias: map.get($value, 'alias');
      $alias: ', .' + $alias;
    }
    @if map.has-key($value, 'unit') {
      $unit: map.get($value, 'unit');
      $unit-copy: map.get($value, 'unit');
    }
    @if map.has-key($value, 'condition') {
      $condition: map.get($value, 'condition');
    }
    $value: map.get($value, 'value');
  }

  @if meta.type-of($value) == 'string' {
    @if $unit-copy == null {
      $unit: '';
    }
  }
  
  $gen-pref: helpers.trim-end($pref, '-'); // in case we want prefixes to have `-` in front of orientation
  @if $b-name {
    $b-name: -#{$b-name};
  }

  @if meta.type-of($key) == 'map' {
    $key: map.get($key, 'value');
  }
  $key: '' + $key; // cause values create dart-sass warns

  @if $condition == '' or $condition == $property {
    .#{$gen-pref}#{$b-name}-#{$key}#{$suff}#{$alias} {
      @if $var {
        @if meta.type-of($var) == 'string' {
          #{$property}: var(--#{$var}-#{$key}#{$suff});
        } @else {
          #{$property}: var(--#{$property}-#{$key}#{$suff});
        }
      } @else {
        #{$property}: #{$value}#{$unit};
      }
    }
  }
}

@mixin orientated-utility-class-generator($pref, $property, $b-name, $key, $value, $unit, $suff, $var: false) {
  $alias: '';
  $condition: ''; // if property is not the same as condition, it will be ignored
  $unit-copy: null;
  $suff-copy: null;

  @if meta.type-of($value) == 'map' {
    @if map.has-key($value, 'prefix') {
      $pref: map.get($value, 'prefix');
    }
    @if map.has-key($value, 'suffix') {
      $suff: map.get($value, 'suffix');
    }
    @if map.has-key($value, 'alias') {
      $alias: map.get($value, 'alias');
      $alias: ', .' + $alias;
    }
    @if map.has-key($value, 'unit') {
      $unit: map.get($value, 'unit');
      $unit-copy: map.get($value, 'unit');
    }
    @if map.has-key($value, 'condition') {
      $condition: map.get($value, 'condition');
    }
    $value: map.get($value, 'value');
  }

  @if meta.type-of($value) == 'string' {
    @if $unit-copy == null {
      $unit: '';
    }
  }

  $gen-pref: helpers.trim-end($pref, '-'); // in case we want prefixes to have `-` in front of orientation
  @if $b-name {
    $b-name: -#{$b-name};
  }

  @if meta.type-of($key) == 'map' {
    $key: map.get($key, 'value');
  }
  $key: '' + $key; // cause values create dart-sass warns

  @if $condition == '' or $condition == $property {
    @each $o-name, $o-values in config.$orientations {
      .#{$pref}#{$o-name}#{$b-name}-#{$key}#{$suff} {
        @each $o-value in $o-values {
          @if $var {
            @if meta.type-of($var) == 'string' {
              #{$property}-#{$o-value}: var(--#{$var}-#{$o-name}#{$b-name}-#{$key}#{$suff}, var(--#{$var}-#{$key}#{$suff}));
            } @else {
              #{$property}-#{$o-value}: var(--#{$property}-#{$o-name}#{$b-name}-#{$key}#{$suff}, var(--#{$property}-#{$key}#{$suff}));
            }
          } @else {
            #{$property}-#{$o-value}: #{$value}#{$unit};
          }
        }
      }
    }
  }
}

@mixin generic-and-orientated-utility-class-generator(
  $pref,
  $property,
  $b-name,
  $values,
  $unit: '',
  $suff: '',
  $orientations: false,
  $var: false) {

  @if meta.type-of($values) == 'map' {
    @each $key, $value in $values {
      @include generic-utility-class-generator($pref, $property, $b-name, $key, $value, $unit, $suff, $var);

      @if $orientations {
        @include orientated-utility-class-generator($pref, $property, $b-name, $key, $value, $unit, $suff, $var);
      }
    }
  } @else {
    @each $value in $values {
      @include generic-utility-class-generator($pref, $property, $b-name, $value, $value, $unit, $suff, $var);

      @if $orientations {
        @include orientated-utility-class-generator($pref, $property, $b-name, $value, $value, $unit, $suff, $var);
      }
    }
  }
}

/*
 * Mixin for generating generic utility classes. E.g. `.t-32` => `top: 32px`
 * Uses `generate-breakpoint` function.
 * @see utils/_config.scss $breakpoints. $breakpoints is a globally defined var.
 * @see utils/_config.scss $orientations. $orientations is a globally defined
 * var.
 *
 * @param $pref: (string) base class name
 * @param $property: (string) css property name
 * @param $values: (list | map) list or a map of property values
 * @param $unit: (string) value unit
 * @param $suff: (string) generated class name suffix
 * @param $orientations: (map | optional) map of orientation property variations
 * @param $responsive: (boolean | optional) whether to generate responsive classes
 */
@mixin utility-class-generator(
  $pref,
  $property: null,
  $values: null,
  $unit: '',
  $suff: '',
  $orientations: false,
  $responsive: true,
  $var: false ) {

  @if meta.type-of($pref) == 'map' {
    $property: map.get($pref, 'property');
    $values: map.get($pref, 'values');
    @if map.has-key($pref, 'unit') {
      $unit: map.get($pref, 'unit');
    } @else {
      $unit: '';
    }
    @if map.has-key($pref, 'suffix') {
      $suff: map.get($pref, 'suffix');
    } @else {
      $suff: '';
    }
    @if map.has-key($pref, 'orientations') {
      $orientations: map.get($pref, 'orientations');
    } @else {
      $orientations: false;
    }
    @if map.has-key($pref, 'responsive') {
      $responsive: map.get($pref, 'responsive');
    } @else {
      $responsive: true;
    }
    @if map.has-key($pref, 'variable') {
      $var: map.get($pref, 'variable');
    } @else {
      $var: false;
    }
    $pref: map.get($pref, 'prefix'); 
  }

  @include generic-and-orientated-utility-class-generator($pref, $property, null, $values, $unit, $suff, $orientations, $var);

  // responsive variations
  @if $responsive {
    @each $b-name, $b-value in $breakpoints {
      @if $b-name != '' and $b-name != 'min' and $b-name != 'minimal' {
        @media #{generate-breakpoint($b-name)} {
          @include generic-and-orientated-utility-class-generator($pref, $property, $b-name, $values, $unit, $suff, $orientations, $var);
        }
      }
    }
  }
}

@function color-palette-generator($color-map, $color-name, $color-value, $mod: 10%) {
  $grade: 500;
  $combined-name: #{$color-name}-#{$grade};
  $color-map: map.merge($color-map, ($combined-name: $color-value));

  @for $i from 1 through 4 {
    $count: 5 + $i;
    $grade: $count * 100;
    $percentage: $i * $mod * 2;
    $combined-name: #{$color-name}-#{$grade};
    $color-lightened: color.mix(#000000, $color-value, $percentage);

    $color-map: map.merge($color-map, ($combined-name: $color-lightened));
  }

  @for $i from 1 through 4 {
    $grade: $i * 100;
    $percentage: $i * $mod * 2;
    $combined-name: #{$color-name}-#{$grade};
    $color-lightened: color.mix($color-value, #ffffff, $percentage);

    $color-map: map.merge($color-map, ($combined-name: $color-lightened));
  }

  @return $color-map;
}

@function generate-color-palettes($color-map, $palette-map) {
  @if $palette-map == null {
    @return $color-map;
  }
  
  @each $name, $value in $palette-map {
    @if meta.type-of($value) == 'color' {
      $color-map: color-palette-generator($color-map, $name, $value);
    } @else {
      $color-map: color-palette-generator($color-map, $name, list.nth($value, 1), list.nth($value, 2));
    }
  }

  @return $color-map;
}

@mixin css-variable-generator($pref, $map) {
  @each $name, $value in $map {
    $name: '' + $name;
    --#{$pref}-#{$name}: #{$value};
  }
}

@mixin grid-column-generator($columns, $responsive: true) {
  $base: math.div(100, $columns);
  $unit: '%';

  @for $i from 1 through $columns {
    .col-#{$i} {
      $width: $base * $i;
      width: #{$width}#{$unit};
    }
  }

  @if $responsive {
    @each $b-name, $b-value in $breakpoints {
      @if $b-name != '' and $b-name != 'min' and $b-name != 'minimal' {
        @media #{generate-breakpoint($b-name)} {
          @for $i from 1 through $columns {
            .col-#{$b-name}-#{$i} {
              $width: $base * $i;
              width: #{$width}#{$unit};
            }
          }
        }
      }
    }
  }
}

@function get-min-breakpoint-value() {
  $value: 0;

  @each $b-name, $b-value in $breakpoints {
    @if $b-name == '' {
      $value: list.nth($b-value, 2);
    }
  }

  @return $value;
}

@function get-max-breakpoint-value() {
  $value: 0;

  @each $b-name, $b-value in $breakpoints {
    @if list.nth($b-value, 2) == false {
      $value: list.nth($b-value, 1);
    }
  }

  @return $value;
}

@mixin grid-column-max-generator($columns) {
  $base: math.div(100, $columns);
  $max-breakpoint-value: get-max-breakpoint-value();

  @for $i from 1 through $columns - 1 {
    .col-#{$i}-max {
      $width: $base * $i;
      max-width: ($max-breakpoint-value - config.$container-offset-mobile * 2 + config.$grid-gutter-mobile) * math.div($width, 100);

      @include breakpoint(config.$container-breakpoint) {
        max-width: ($max-breakpoint-value - config.$container-offset * 2 + config.$grid-gutter) * math.div($width, 100);
      }
    }
  }
}

/* stylelint-disable max-nesting-depth */
@mixin grid-offset-generator($columns, $responsive: true) {
  $base: math.div(100, $columns);
  $unit: '%';

  @for $i from 0 through $columns - 1 {
    .col-offset-#{$i} {
      $width: $base * $i;
      margin-left: #{$width}#{$unit};

      .grid-reverse & {
        margin-left: auto;
        margin-right: #{$width}#{$unit};
      }
    }
  }

  @if $responsive {
    @each $b-name, $b-value in $breakpoints {
      @if $b-name != '' and $b-name != 'min' and $b-name != 'minimal' {
        @media #{generate-breakpoint($b-name)} {
          @for $i from 0 through $columns - 1 {
            .col-offset-#{$b-name}-#{$i} {
              $width: $base * $i;
              margin-left: #{$width}#{$unit};

              .grid-reverse & {
                margin-left: auto;
                margin-right: #{$width}#{$unit};
              }
            }
          }
        }
      }
    }
  }
}
/* stylelint-enable */

@mixin typography-rules-generator($font-size, $letter-spacing: null, $line-height: null, $font-weight: null, $text-transform: null, $font-family: null) {

  @if meta.type-of($font-size) == 'list' {
    @if list.length($font-size) > 1 {
      $letter-spacing: list.nth($font-size, 2);
    }
  
    @if list.length($font-size) > 2 {
      $line-height: list.nth($font-size, 3);
    }
  
    @if list.length($font-size) > 3 {
      $font-weight: list.nth($font-size, 4);
    }
  
    @if list.length($font-size) > 4 {
      $text-transform: list.nth($font-size, 5);
    }

    @if list.length($font-size) > 5 {
      $font-family: list.nth($font-size, 6);
    }

    @if list.length($font-size) > 0 {
      $font-size: list.nth($font-size, 1);
    }
  }

  @if meta.type-of($font-size) == 'map' {
    @if map.has-key($font-size, letter-spacing) {
      $letter-spacing: map.get($font-size, letter-spacing);
    }

    @if map.has-key($font-size, line-height) {
      $line-height: map.get($font-size, line-height);
    }

    @if map.has-key($font-size, font-weight) {
      $font-weight: map.get($font-size, font-weight);
    }

    @if map.has-key($font-size, text-transform) {
      $text-transform: map.get($font-size, text-transform);
    }

    @if map.has-key($font-size, font-family) {
      $font-family: map.get($font-size, font-family);
    }

    @if map.has-key($font-size, font-size) {
      $font-size: map.get($font-size, font-size);
    }
  }

  @if $font-size {
    font-size: helpers.toRem($font-size);
  }

  @if $letter-spacing {
    letter-spacing: helpers.toRem($letter-spacing);
  }

  @if $line-height {
    line-height: helpers.toRem($line-height);
  }

  @if $font-weight {
    font-weight: $font-weight;
  }

  @if $text-transform {
    text-transform: $text-transform;
  }

  @if $font-family {
    font-family: $font-family;
  }
}

@mixin typography-generator($map) {
  @each $key, $value in $map {
    #{$key} {
      @if meta.type-of($value) == 'map' 
      and not map.has-key($value, font-size) {
        @if map.has-key($value, mobile) or map.has-key($value, base) {
          @include typography-rules-generator(map.get($value, mobile));
        }

        @each $b-name, $b-value in $breakpoints {
          @if $b-name == config.$container-breakpoint and map.has-key($value, desktop) {
            @include breakpoint($b-name) {
              @include typography-rules-generator(map.get($value, desktop));
            }
          } 
          @else {
            @if map.has-key($value, $b-name) {
              @include breakpoint($b-name) {
                @include typography-rules-generator(map.get($value, $b-name));
              }
            }
          }
        }
      }
      @else {
        @include typography-rules-generator($value);
      }
    }
  }
}

@function get-transition-fn($easing) {
  @if map.has-key(config.$custom-easings, $easing) {
    $easing: map.get(config.$custom-easings, $easing);
  }

  @return $easing;
}

@function generate-transition($properties, $durations: config.$default-transition-duration, $easings: config.$default-transition-easing) {
  $transition: '';

  @if meta.type-of($properties) == 'list' {
    $properties-ln: list.length($properties);
    $i: 0;
    @each $property in $properties {
      $i: $i+1;
      $duration: $durations;
      @if meta.type-of($durations) == 'list' {
        $duration: list.nth($durations, $i);
      }

      $easing: $easings;
      @if meta.type-of($easings) == 'list' {
        $easing: list.nth($easings, $i);
      }
      $easing: get-transition-fn($easing);

      $comma: ',';
      @if $i == $properties-ln {
        $comma: '';
      }

      $transition: '#{$transition} #{$property} #{$duration} #{$easing}#{$comma}';
    }
  }
  @else {
    $easings: get-transition-fn($easings);
    $transition: '#{$properties} #{$durations} #{$easings}';
  }

  @return string.unquote($transition);
}

@mixin transition($properties, $durations: config.$default-transition-duration, $easings: config.$default-transition-easing) {
  transition: generate-transition($properties, $durations, $easings);
}
