@function __base-find-iteratee($value, $key, $collection) {
  $predicate: __this("predicate");
  $return-key: __this("return-key");

  $iteration: __exec($predicate, $value, $key, $collection);

  @if __is-truthy($iteration) {
    $_: __this("result", if($return-key, $key, $value));

    @return false;
  }

  @return true;
}

@function __base-find(
  $collection,
  $predicate,
  $each-function: "__base-each",
  $return-key: false
) {
  $result: null;
  $_: __scope(
    (
      "result": null,
      "predicate": $predicate,
      "return-key": $return-key
    )
  );
  $iteratee: __bind(__base-find-iteratee);
  $_: __exec($each-function, $collection, $iteratee);
  $result: __this("result");
  $_: __scope(false);

  @return $result;
}

@function __find($collection, $predicate: "__identity", $this-arg: null) {
  @if __is-string($collection) {
    $collection: __to-list($collection);
  }

  @if __is-list($collection) {
    $index: __find-index($collection, $predicate, $this-arg);

    @return if($index > -1, nth($collection, $index), null);
  }

  $predicate: __get-callback($predicate, $this-arg, 3);

  @return __base-find($collection, $predicate, "__base-each");
}

@function __find-last($collection, $predicate: "__identity", $this-arg: null) {
  @if __is-string($collection) {
    $collection: __to-list($collection);
  }

  @if __is-list($collection) {
    $index: __find-last-index($collection, $predicate, $this-arg);

    @return if($index > -1, nth($collection, $index), null);
  }

  $predicate: __get-callback($predicate, $this-arg, 3);

  @return __base-find($collection, $predicate, "__base-each-right");
}

@function __find-where($collection, $source) {
  @return __find($collection, __matches($source));
}

@function __find-index($list, $predicate: "__identity", $this-arg: null) {
  $index: 1;
  $length: if($list, length($list), 0);

  $predicate: __get-callback($predicate, $this-arg, 3);

  @while ($index <= $length) {
    $iteration: __call(
      $predicate,
      $this-arg,
      nth($list, $index),
      $index,
      $list
    );

    @if __is-truthy($iteration) {
      @return $index;
    }

    $index: $index + 1;
  }

  @return -1;
}

@function __find-last-index($list, $predicate: "__identity", $this-arg: null) {
  $length: if($list, length($list), 0);

  $predicate: __get-callback($predicate, $this-arg, 3);

  @while ($length > 0) {
    @if __call($predicate, $this-arg, nth($list, $length), $length, $list) {
      @return $length;
    }

    $length: $length - 1;
  }

  @return -1;
}

@function __find-key($map, $predicate: "__identity", $this-arg: null) {
  $predicate: __get-callback($predicate, $this-arg, 3);

  @return __base-find($map, $predicate, "__base-for-own", true);
}

@function __find-last-key($map, $predicate: "__identity", $this-arg: null) {
  $predicate: __get-callback($predicate, $this-arg, 3);

  @return __base-find($map, $predicate, "__base-for-own-right", true);
}

/// Iterates over elements of `$collection`, returning the first element
/// `$predicate` returns truthy for. The predicate 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
/// map, else `false`.
///
///
/// @access public
/// @group Collection
/// @param {List|Map|string} collection The collection to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {*} Returns the matched element, else `null`.
/// @example scss
///     $users: (
///       ( 'user': 'barney',  'age': 36', active': true ),
///       ( 'user': 'fred',    'age': 40', active': false ),
///       ( 'user': 'pebbles', 'age': 1',  active': false )
///     );
///
///     @function is-young($user, $args...) {
///         @return _get($user, 'age') < 40;
///     }
///
///     $foo: _result(_find($users, is-young), 'user');
///     // => 'barney'
///
///     // using the `_matches` callback shorthand
///     $foo: _result(_find($users, ( 'age': 1, 'active': true )), 'user');
///     // => 'pebbles'
///
///     // using the `_matches-property` callback shorthand
///     $foo: _result(_find($users, 'active', false), 'user');
///     // => 'fred'
///
///     // using the `_property` callback shorthand
///     $foo: _result(_find($users, 'active'), 'user');
///     // => 'barney'

@function _find($args...) {
  @return call(get_function(__find), $args...);
}

/// @alias _find

@function _detect($args...) {
  @return call(get_function(__find), $args...);
}

/// This method is like `_find` except that it returns the index of the first
/// element `$predicate` returns truthy for, instead of the element itself.
///
/// 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 List
/// @param {List} $list The list to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {number} Returns the index of the found element, else `-1`.
/// @example scss
///     $users: (
///       ( 'user': 'barney',  'active': false ),
///       ( 'user': 'fred',    'active': false ),
///       ( 'user': 'pebbles', 'active': true )
///     );
///
///     @function is-barney($user, $args...) {
///         @return _get($user, 'user') == 'barney';
///     }
///
///     $foo: _find-index($users, is-barney);
///     // => 1
///
///     // using the `_matches` callback shorthand
///     $foo: _find-index($users, ( 'user': 'fred', 'active': false ));
///     // => 2
///     // using the `_matches-property` callback shorthand
///     $foo: _find-index($users, 'active', false);
///     // => 1
///
///     // using the `_property` callback shorthand
///     $foo: _find-index($users, 'active');
///     // => 3

@function _find-index($args...) {
  @return call(get-function("__find-index"), $args...);
}

/// This method is like `_find-index` except that it returns the key of the
/// first element `$predicate` returns truthy for, instead of the element itself.
///
/// 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 Map
/// @param {Map} $map The map to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {string|null} Returns the key of the matched element, else `null`.
/// @example scss
///     $users: (
///       'barney':  ( 'age': 36, 'active': true ),
///       'fred':    ( 'age': 40, 'active': false ),
///       'pebbles': ( 'age': 1,  'active': true )
///     );
///
///     @function under-40($chr, $args...) {
///         @return _get($chr, 'age') < 40;
///     }
///
///     $foo: _find-key($users, under-40)
///     // => 'barney'
///
///     // using the `_matches` callback shorthand
///     $foo: _find-key($users, ( 'age': 1, 'active': true ));
///     // => 'pebbles'
///
///     // using the `_matches-property` callback shorthand
///     $foo: _find-key($users, 'active', false);
///     // => 'fred'
///
///     // using the `_property` callback shorthand
///     $foo: _find-key($users, 'active');
///     // => 'barney'

@function _find-key($args...) {
  @return call(get-function("__find-key"), $args...);
}

/// This method is like `_find` except that it iterates over elements of
/// `$collection` from right to left.
///
///
/// @access public
/// @group Collection
/// @param {List|Map|string} $collection The collection to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {*} Returns the matched element, else `null`.
/// @example scss
///     @function is-odd($value, $args...) {
///         @return $value % 2 == 1;
///     }
///
///     $foo: _find-last((1, 2, 3, 4), is-odd);
///     // => 3

@function _find-last($args...) {
  @return call(get-function("__find-last"), $args...);
}

/// This method is like `_find-index` except that it iterates over elements
/// of `$collection` from right to left.
///
/// 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 List
/// @param {List} $list The list to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {number} Returns the index of the found element, else `-1`.
/// @example scss
///     $users: (
///       ( 'user': 'barney',  'active': true ),
///       ( 'user': 'fred',    'active': false ),
///       ( 'user': 'pebbles', 'active': false )
///     );
///
///     @function is-pebbles($chr, $args...) {
///         @return _get($chr, 'user') == 'pebbles';
///     }
///
///     $foo: _find-last-index($users, is-pebbles);
///     // => 3
///
///     // using the `_matches` callback shorthand
///     $foo: _find-last-index($users, ( 'user': 'barney', 'active': true ));
///     // => 1
///
///     // using the `_matches-property` callback shorthand
///     $foo: _find-last-index($users, 'active', false);
///     // => 2
///
///     // using the `_property` callback shorthand
///     $foo: _find-last-index($users, 'active');
///     // => 1

@function _find-last-index($args...) {
  @return call(get-function("__find-last-index"), $args...);
}

/// This method is like `_find-key` except that it iterates over elements of
/// a collection in the opposite order.
///
/// 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 Map
/// @param {Map} map The map to search.
/// @param {Function|Map|string} $predicate [_identity] - The function invoked
///  per iteration.
/// @param {*} $this-arg [null] - The `_this` binding of `$predicate`.
/// @returns {string|null} Returns the key of the matched element, else `null`.
/// @example scss
///     $users: (
///       'barney':  ( 'age': 36, 'active': true ),
///       'fred':    ( 'age': 40, 'active': false ),
///       'pebbles': ( 'age': 1,  'active': true )
///     );
///
///     @function is-young($chr, $args...) {
///         @return _get($chr, 'age') < 40;
///     }
///
///     $foo: _find-last-key($users, _is-young);
///     // => returns `pebbles` assuming `_find-key` returns `barney`
///
///     // using the `_matches` callback shorthand
///     $foo: _find-last-key($users, ( 'age': 36, 'active': true ));
///     // => 'barney'
///
///     // using the `_matches-property` callback shorthand
///     $foo: _find-last-key($users, 'active', false);
///     // => 'fred'
///
///     // using the `_property` callback shorthand
///     $foo: _find-last-key($users, 'active');
///     // => 'pebbles'

@function _find-last-key($args...) {
  @return call(get-function("__find-last-key"), $args...);
}

/// Performs a deep comparison between each element in `$collection` and the
/// source map, returning the first element that has equivalent property
/// values.
///
/// **Note:** This method supports comparing lists, booleans,
/// numbers, maps, and strings. Maps are compared by
/// their own, not inherited, enumerable properties. For comparing a single
/// own or inherited property value see `_matches-property`.
///
///
/// @access public
/// @group Collection
/// @param {List|Map|string} $collection The collection to search.
/// @param {Map} $source The map of property values to match.
/// @returns {*} Returns the matched element, else `null`.
/// @example scss
/// $users: (
///   ( 'user': 'barney', 'age': 36, 'active': true ),
///   ( 'user': 'fred',   'age': 40, 'active': false )
/// );
///
/// $foo: _result(_find-where($users, ( 'age': 36, 'active': true )), 'user');
/// // => 'barney'
///
/// $foo: _result(_find-where($users, ( 'age': 40, 'active': false )), 'user');
/// // => 'fred'

@function _find-where($args...) {
  @return call(get-function("__find-where"), $args...);
}
