import { getPathSegments, toSegments } from "./path";
/**
 * Path matcher which support :param and *wildcard segments.
 * The wildcard segment is only supported as the last segment
 */
export class PathMatcher {
    tree = new RootSegmentNode();
    loadMapping(mapping) {
        for (const [key, value] of Object.entries(mapping)) {
            this.addSegments(getPathSegments(key), value);
        }
    }
    addPath(path, value) {
        this.addSegments(getPathSegments(path), value);
    }
    addSegments(segments, value) {
        let node = this.tree;
        for (let i = 0, l = segments.length; i < l; i++) {
            const segment = segments[i];
            if (segment[0] === ':') {
                let childNode = node.wildcard;
                if (!childNode) {
                    childNode = new VariableSegmentNode(segment);
                    node.wildcard = childNode;
                }
                else if (!(childNode instanceof VariableSegmentNode)) {
                    throw new Error(`Failed to index path segments: ${segments.join('/')}. A wildcard ":" segment will overwrite an existing wildcard segment at path: ${'/' + segments.slice(0, i).join("/")}`);
                }
                node = childNode;
            }
            else if (segment === '*') {
                if (node.wildcard) {
                    throw new Error(`Failed to index path segments: ${segments.join('/')}. A wildcard "*" segment already exists at path: ${'/' + segments.slice(0, i).join("/")}`);
                }
                node.wildcard = new WildcardSegmentNode(segment, value);
                if (i < l - 1) {
                    throw new Error(`Failed to index path segments: ${segments.join('/')}. A wildcard segment must be the last segment`);
                }
                return;
            }
            else {
                let childNode = node.children[segment];
                if (!childNode) {
                    childNode = new LiteralSegmentNode(segment);
                    node.children[segment] = childNode;
                } // else // a literal segment already exists
                node = childNode;
            }
        }
        if (node.value !== undefined) {
            throw new Error(`Failed to index path segments: ${segments.join('/')}. A value already exists at path: ${'/' + segments.join("/")}`);
        }
        node.value = value;
    }
    match(path) {
        const segments = toSegments(path);
        if (segments.length === 0) {
            return null;
        }
        const params = {};
        let node = this.tree;
        for (const segment of segments) {
            const match = node.match(segment, params);
            if (match) {
                node = match;
            }
            else {
                return null;
            }
        }
        if (!node.value) { // not a leaf node (partial match)
            if (node instanceof ParentSegmentNode) {
                if (node.wildcard instanceof WildcardSegmentNode) {
                    node = node.wildcard.match('', params);
                    if (!node.value) {
                        throw new Error("Wildcard segment node `*` must have a value");
                    }
                }
            }
            if (!node.value)
                return null; // not a leaf node, neither a trailing wildcard (partial match)
        }
        let matchedSegments, remainingSegments;
        if (params._ && params._.length > 0) {
            matchedSegments = segments.slice(0, -params._.length);
            remainingSegments = params._;
        }
        else {
            matchedSegments = segments;
        }
        return { params, matchedSegments, remainingSegments, value: node.value };
    }
}
class ParentSegmentNode {
    name;
    value;
    children = {};
    wildcard;
    constructor(name, value) {
        this.name = name;
        this.value = value;
    }
    match(segment, params) {
        let node = this.children[segment];
        if (node) {
            return node;
        }
        else if (this.wildcard) {
            if (this.wildcard instanceof WildcardSegmentNode) {
                return this.wildcard.match(segment, params);
            }
            else if (this.wildcard instanceof VariableSegmentNode) {
                params[this.wildcard.paramName] = segment;
                return this.wildcard;
            }
            else {
                throw new Error("Unknown wildcard segment node type: " + this.wildcard.constructor.name);
            }
        }
        else {
            return null;
        }
    }
}
class RootSegmentNode extends ParentSegmentNode {
    constructor() {
        super("#root");
    }
}
class LiteralSegmentNode extends ParentSegmentNode {
    constructor(name, value) {
        super(name, value);
    }
}
class VariableSegmentNode extends ParentSegmentNode {
    paramName;
    constructor(name, value) {
        super(name, value);
        this.paramName = name.substring(1);
    }
}
class WildcardSegmentNode {
    name;
    value;
    constructor(name, value) {
        this.name = name;
        this.value = value;
    }
    match(segment, params) {
        if (!params._) {
            params._ = segment ? [segment] : [];
        }
        else {
            segment && params._.push(segment);
        }
        return this;
    }
}
//# sourceMappingURL=PathMatcher.js.map