const MATCH_ALL_PATTERN = '*';
const NO_MATCH = -1;
const TRAILING_WILDCARD_LENGTH = 1;
const SEGMENT_EXACT = 'exact';
const SEGMENT_PREFIX = 'prefix';
const sortedEntryKeysBySet = new WeakMap();
export default class QueryNodePatternIndex {
    #alive;
    constructor(alive) {
        this.#alive = alive;
        Object.freeze(this);
    }
    *liveCandidates(pattern) {
        const route = patternRoute(pattern);
        if (route === null) {
            yield* this.#allLiveCandidates();
            return;
        }
        yield* this.#indexedCandidates(route);
    }
    *#allLiveCandidates() {
        for (const element of this.#alive.entries.keys()) {
            if (this.#alive.contains(element)) {
                yield element;
            }
        }
    }
    *#indexedCandidates(route) {
        const emitted = new Set();
        for (const segment of route) {
            yield* this.#segmentCandidates(segment, emitted);
        }
    }
    *#segmentCandidates(segment, emitted) {
        const candidates = segment.kind === SEGMENT_EXACT
            ? this.#exactCandidate(segment.value)
            : this.#prefixCandidates(segment.value);
        for (const candidate of candidates) {
            if (!emitted.has(candidate)) {
                emitted.add(candidate);
                yield candidate;
            }
        }
    }
    *#exactCandidate(nodeId) {
        if (this.#alive.contains(nodeId)) {
            yield nodeId;
        }
    }
    *#prefixCandidates(prefix) {
        const keys = sortedEntryKeys(this.#alive);
        for (let index = lowerBound(keys, prefix); index < keys.length; index += 1) {
            const candidate = keys[index];
            if (candidate === undefined) {
                return;
            }
            if (!candidate.startsWith(prefix)) {
                return;
            }
            if (this.#alive.contains(candidate)) {
                yield candidate;
            }
        }
    }
}
function patternRoute(pattern) {
    const patterns = typeof pattern === 'string' ? [pattern] : pattern;
    const route = [];
    for (const entry of patterns) {
        const segment = patternSegment(entry);
        if (segment === null) {
            return null;
        }
        route.push(segment);
    }
    return Object.freeze(route);
}
function patternSegment(pattern) {
    if (pattern === MATCH_ALL_PATTERN) {
        return null;
    }
    const wildcardIndex = pattern.indexOf(MATCH_ALL_PATTERN);
    if (wildcardIndex === NO_MATCH) {
        return Object.freeze({ kind: SEGMENT_EXACT, value: pattern });
    }
    if (isSingleTrailingWildcard(pattern, wildcardIndex)) {
        return Object.freeze({ kind: SEGMENT_PREFIX, value: pattern.slice(0, wildcardIndex) });
    }
    return null;
}
function isSingleTrailingWildcard(pattern, wildcardIndex) {
    return wildcardIndex === pattern.length - TRAILING_WILDCARD_LENGTH
        && pattern.indexOf(MATCH_ALL_PATTERN, wildcardIndex + TRAILING_WILDCARD_LENGTH) === NO_MATCH;
}
function sortedEntryKeys(alive) {
    const cached = sortedEntryKeysBySet.get(alive);
    if (cached !== undefined && cached.entryCount === alive.entries.size) {
        return cached.keys;
    }
    const keys = Object.freeze([...alive.entries.keys()].sort(compareStrings));
    sortedEntryKeysBySet.set(alive, Object.freeze({ entryCount: alive.entries.size, keys }));
    return keys;
}
function lowerBound(keys, needle) {
    let low = 0;
    let high = keys.length;
    while (low < high) {
        const mid = Math.floor((low + high) / 2);
        const midValue = keys[mid];
        if (midValue === undefined) {
            return low;
        }
        if (midValue < needle) {
            low = mid + 1;
        }
        else {
            high = mid;
        }
    }
    return low;
}
function compareStrings(left, right) {
    if (left < right) {
        return -1;
    }
    if (left > right) {
        return 1;
    }
    return 0;
}
