import { sortedLastIndexBy } from './sortedLastIndexBy.ts';
import { isNil } from '../../predicate/isNil.ts';
import { isNull } from '../../predicate/isNull.ts';
import { isSymbol } from '../../predicate/isSymbol.ts';
import { isNumber } from '../predicate/isNumber.ts';

const MAX_ARRAY_LENGTH = 4294967295;
const HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;

/**
 * Uses a binary search to determine the highest index at which `value`
 * should be inserted into `array` in order to maintain its sort order.
 *
 * @category Array
 * @param array The sorted array to inspect.
 * @param value The value to evaluate.
 * @returns Returns the index at which `value` should be inserted
 *  into `array`.
 * @example
 * sortedLastIndex([4, 5, 5, 5, 6], 5)
 * // => 4
 */
export function sortedLastIndex<T>(array: ArrayLike<T> | null | undefined, value: T): number {
  if (isNil(array)) {
    return 0;
  }

  let high = array.length;

  if (!isNumber(value) || Number.isNaN(value) || high > HALF_MAX_ARRAY_LENGTH) {
    return sortedLastIndexBy(array, value, value => value);
  }

  let low = 0;

  while (low < high) {
    const mid = (low + high) >>> 1;
    const compute = array[mid];
    if (!isNull(compute) && !isSymbol(compute) && (compute as any) <= value) {
      low = mid + 1;
    } else {
      high = mid;
    }
  }

  return high;
}
