//                                                                            _
//                                                                           | |
//  ___  __ _ ___ ___ ______ _ __ ___   __ _ _ __  ___ ______ _ __   _____  _| |_
// / __|/ _` / __/ __|______| '_ ` _ \ / _` | '_ \/ __|______| '_ \ / _ \ \/ / __|
// \__ \ (_| \__ \__ \      | | | | | | (_| | |_) \__ \      | | | |  __/>  <| |_
// |___/\__,_|___/___/      |_| |_| |_|\__,_| .__/|___/      |_| |_|\___/_/\_\\__|
//                                          | |
//                                          |_|

/////////////////////////////////////////////
///////////////// (private) /////////////////
/////////////////////////////////////////////

@function --smn-str-times($str, $n: 1) {
  $output: '';
  @while $n > 0 {
    $output: $output + $str;
    $n: $n - 1;
  }
  @return $output;
}

@function --smn-list-slice($list, $start: 1, $end: length($list), $separator: list-separator($list)) {
  $output: ();
  @if $start >= 1 and $end >= $start {
    @for $i from $start through $end {
      $output: append($output, nth($list, $i), $separator);
    }
  }
  @return $output;
}

@function --smn-is-map($map) {
  @return type-of($map) == "map" or (type-of($map) == "list" and length($map) == 0);
}

/////////////////////////////////////////////
///////////////// map-invert ////////////////
/////////////////////////////////////////////

@function map-invert($map) {
  @if not --smn-is-map($map) {
    @return throw-error('argument `$map` of map-invert($map) must be a map');
  }
  $result: ();
  @each $key, $value in $map {
    $result: map-merge($result, ($value: $key));
  }
  @return $result;
}

/////////////////////////////////////////////
////////////////// map-pick /////////////////
/////////////////////////////////////////////

@function map-pick($map, $keys...) {
  @if not --smn-is-map($map) {
    @return throw-error('argument `$map` of map-pick($map, $keys...) must be a map');
  }
  $nots: ();
  @each $key in map-keys($map) {
    @if not index($keys, $key) { $nots: append($nots, $key); }
  }
  @return map-remove($map, $nots...);
}

/////////////////////////////////////////////
////////////////// map-omit /////////////////
/////////////////////////////////////////////

@function map-omit($map, $keys...) {
  @if length($map) == 0 { @return (); }
  @if not --smn-is-map($map) {
    @return throw-error('argument `$map` of map-omit($map, $keys...) must be a map');
  }
  @return map-remove($map, $keys...);
}

/////////////////////////////////////////////
////////////////// map-get //////////////////
/////////////////////////////////////////////

$native-map-get: get-function('map-get');
@function native-map-get($map, $key) { @return call($native-map-get, $map, $key); }

@function map-get($map, $keys...) {
  @if length($map) == 0 { @return null; }
  @if not --smn-is-map($map) {
    @return throw-error('argument `$map` of `map-get($map, $keys...)` must be a map');
  }
  @if length($keys) == 0 {
    @return throw-error('function `map-get($map, $keys...)` is missing rest-argument `$keys`');
  }
  @each $key in $keys {
    // @if $map == null { @return $map; }
    @if not --smn-is-map($map) { @return null; }
    $map: native-map-get($map, $key);
  }
  @return $map;
}

/////////////////////////////////////////////
///////////////// map-merge /////////////////
/////////////////////////////////////////////

$native-map-merge: get-function('map-merge');
@function native-map-merge($map1, $map2) { @return call($native-map-merge, $map1, $map2); }

@function map-merge($map1, $keys-and-map2...) {
  @if not --smn-is-map($map1) {
    @return throw-error('argument `$map1` of `map-merge($map1, $keys-and-map2...)` must be a map');
  }
  @if length($keys-and-map2) == 0 {
    @return throw-error('rest-argument `$keys-and-map2...` of map-merge($map1, $keys-and-map2...) is missing');
  }
  $keys-and-map2-length: length($keys-and-map2);
  $key-length: $keys-and-map2-length - 1;
  $map2: nth($keys-and-map2, $keys-and-map2-length);
  @if not --smn-is-map($map2) {
    @return throw-error('argument `$map2` of `map-merge($map1, $keys-and-map2...)` must be a map');
  }
  @if $key-length == 0 {
    $map2: native-map-merge($map1, $map2);
  } @else {
    @for $i from 0 through $key-length {
      $new: if($i == 0, $map2, (nth($keys-and-map2, $keys-and-map2-length - $i): $map2));
      $tmp: --smn-list-slice($keys-and-map2, 1, $key-length - $i);
      $old: if($i == $key-length, $map1, map-get($map1, $tmp...) or ());
      $map2: if(type-of($old) == 'map', native-map-merge($old, $new), $new);
    }
  }
  @return $map2;
}

/////////////////////////////////////////////
////////////////// map-set //////////////////
/////////////////////////////////////////////

@function map-set($map, $keys-and-value...) {
  @if not --smn-is-map($map) {
    @return throw-error('argument `$map` of `map-set($map, $keys...)` is not a map');
  }
  @if length($keys-and-value) == 0 {
    @return throw-error('rest-argument `$keys-and-value` of `map-set($map, $keys-and-value...)` is missing');
  }
  $keys-and-value-length: length($keys-and-value);
  $key-length: $keys-and-value-length - 1;
  $value: nth($keys-and-value, $keys-and-value-length);
  @if $key-length == 0 {
    @return throw-error('at least one `$key` argument required in `map-set($map, $keys-and-value...)`');
  } @else {
    @for $i from 1 through $key-length {
      $new: (nth($keys-and-value, $keys-and-value-length - $i): $value);
      $old: if($i == $key-length, $map, map-get($map, --smn-list-slice($keys-and-value, 1, $key-length - $i)...) or ());
      $value: if(type-of($old) == 'map', native-map-merge($old, $new), $new);
    }
  }
  @return $value;
}

/////////////////////////////////////////////
//////////////// map-inspect ////////////////
/////////////////////////////////////////////

@function map-inspect($map, $tab: '  ', $level: 1) {
  @if type-of($map) != 'map' { @return '#{inspect($map)}'; }
  $length: length(map-keys($map));
  $indent: --smn-str-times($tab, $level);
  $output: '(\A' + $indent;
  $i: 1;
  @each $key, $value in $map {
    @if type-of($value) == 'map' {
      $output: $output + $key + ': ' + map-inspect($value, $tab, $level + 1);
    } @else {
      $output: $output + $key + ': ' + $value;
    }
    @if $i < $length {
      $output: $output + ',\A' + $indent;
    }
    $i: $i + 1;
  }
  $outdent: --smn-str-times($tab, $level - 1);
  @return unquote($output + '\A' + $outdent + ')');
}
