/**
 * Stack (immutable)
 *
 * @example Overview
 * ```js
 * stack.push(item); // Return a new stack with item(s) added
 * stack.pop();      // Return a new stack with top-most item removed (ie. newest)
 * stack.peek;       // Return what is at the top of the stack or undefined if empty
 * stack.isEmpty;
 * stack.isFull;
 * stack.length;     // How many items in stack
 * stack.data;       // Get the underlying array
 * ```
 *
 * @example
 * ```js
 * let sanga = new Stack();
 * sanga = sanga.push(`bread`, `tomato`, `cheese`);
 * sanga.peek;  // `cheese`
 * sanga = sanga.pop(); // removes `cheese`
 * sanga.peek;  // `tomato`
 * const sangaAlt = sanga.push(`lettuce`, `cheese`); // sanga stays [`bread`, `tomato`], while sangaAlt is [`bread`, `tomato`, `lettuce`, `cheese`]
 * ```
 *
 * Stack can also be created from the basis of an existing array. First index of array will be the bottom of the stack.
 * @class Stack
 * @typeParam V - Type of stored items
 */
interface IStack<V> {
    /**
     * Enumerates stack from bottom-to-top
     *
     */
    forEach(fn: (v: V) => void): void;
    /**
     * Enumerates stack from top-to-bottom
     * @param fn
     */
    forEachFromTop(fn: (v: V) => void): void;
    get data(): readonly V[];
    /**
     * _True_ if stack is empty
     */
    get isEmpty(): boolean;
    /**
     * _True_ if stack is at its capacity. _False_ if not, or if there is no capacity.
     */
    get isFull(): boolean;
    /**
     * Get the item at the top of the stack without removing it (like `pop` would do)
     * @returns Item at the top of the stack, or _undefined_ if empty.
     */
    get peek(): V | undefined;
    /**
     * Number of items in stack
     */
    get length(): number;
}

interface IStackImmutable<V> extends IStack<V> {
    push(...toAdd: ReadonlyArray<V>): IStackImmutable<V>;
    pop(): IStackImmutable<V>;
}

export type { IStackImmutable as I, IStack as a };
