@function __base-map-iteratee($value, $key, $collection) {
  $result: __this("result");
  $iteratee: __this("iteratee");

  $result: append($result, __exec($iteratee, $value, $key, $collection));
  $_: __this("result", $result);

  @return $result;
}

@function __base-map($collection, $iteratee) {
  $_: __scope(
    (
      "result": (),
      "iteratee": $iteratee
    )
  );
  $_: __base-each($collection, "__base-map-iteratee");
  $result: __this("result");
  $_: __scope(false);

  @return $result;
}

@function __list-map($list, $iteratee) {
  $index: 1;
  $length: length($list);
  $result-list: ();

  @while ($index <= $length) {
    $value: nth($list, $index);
    $result-list: append(
      $result-list,
      __exec($iteratee, $value, $index, $list)
    );
    $index: $index + 1;
  }

  @return $result-list;
}

@function __map($collection, $iteratee: "__identity", $this-arg: null) {
  @if not __is-iterable($collection) {
    $collection: ();
  }

  $collection: if(
    __is-string($collection),
    __to-list($collection),
    $collection
  );
  $function: if(__is-list($collection), "__list-map", "__base-map");
  $iteratee: __get-callback($iteratee, $this-arg, 3);

  @return __exec($function, $collection, $iteratee);
}

@function __map-values-iteratee($value, $key, $map) {
  $result: __this("result");
  $iteratee: __this("iteratee");
  $result: __set($result, $key, __exec($iteratee, $value, $key, $map));
  $_: __this("result", $result);

  @return true;
}

@function __map-values($map, $iteratee: __identity, $this-arg: null) {
  $result: ();
  $iteratee: __get-callback($iteratee, $this-arg, 3);
  $_: __scope(
    (
      "result": $result,
      "iteratee": $iteratee
    )
  );
  $map-values-iteratee: __bind("__map-values-iteratee");
  $_: __base-for-own($map, $map-values-iteratee);
  $result: __this("result");
  $_: __scope(false);

  @return $result;
}

@function __map-keys-iteratee($value, $key, $map) {
  $result: __this("result");
  $iteratee: __this("iteratee");
  $iteratee-value: if(__this("with-keys"), $key, $value);

  $result: __set(
    $result,
    __exec($iteratee, $iteratee-value, $key, $map),
    $value
  );
  $_: __this("result", $result);

  @return $result;
}

@function __map-keys(
  $map,
  $iteratee: __identity,
  $this-arg: null,
  $with-keys: false
) {
  $result: ();
  $iteratee: __get-callback($iteratee, $this-arg, 3);
  $_: __scope(
    (
      "result": $result,
      "iteratee": $iteratee,
      "with-keys": $with-keys
    )
  );
  $map-keys-iteratee: __bind("__map-keys-iteratee");

  $_: __base-for-own($map, $map-keys-iteratee);
  $result: __this("result");
  $_: __scope(false);

  @return $result;
}

/// Creates a list of values by running each element in `$collection` through
/// `$iteratee`. The `$iteratee` is bound to `$this-arg` and invoked with three
/// arguments; (value, index|key, collection).
///
/// If a property name is provided for `$predicate` the created `_property`
/// style callback returns the property value of the given element.
///
/// If a value is also provided for `$this-arg` the created `_matches-property`
/// style callback returns `true` for elements that have a matching property
/// value, else `false`.
///
/// If a map is provided for `$predicate` the created `_matches` style
/// callback returns `true` for elements that have the properties of the given
/// object, else `false`.
///
/// Many lodash methods are guarded to work as interatees for methods like
/// `_every`, `_filter`, `_map`, `_map-values`, `_reject`, and `_some`.
///
/// @access public
/// @group Collection
/// @param {List|Map|string} $collection The collection to iterate over.
/// @param {Function|Map|string} $iteratee [_identity] - The function invoked
///  per iteration.
///  create a `_property` or `_matches` style callback respectively.
/// @param {*} $this-arg [null] - The `_this` binding of `$iteratee`.
/// @returns {List} Returns the new mapped list.
/// @example scss
///     @function times-three($n, $args...) {
///       @return $n * 3;
///     }
///
///     $foo: _map((1, 2), times-three);
///     // => (3, 6)
///
///     $foo: _map(( 'a': 1, 'b': 2 ), times-three);
///     // => (3, 6)
///
///     $users: (
///       ( 'user': 'barney' ),
///       ( 'user': 'fred' )
///     );
///     // using the `_property` callback shorthand
///     $foo: _map($users, 'user');
///     // => ('barney', 'fred')

@function _map($args...) {
  @return call(get-function("__map"), $args...);
}

/// @alias _map

@function _collect($args...) {
  @return call(get-function("__map"), $args...);
}

/// Creates a map with the same keys as `$map` and values generated by
/// running each own enumerable property of `$map` through `$iteratee`. The
/// iteratee function is bound to `$this-arg` and invoked with three arguments;
/// (value, key, map).
///
/// If a property name is provided for `$iteratee` the created `_property`
/// style callback returns the property value of the given element.
///
/// If a value is also provided for `$this-arg` the created `_matches-property`
/// style callback returns `true` for elements that have a matching property
/// value, else `false`.
///
/// If a map is provided for `$iteratee` the created `_matches` style
/// callback returns `true` for elements that have the properties of the given
/// map, else `false`.
///
///
/// @access public
/// @group Map
/// @param {Map} $map The map to iterate over.
/// @param {Function|Map|string} $iteratee [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$iteratee`.
/// @returns {Map} Returns the new mapped map.
/// @example scss
///     @function times-three($n, $args...) {
///         @return $n * 3;
///     }
///
///     $foo: _map-values(( 'a': 1, 'b': 2 ), times-three);
///     // => ( 'a': 3, 'b': 6 )
///
///     $users: (
///       'fred':    ( 'user': 'fred',    'age': 40 ),
///       'pebbles': ( 'user': 'pebbles', 'age': 1 )
///     );
///     // using the `_property` callback shorthand
///     $foo: _map-values($users, 'age');
///     // => ( 'fred': 40, 'pebbles': 1 )

@function _map-values($args...) {
  @return call(get-function("__map-values"), $args...);
}

/// The opposite of `_map-values`; this method creates a map with the same
/// values as `$map` and keys generated by running each own enumerable
/// property of `$map` through `$iteratee`.
///
/// If a property name is provided for `$iteratee` the created `_property`
/// style callback returns the property value of the given element.
///
/// If a value is also provided for `$this-arg` the created `_matches-property`
/// style callback returns `true` for elements that have a matching property
/// value, else `false`.
///
/// If a map is provided for `$iteratee` the created `_matches` style
/// callback returns `true` for elements that have the properties of the given
/// map, else `false`.
///
/// **Also** - if `$with-keys` is specified as `true`, the iteratee
/// will only use each `key` of the `$map` as its value.
///
/// @access public
/// @group Map
/// @param {Map} $map The map to iterate over.
/// @param {Function|Map|string} $iteratee [_identity] - The function invoked
/// per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$iteratee`.
/// @returns {Map} Returns the new mapped map.
/// @example scss
///     @function to-css-prop($val, $key, $args...) {
///         @return _kebab-case($key);
///     }
///
///     $foo: (
///         'backgroundColor': white,
///         'marginTop': 1rem
///     );
///
///     $foo: _map-keys($foo, to-css-prop);
///     // => (
///     //  'background-color': white,
///     //  'margin-top': 1rem
///     // )
///
///     // Using $with-keys
///     $foo: _map-keys($foo, _kebab-case, null, $with-key: true);
///     // => (
///     //  'background-color': white,
///     //  'margin-top': 1rem
///     // )

@function _map-keys($args...) {
  @return call(get-function("__map-keys"), $args...);
}
