@function __extremum-by-iteratee($value, $index, $collection) {
  $collection: __this("collection");
  $iteratee: __this("iteratee");
  $is-min: __this("is-min");
  $ex-value: __this("ex-value");
  $computed: __this("computed");
  $result: __this("result");

  $current: __exec($iteratee, $value, $index, $collection);

  @if (if($is-min, $current < $computed, $current > $computed)) or
    ($current == $ex-value and $current == $result)
  {
    $_: __this("computed", $current);
    $_: __this("result", $value);
  }

  @return true;
}

@function __extremum-by($collection, $iteratee, $is-min: false) {
  $ex-value: if(
    $is-min,
    __const("POSITIVE_INFINITY"),
    __const("NEGATIVE_INFINITY")
  );
  $computed: $ex-value;
  $result: $computed;

  $_: __scope(
    (
      "collection": $collection,
      "iteratee": $iteratee,
      "is-min": $is-min,
      "ex-value": $ex-value,
      "computed": $computed,
      "result": $result
    )
  );
  $iteratee: __bind("__extremum-by-iteratee");
  $_: __base-each($collection, $iteratee);
  $result: __this("result");
  $_: __scope(false);

  @return $result;
}

@function __create-extremum-function(
  $collection,
  $iteratee: null,
  $this-arg: null
) {
  $list-function: __this("list-function");
  $is-min: __this("is-min");

  @if ($this-arg and __is-iteratee-call($collection, $iteratee, $this-arg)) {
    $iteratee: null;
  }

  $function: __get-callback();
  $no-iteratee: ($iteratee == null);

  @if not($function == "__base-callback" and $no-iteratee) {
    $no-iteratee: false;
    $iteratee: __exec($function, $iteratee, $this-arg, 3);
  }

  @if ($no-iteratee) {
    $is-list: __is-list($collection);

    @if (not $is-list and __is-string($collection)) {
      $iteratee: __char-at-callback;
    } @else {
      @return __exec(
        $list-function,
        if($is-list, $collection, __to-iterable($collection))
      );
    }
  }

  @return __extremum-by($collection, $iteratee, $is-min);
}

@function __create-extremum($list-function, $is-min: false) {
  $_: __scope(
    (
      "list-function": $list-function,
      "is-min": $is-min
    )
  );
  $create-extremum-function: __bind("__create-extremum-function");
  $_: __scope(false);

  @return $create-extremum-function;
}

@function __max($arguments...) {
  $function: __create-extremum("__list-max");

  @return __exec($function, $arguments...);
}

@function __min($arguments...) {
  $function: __create-extremum("__list-min", true);

  @return __exec($function, $arguments...);
}

@function __list-max($list) {
  @if __is-map($list) {
    $list: __values($list);
  }

  $list: __list-each($list, "__to-comparable");
  $result: __const("NEGATIVE_INFINITY");

  // TODO use Hugo's comparison for strings
  @each $value in $list {
    @if __is-number($value) and ($value > $result) {
      $result: $value;
    }
  }

  @return $result;
}

@function __list-min($list) {
  @if (__is-map($list)) {
    $list: __values($list);
  }

  $list: __list-each($list, "__to-comparable");
  $result: __const("POSITIVE_INFINITY");

  // TODO use Hugo's comparison for strings
  @each $value in $list {
    @if __is-number($value) and $value < $result {
      $result: $value;
    }
  }

  @return $result;
}

/// Gets the maximum value of `$collection`. If `$collection` is empty or falsey
/// `-Infinity` is returned. If an iteratee function is provided it is invoked
/// for each value in `$collection` to generate the criterion by which the value
/// is ranked. The `$iteratee` is bound to `$this-arg` and invoked with three
/// arguments: (value, index, 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
/// map, else `false`.
///
///
/// @access public
/// @group Collection
/// @param {List|Map|string} $collection The collection to iterate over.
/// @param {Function|Map|string} $iteratee [null] - The function invoked per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$iteratee`.
/// @returns {*} Returns the maximum value.
/// @example scss
/// $foo: _max((4, 2, 8, 6));
/// // => 8
///
/// $foo: _max(());
/// // => -Infinity
///
/// $users: (
///   ( 'user': 'barney', 'age': 36 ),
///   ( 'user': 'fred',   'age': 40 )
/// );
///
/// // using the `_property` callback shorthand
/// $foo: _max($users, 'age');
/// // => ( 'user': 'fred', 'age': 40 );

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

/// Gets the minimum value of `$collection`. If `$collection` is empty or falsey
/// `Infinity` is returned. If an iteratee function is provided it is invoked
/// for each value in `$collection` to generate the criterion by which the value
/// is ranked. The `$iteratee` is bound to `$this-arg` and invoked with three
/// arguments; (value, index, 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
/// map, else `false`.
///
///
/// @access public
/// @group Collection
/// @param {List|Map|string} $collection - The collection to iterate over.
/// @param {Function|Map|string} $iteratee [null] - The function invoked per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$iteratee`.
/// @returns {*} Returns the minimum value.
/// @example scss
/// $foo: _min((4, 2, 8, 6));
/// // => 2
///
/// $foo: _min(());
/// // => Infinity
///
/// $users: (
///   ( 'user': 'barney', 'age': 36 ),
///   ( 'user': 'fred',   'age': 40 )
/// );
///
/// // using the `_property` callback shorthand
/// $foo: _min($users, 'age');
/// // => ( 'user': 'barney', 'age': 36 );

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