/**
 * Return the last index in the array that matches the predicate
 */
export function findLastIndex(array, predicate) {
    let i = array.length;
    while (i--) {
        if (predicate(array[i])) {
            return i;
        }
    }
    return -1;
}
/**
 * The node for LinkedList below
 */
class Node {
    data;
    next = null;
    prev = null;
    constructor(data) {
        this.data = data;
    }
}
/**
 * We want to use this if we only need push/pop/shift method
 * without random access.
 * The shift() method should be cheaper than regular array.
 */
export class LinkedList {
    _length;
    head;
    tail;
    pointer = null;
    constructor() {
        this._length = 0;
        this.head = null;
        this.tail = null;
    }
    get length() {
        return this._length;
    }
    /**
     * Add to the end of the list
     */
    push(data) {
        if (this._length === 0) {
            this.tail = this.head = new Node(data);
            this._length++;
            return;
        }
        if (!this.head || !this.tail) {
            // should not happen
            throw Error("No head or tail");
        }
        const newTail = new Node(data);
        this.tail.next = newTail;
        newTail.prev = this.tail;
        this.tail = newTail;
        this._length++;
    }
    /**
     * Add to the beginning of the list
     */
    unshift(data) {
        if (this._length === 0) {
            this.tail = this.head = new Node(data);
            this._length++;
            return;
        }
        if (!this.head || !this.tail) {
            // should not happen
            throw Error("No head or tail");
        }
        const newHead = new Node(data);
        newHead.next = this.head;
        this.head.prev = newHead;
        this.head = newHead;
        this._length++;
    }
    insertAfter(after, data) {
        const node = this.findNode(after);
        if (!node) {
            return;
        }
        if (node === this.tail) {
            this.push(data);
            return;
        }
        const newNode = new Node(data);
        newNode.next = node.next;
        newNode.prev = node;
        node.next = newNode;
        if (newNode.next)
            newNode.next.prev = newNode;
        this._length++;
    }
    pop() {
        const oldTail = this.tail;
        if (!oldTail)
            return null;
        this._length = Math.max(0, this._length - 1);
        if (this._length === 0) {
            this.head = this.tail = null;
        }
        else {
            this.tail = oldTail.prev;
            if (this.tail)
                this.tail.next = null;
            oldTail.prev = oldTail.next = null;
        }
        return oldTail.data;
    }
    shift() {
        const oldHead = this.head;
        if (!oldHead)
            return null;
        this._length = Math.max(0, this._length - 1);
        if (this._length === 0) {
            this.head = this.tail = null;
        }
        else {
            this.head = oldHead.next;
            if (this.head)
                this.head.prev = null;
            oldHead.prev = oldHead.next = null;
        }
        return oldHead.data;
    }
    first() {
        return this.head?.data ?? null;
    }
    last() {
        return this.tail?.data ?? null;
    }
    /**
     * Delete the first item thats search from head
     */
    deleteFirst(item) {
        if (item === this.head?.data) {
            this.shift();
            return true;
        }
        let node = this.head;
        while (node) {
            if (node.data === item) {
                if (node === this.tail)
                    this.tail = node.prev;
                if (node.prev)
                    node.prev.next = node.next;
                if (node.next)
                    node.next.prev = node.prev;
                node.prev = node.next = null;
                this._length--;
                return true;
            }
            node = node.next;
        }
        return false;
    }
    /**
     * Delete the first item search from tail.
     */
    deleteLast(item) {
        if (item === this.tail?.data) {
            this.pop();
            return true;
        }
        let node = this.tail;
        while (node) {
            if (node.data === item) {
                if (node === this.head)
                    this.head = node.next;
                if (node.prev)
                    node.prev.next = node.next;
                if (node.next)
                    node.next.prev = node.prev;
                node.prev = node.next = null;
                this._length--;
                return true;
            }
            node = node.prev;
        }
        return false;
    }
    /**
     * Move an existing item to the head of the list.
     * If the item is not found, do nothing.
     */
    moveToHead(item) {
        // if this is head, do nothing
        if (this.head?.data === item) {
            return;
        }
        const found = this.deleteFirst(item);
        if (found) {
            this.unshift(item);
        }
    }
    /**
     * Move an existing item to the second position of the list.
     * If the item is not found, do nothing.
     */
    moveToSecond(item) {
        // if this is head or second, do nothing
        if (this.head?.data === item || this.head?.next?.data === item) {
            return;
        }
        const found = this.deleteFirst(item);
        if (found) {
            if (this.head?.next) {
                const oldSecond = this.head.next;
                const newSecond = new Node(item);
                this.head.next = newSecond;
                newSecond.next = oldSecond;
                newSecond.prev = this.head;
                oldSecond.prev = newSecond;
            }
            else {
                // only 1 item in the list
                this.push(item);
            }
        }
    }
    next() {
        if (!this.pointer) {
            return { done: true, value: undefined };
        }
        const value = this.pointer.data;
        this.pointer = this.pointer.next;
        return { done: false, value };
    }
    [Symbol.iterator]() {
        this.pointer = this.head;
        return this;
    }
    values() {
        this.pointer = this.head;
        return this;
    }
    clear() {
        this.head = this.tail = null;
        this._length = 0;
    }
    toArray() {
        let node = this.head;
        if (!node)
            return [];
        const arr = [];
        while (node) {
            arr.push(node.data);
            node = node.next;
        }
        return arr;
    }
    map(fn) {
        let node = this.head;
        if (!node)
            return [];
        const arr = [];
        while (node) {
            arr.push(fn(node.data));
            node = node.next;
        }
        return arr;
    }
    /**
     * Check if the item is in the list.
     * @returns
     */
    has(item) {
        return this.findNode(item) !== null;
    }
    findNode(item) {
        let node = this.head;
        while (node) {
            if (node.data === item) {
                return node;
            }
            node = node.next;
        }
        return null;
    }
}
//# sourceMappingURL=array.js.map