import type { Range } from './internal.mts';

/**
 * A flexible range specification for numeric indices.
 * If a start or end is defined, a tuple can be used where the second item is a boolean
 * indicating whether that end is inclusive or exclusive.<br/>
 * An IndexRange can have one of the following forms:<br/>
 * <br/>
 * - { amount: number }<br/>
 * - { start: number }<br/>
 * - { start: number, amount: number }<br/>
 * - { start: number, end: number }<br/>
 * - { start: number, end: [number, boolean] }<br/>
 * - { start: [number, boolean] }<br/>
 * - { start: [number, boolean], amount: number }<br/>
 * - { start: [number, boolean], end: number }<br/>
 * - { start: [number, boolean], end: [number, boolean] }<br/>
 * - { end: number }<br/>
 * - { end: [number, boolean] }<br/>
 */
export type IndexRange =
  | { amount: number; start?: number | [number, boolean]; end?: undefined }
  | Range<number>;

export namespace IndexRange {
  /**
   * Returns, given the `range` `IndexRange`, a normalized tuple containing the
   * start index, and optionally an end index.
   * @param range - the `IndexRange` to use
   */
  export function getIndexRangeIndices(
    range: IndexRange
  ): [number, number | undefined] {
    if (undefined !== range.amount) {
      if (undefined === range.start) return [0, range.amount - 1];

      if (Array.isArray(range.start)) {
        const [start, includeStart] = range.start;
        if (includeStart) return [start, start + range.amount - 1];
        return [start + 1, start + 1 + range.amount - 1];
      }
      return [range.start, range.start + range.amount - 1];
    }

    let start = 0;
    let end: number | undefined = undefined;

    if (`start` in range) {
      if (Array.isArray(range.start)) {
        if (range.start[1]) start = range.start[0];
        else start = range.start[0] + 1;
      } else start = range.start ?? 0;
    }

    if (`end` in range) {
      if (Array.isArray(range.end)) {
        if (range.end[1]) end = range.end[0];
        else end = range.end[0] - 1;
      } else end = range.end;
    }

    return [start, end];
  }

  /**
   * Returns, given the `range` `IndexRange`, and a target maximum `length`, the actual index range.
   * This can be one of three options:
   * - 'empty': there are no elements within the range
   * - 'all': all elements are within the range
   * - [start: number, end: number]: an inclusive range of element indices within the given range
   * @param range - the `IndexRange` to use
   * @param length - the target maximum length
   */
  export function getIndicesFor(
    range: IndexRange,
    length: number
  ): [number, number] | 'empty' | 'all' {
    if (length <= 0) return 'empty';

    let start = 0;
    let end = length - 1;

    if (undefined !== range.start) {
      if (Array.isArray(range.start)) {
        start = range.start[0];
        if (!range.start[1]) start++;
      } else start = range.start;

      if (start >= length || -start > length) return 'empty';
      if (start < 0) start = length + start;
    }

    if (undefined !== range.amount) {
      if (range.amount <= 0) return 'empty';
      if (undefined === range.start) {
        if (range.amount >= length) return 'all';
        return [0, Math.min(end, range.amount - 1)];
      }

      end = start + range.amount - 1;
    } else if (undefined !== range.end) {
      if (Array.isArray(range.end)) {
        end = range.end[0];
        if (!range.end[1]) {
          if (end === 0) return 'empty';
          end--;
        }
      } else end = range.end;

      if (end < 0) end = length + end;
    }

    if (end < start) return 'empty';
    end = Math.min(length - 1, end);

    if (start === 0 && end === length - 1) return 'all';

    return [start, end];
  }
}
