const SORT_ASC_REVISION = (p1, p2) => sortAscending(p1.revision, p2.revision);
export class DeepMap {
    static clone(map) {
        const clone = new DeepMap();
        map.visit((pair, keys) => {
            clone.set(keys, pair.value);
        });
        return clone;
    }
    constructor(initial) {
        this.map = new Map();
        this.length = 0;
        this.revision = 0;
        this.visit = (fn) => {
            this.map.forEach((_, k) => this.visitKey(k, this.map, [], fn));
        };
        this.visitDepthFirst = (fn) => {
            this.visitWithNext([], fn);
        };
        this.visitWithNext = (parentKeys, fn, currentMap = this.map) => {
            if (!currentMap) {
                return;
            }
            let i = 0;
            currentMap.forEach((_, key) => {
                const pair = currentMap.get(key);
                if (!pair) {
                    return;
                }
                const { map } = pair;
                const keys = [...parentKeys, key];
                const next = map ? () => this.visitWithNext(keys, fn, map) : undefined;
                if (pair.hasOwnProperty('value')) {
                    fn(pair.value, keys, i, next);
                    i++;
                }
                else {
                    next?.();
                }
            });
        };
        if (initial) {
            initial.forEach((entry) => {
                const [keys, value] = entry;
                this.set(keys, value);
            });
        }
    }
    set(keys, value) {
        let currentMap = this.map;
        if (!keys.length) {
            throw `Cannot set given keys - please provide a non-empty keys array.`;
            // currentMap.set((EMPTY_KEY as unknown) as K, { value });
            // this.length++;
            // return this;
        }
        for (let i = 0, len = keys.length; i < len; i++) {
            const key = keys[i];
            const last = i === len - 1;
            if (last) {
                const pair = currentMap.has(key) ? currentMap.get(key) : {};
                pair.revision = this.revision++;
                pair.value = value;
                currentMap.set(key, pair);
                this.length++;
            }
            else {
                const pair = currentMap.has(key) ? currentMap.get(key) : {};
                if (!pair.map) {
                    pair.map = new Map();
                    currentMap.set(key, pair);
                }
                currentMap = pair.map;
            }
        }
        return this;
    }
    get(keys) {
        let currentMap = this.map;
        if (!keys.length) {
            throw `Cannot get given keys - please provide a non-empty keys array.`;
            // return currentMap.get((EMPTY_KEY as unknown) as K)?.value;
        }
        for (let i = 0, len = keys.length; i < len; i++) {
            const key = keys[i];
            const last = i === len - 1;
            if (last) {
                return currentMap.get(key)?.value;
            }
            else {
                const pair = currentMap.get(key);
                if (!pair || !pair.map) {
                    return;
                }
                currentMap = pair.map;
            }
        }
        return;
    }
    get size() {
        return this.length;
    }
    clear() {
        const clearMap = (map) => {
            map.forEach((value, _key) => {
                const { map } = value;
                if (map) {
                    clearMap(map);
                }
            });
            map.clear();
        };
        clearMap(this.map);
        this.length = 0;
        this.revision = 0;
    }
    delete(keys) {
        keys = [...keys];
        let currentMap = this.map;
        if (!keys.length) {
            throw `Cannot delete given keys - please provide a non-empty keys array.`;
            // if (currentMap.has(EMPTY_KEY as unknown as K)) {
            //   this.length--;
            // }
            // return currentMap.delete((EMPTY_KEY as unknown) as K);
        }
        let maps = [currentMap];
        let result = false;
        for (let i = 0, len = keys.length; i < len; i++) {
            const key = keys[i];
            const last = i === len - 1;
            if (last) {
                const pair = currentMap.get(key);
                if (pair) {
                    if (pair.hasOwnProperty('value')) {
                        delete pair.value;
                        delete pair.revision;
                        result = true;
                        this.length--;
                    }
                    if (pair.map && pair.map.size === 0) {
                        delete pair.map;
                    }
                    if (!pair.map) {
                        // pair is empty, so we can remove it altogether
                        currentMap.delete(key);
                    }
                }
                break;
            }
            else {
                const pair = currentMap.get(key);
                if (!pair || !pair.map) {
                    result = false;
                    break;
                }
                currentMap = pair.map;
                maps.push(currentMap);
            }
        }
        while (maps.length) {
            let map = maps.pop();
            let key = keys.pop();
            if (key && map?.size === 0) {
                let parentMap = maps[maps.length - 1];
                let pair = parentMap?.get(key);
                if (pair) {
                    // pair.map === map ; which can be deleted
                    delete pair.map;
                    if (!pair.hasOwnProperty('value')) {
                        // whole pair can be successfully deleted from parentMap
                        parentMap.delete(key);
                    }
                }
            }
        }
        return result;
    }
    has(keys) {
        let currentMap = this.map;
        if (!keys.length) {
            throw `Cannot find existing given keys - please provide a non-empty keys array.`;
            // return (
            //   currentMap.has((EMPTY_KEY as unknown) as K) &&
            //   currentMap.get((EMPTY_KEY as unknown) as K)?.value !== undefined
            // );
        }
        for (let i = 0, len = keys.length; i < len; i++) {
            const key = keys[i];
            const last = i === len - 1;
            if (last) {
                const pair = currentMap.get(key);
                return !!pair && pair.hasOwnProperty('value');
            }
            else {
                const pair = currentMap.get(key);
                if (!pair || !pair.map) {
                    return false;
                }
                currentMap = pair.map;
            }
        }
        return false;
    }
    visitKey(key, currentMap, parentKeys, fn) {
        const pair = currentMap.get(key);
        if (!pair) {
            return;
        }
        const { map } = pair;
        const keys = [...parentKeys, key];
        const next = once(() => {
            if (map) {
                map.forEach((_, k) => {
                    this.visitKey(k, map, keys, fn);
                });
            }
        });
        if (pair.hasOwnProperty('value')) {
            fn(pair, keys, next);
        }
        // if it was called by fn, it won't be called again, as it's once-d
        next();
    }
    getArray(fn) {
        const result = [];
        this.visit((pair, keys) => {
            result.push(fn({ ...pair, keys }));
        });
        return result;
    }
    values() {
        return this.sortedIterator((pair) => pair.value);
    }
    keys() {
        const keys = this.sortedIterator((pair) => pair.keys);
        return keys;
    }
    entries() {
        return this.sortedIterator((pair) => [pair.keys, pair.value]);
    }
    topDownEntries() {
        return this.getArray((pair) => [pair.keys, pair.value]);
    }
    topDownKeys() {
        return this.getArray((pair) => pair.keys);
    }
    topDownValues() {
        return this.getArray((pair) => pair.value);
    }
    sortedIterator(fn) {
        const result = [];
        this.visit((pair, keys) => {
            result.push({ ...pair, keys });
        });
        result.sort(SORT_ASC_REVISION);
        function* makeIterator() {
            for (let i = 0, len = result.length; i < len; i++) {
                yield fn(result[i]);
            }
        }
        return makeIterator();
    }
}
export function once(fn) {
    let called = false;
    let result = null;
    const onceFn = () => {
        if (called) {
            return result;
        }
        called = true;
        result = fn();
        return result;
    };
    return onceFn;
}
const sortAscending = (a, b) => a - b;
