@function __equal-lists($list, $other, $equal-func, $customizer, $is-where) {
  $index: 1;
  $list-length: length($list);
  $other-length: length($other);
  $result: true;

  @if not($list-length == $other-length) and not
    ($is-where and ($other-length > $list-length))
  {
    @return false;
  }

  @while ($result and ($index <= $list-length)) {
    $list-value: nth($list, $index);
    $other-value: nth($other, $index);
    $result: $__undefined__;

    @if ($customizer) {
      $result: if(
        $is-where,
        __exec($customizer, $other-value, $list-value, $index),
        __exec($customizer, $list-value, $other-value, $index)
      );
    }

    @if (__is-undefined($result)) {
      @if ($is-where) {
        $other-index: $other-length;
        $break: false;

        @while ($other-index > 0 and not $break) {
          $other-value: nth($other, $other-index);
          $result: if(
            $list-value == $other-value,
            true,
            __exec(
              $equal-func,
              $list-value,
              $other-value,
              $customizer,
              $is-where
            )
          );

          @if ($result) {
            $break: true;
          }

          $other-index: $other-index - 1;
        }
      } @else {
        $result: if(
          if($list-value, $list-value == $other-value, false),
          true,
          __exec($equal-func, $list-value, $other-value, $customizer, $is-where)
        );
      }
    }

    $index: $index + 1;
  }

  @return if($result, true, false);
}

@function __equal-by-tag() {
}

@function __equal-maps(
  $map,
  $other,
  $equal-func: false,
  $customizer: false,
  $is-where: false
) {
  $map-props: map-keys($map);
  $map-length: length($map-props);
  $other-props: map-keys($other);
  $other-length: length($other-props);

  @if ($map-length != $other-length) and not ($is-where) {
    @return false;
  }

  $has-ctor: false;
  $index: 1;

  @while ($index <= $map-length) {
    $key: nth($map-props, $index);
    $result: map-has-key($other, $key);

    @if ($result) {
      $map-value: __get($map, $key);
      $other-value: __get($other, $key);
      $result: $__undefined__;

      @if ($customizer) {
        $result: if(
          $is-where,
          __exec($customizer, $other-value, $map-value, $key),
          __exec($customizer, $map-value, $other-value, $key)
        );
      }

      @if (__is-undefined($result)) {
        $result: if(
          $map-value == $other-value,
          true,
          __exec($equal-func, $map-value, $other-value, $customizer, $is-where)
        );
      }
    }

    @if not($result) {
      @return false;
    }

    $has-ctor: if($has-ctor, true, ($key == "_type"));
    $index: $index + 1;
  }

  @if not($has-ctor) {
    // todo
  }

  @return true;
}

@function __base-is-equal($value, $other, $customizer: null, $is-where: null) {
  @if $value == $other {
    @return true;
  }

  @if __is-string($value) or not __is-iterable($value) {
    @return false;
  }

  @return __base-is-equal-deep($value, $other, $customizer, $is-where);
}

@function __base-is-equal-deep(
  $value,
  $other,
  $customizer: null,
  $is-where: null
) {
  $func: if(__is-list-like($value), "__equal-lists", "__equal-maps");

  @return __exec(
    $func,
    $value,
    $other,
    "__base-is-equal",
    $customizer,
    $is-where
  );
}

@function __is-equal($value, $other, $customizer: null, $this-arg: null) {
  $customizer: if(
    __function-exists($customizer),
    __bind-callback($customizer, $this-arg, 3),
    false
  );

  @if (
    not
      $customizer and
      __is-strict-comparable($value) and
      __is-strict-comparable($other)
  ) {
    @return ($value == $other) and (type-of($value) == type-of($other));
  }

  $result: if($customizer, __exec($customizer, $value, $other), $__undefined__);

  @return if(
    __is-undefined($result) or __is-null($result),
    __base-is-equal($value, $other, $customizer),
    ($result and true)
  );
}

/// Performs a deep comparison between two values to determine if they are
/// equivalent. If `$customizer` is provided it is invoked to compare values.
/// If `$customizer` returns `undefined` comparisons are handled by the method
/// instead. The `$customizer` is bound to `$this-arg` and invoked with three
/// arguments; (value, other, (index|key)).
///
///
/// @access public
/// @group Lang
/// @param {*} $value The value to compare.
/// @param {*} $other The other value to compare.
/// @param {Function} $customizer [null] The function to customize comparing values.
/// @param {*} $this-arg [null] - The `_this` binding of `$customizer`.
/// @returns {boolean} Returns `true` if the values are equivalent, else `false`.
/// @example scss
///     $object: ( 'user': 'fred' );
///     $other: ( 'user': 'fred' );
///     $foo: _is-equal($object, $other);
///     // => true
///
///     // using a customizer callback
///     // todo

@function _is-equal($args...) {
  @return call(get-function("__is-equal"), $args...);
}

/// @alias _is-equal

@function _eq($args...) {
  @return call(get-function("__is-equal"), $args...);
}
