"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.keyPathContains = exports.keyPathEndsWith = exports.keyPathStartsWith = exports.keyPathsEqual = exports.isKeyPathThis = exports.thisKeyPath = exports.keysOf = void 0;
const optional_1 = require("../../types/optional");
/**
 * A global cache containing parsed string key paths
 * with components separated by a dot.
 */
const parsedKeyPaths = {};
/**
 * Extract the individual keys from a key path in order
 * to traverse into an object to access a specific value.
 *
 * @param keyPath - A key path to extract the keys from.
 * @returns An array containing the keys making up `keyPath`.
 */
function keysOf(keyPath) {
    // TODO: Normalizing into an array is potentially a bottleneck.
    //       Do we want to do this differently for slower environments?
    if (Array.isArray(keyPath)) {
        return keyPath;
    }
    else {
        switch (typeof keyPath) {
            case "string": {
                const existingKeyPath = parsedKeyPaths[keyPath];
                if ((0, optional_1.isSome)(existingKeyPath)) {
                    return existingKeyPath;
                }
                else {
                    const newKeyPath = Object.freeze(keyPath.split("."));
                    parsedKeyPaths[keyPath] = newKeyPath;
                    return newKeyPath;
                }
            }
            case "number": {
                return [keyPath];
            }
            case "symbol": {
                return [keyPath];
            }
            default: {
                throw new TypeError(`${keyPath.toString()} is not a KeyPath`);
            }
        }
    }
}
exports.keysOf = keysOf;
/**
 * A key path representing an object itself.
 */
exports.thisKeyPath = Object.freeze([]);
/**
 * Determine whether a given key path is the `this` (identity) key path.
 * @param keyPath - A key path to test.
 */
function isKeyPathThis(keyPath) {
    return Array.isArray(keyPath) && keyPath.length === 0;
}
exports.isKeyPathThis = isKeyPathThis;
/**
 * Determines whether two key paths are equivalent taking into account
 * that the key paths may have different representations.
 *
 * @param lhs - A key path to compare.
 * @param rhs - Another key path to compare.
 */
function keyPathsEqual(lhs, rhs) {
    // 1. Are the key paths equal through value semantics?
    if (lhs === rhs) {
        return true;
    }
    const lhsKeys = keysOf(lhs);
    const rhsKeys = keysOf(rhs);
    // 2. Do we have the same number of keys in each path?
    if (lhsKeys.length !== rhsKeys.length) {
        return false;
    }
    // 3. Do any of the keys in our paths differ?
    for (let index = 0, length = lhsKeys.length; index < length; index += 1) {
        if (lhsKeys[index] !== rhsKeys[index]) {
            return false;
        }
    }
    // 4. We have passed all checks and are considered equal.
    return true;
}
exports.keyPathsEqual = keyPathsEqual;
/**
 * Determine whether a given key path starts with a specified key.
 *
 * @param haystack - A key path to perform a prefix check on.
 * @param needle - The key to check for.
 */
function keyPathStartsWith(haystack, needle) {
    if (haystack === needle) {
        return true;
    }
    else {
        const keys = keysOf(haystack);
        if (keys.length === 0) {
            return false;
        }
        return keys[0] === needle;
    }
}
exports.keyPathStartsWith = keyPathStartsWith;
/**
 * Determine whether a given key path ends with a specified key.
 *
 * @param haystack - A key path to perform a suffix check on.
 * @param needle - The key to check for.
 */
function keyPathEndsWith(haystack, needle) {
    if (haystack === needle) {
        return true;
    }
    else {
        const keys = keysOf(haystack);
        if (keys.length === 0) {
            return false;
        }
        return keys[keys.length - 1] === needle;
    }
}
exports.keyPathEndsWith = keyPathEndsWith;
/**
 * Determine whether a given key path contains a specified key.
 *
 * @param haystack - A key path to search.
 * @param needle - The key to search for.
 */
function keyPathContains(haystack, needle) {
    if (haystack === needle) {
        return true;
    }
    else {
        const keys = keysOf(haystack);
        return keys.includes(needle);
    }
}
exports.keyPathContains = keyPathContains;
//# sourceMappingURL=key-path.js.map