@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...);
}
