"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.not = not;
exports.or = or;
exports.and = and;
exports.unique = unique;
exports.groupBy = groupBy;
exports.clamp = clamp;
exports.urlToPath = urlToPath;
exports.pathToUrl = pathToUrl;
exports.normalizePath = normalizePath;
exports.normalizeUri = normalizeUri;
exports.getLastPartOfPath = getLastPartOfPath;
exports.passMap = passMap;
exports.isInRange = isInRange;
exports.isZeroLengthRange = isZeroLengthRange;
exports.isRangeStartAfterEnd = isRangeStartAfterEnd;
exports.swapRangeStartEndIfNecessary = swapRangeStartEndIfNecessary;
exports.moveRangeStartToEndIfNecessary = moveRangeStartToEndIfNecessary;
exports.isBeforeOrEqualToPosition = isBeforeOrEqualToPosition;
exports.isPositionEqual = isPositionEqual;
exports.isNotNullOrUndefined = isNotNullOrUndefined;
exports.debounceSameArg = debounceSameArg;
exports.debounce = debounce;
exports.debounceThrottle = debounceThrottle;
exports.regexLastIndexOf = regexLastIndexOf;
exports.regexIndexOf = regexIndexOf;
exports.getRegExpMatches = getRegExpMatches;
exports.modifyLines = modifyLines;
exports.isSamePosition = isSamePosition;
exports.filterAsync = filterAsync;
exports.getIndent = getIndent;
exports.possiblyComponent = possiblyComponent;
exports.returnObjectIfHasKeys = returnObjectIfHasKeys;
exports.toFileNameLowerCase = toFileNameLowerCase;
exports.createGetCanonicalFileName = createGetCanonicalFileName;
exports.memoize = memoize;
exports.removeLineWithString = removeLineWithString;
exports.traverseTypeString = traverseTypeString;
const node_util_1 = require("node:util");
const vscode_uri_1 = require("vscode-uri");
function not(predicate) {
    return (x) => !predicate(x);
}
function or(...predicates) {
    return (x) => predicates.some((predicate) => predicate(x));
}
function and(...predicates) {
    return (x) => predicates.every((predicate) => predicate(x));
}
function unique(array) {
    return array.filter((item, index) => {
        return array.findIndex((step) => (0, node_util_1.isDeepStrictEqual)(item, step)) === index;
    });
}
// todo: replace with `Object.groupBy` when minimum node is bumped to v22
function groupBy(array, predicate) {
    return array.reduce((acc, item) => {
        const key = predicate(item);
        acc[key] ??= [];
        acc[key].push(item);
        return acc;
    }, {});
}
function clamp(num, min, max) {
    return Math.max(min, Math.min(max, num));
}
function urlToPath(stringUrl) {
    const url = vscode_uri_1.URI.parse(stringUrl);
    if (url.scheme !== 'file') {
        return null;
    }
    return url.fsPath.replace(/\\/g, '/');
}
function pathToUrl(path) {
    return vscode_uri_1.URI.file(path).toString();
}
const backslashRegEx = /\\/g;
/**
 * Some paths (on windows) start with a upper case driver letter, some don't.
 * This is normalized here.
 */
function normalizePath(path) {
    return normalizeDriveLetter(path.replace(backslashRegEx, '/'));
}
function normalizeDriveLetter(path) {
    if (path.charCodeAt(1) !== /*:*/ 58) {
        return path;
    }
    const driveLetter = path.charCodeAt(0);
    if (driveLetter >= /*A*/ 65 && driveLetter <= /*Z*/ 90) {
        return String.fromCharCode(driveLetter + 32) + path.slice(1);
    }
    return path;
}
/**
 * URIs coming from the client could be encoded in a different
 * way than expected / than the internal services create them.
 * This normalizes them to be the same as the internally generated ones.
 */
function normalizeUri(uri) {
    return vscode_uri_1.URI.parse(uri).toString();
}
/**
 * Given a path like foo/bar or foo/bar.svelte , returns its last path
 * (bar or bar.svelte in this example).
 */
function getLastPartOfPath(path) {
    const lastSlash = path.lastIndexOf('/');
    const lastBackslash = path.lastIndexOf('\\');
    const lastSeparator = Math.max(lastSlash, lastBackslash);
    return lastSeparator === -1 ? path : path.slice(lastSeparator + 1);
}
/**
 * Map or keep original (passthrough) if the mapper returns undefined.
 */
function passMap(array, mapper) {
    return array.map((x) => {
        const mapped = mapper(x);
        return mapped === undefined ? x : mapped;
    });
}
function isInRange(range, positionToTest) {
    return (isBeforeOrEqualToPosition(range.end, positionToTest) &&
        isBeforeOrEqualToPosition(positionToTest, range.start));
}
function isZeroLengthRange(range) {
    return isPositionEqual(range.start, range.end);
}
function isRangeStartAfterEnd(range) {
    return (range.end.line < range.start.line ||
        (range.end.line === range.start.line && range.end.character < range.start.character));
}
function swapRangeStartEndIfNecessary(range) {
    if (isRangeStartAfterEnd(range)) {
        const start = range.start;
        range.start = range.end;
        range.end = start;
    }
    return range;
}
function moveRangeStartToEndIfNecessary(range) {
    if (isRangeStartAfterEnd(range)) {
        range.start = range.end;
    }
    return range;
}
function isBeforeOrEqualToPosition(position, positionToTest) {
    return (positionToTest.line < position.line ||
        (positionToTest.line === position.line && positionToTest.character <= position.character));
}
function isPositionEqual(position1, position2) {
    return position1.line === position2.line && position1.character === position2.character;
}
function isNotNullOrUndefined(val) {
    return val !== undefined && val !== null;
}
/**
 * Debounces a function but cancels previous invocation only if
 * a second function determines it should.
 *
 * @param fn The function with it's argument
 * @param determineIfSame The function which determines if the previous invocation should be canceld or not
 * @param miliseconds Number of miliseconds to debounce
 */
function debounceSameArg(fn, shouldCancelPrevious, miliseconds) {
    let timeout;
    let prevArg;
    return (arg) => {
        if (shouldCancelPrevious(arg, prevArg)) {
            clearTimeout(timeout);
        }
        prevArg = arg;
        timeout = setTimeout(() => {
            fn(arg);
            prevArg = undefined;
        }, miliseconds);
    };
}
function debounce(fn, miliseconds) {
    let timeout;
    return () => {
        clearTimeout(timeout);
        timeout = setTimeout(fn, miliseconds);
    };
}
/**
 * Debounces a function but also waits at minimum the specified number of miliseconds until
 * the next invocation. This avoids needless calls when a synchronous call (like diagnostics)
 * took too long and the whole timeout of the next call was eaten up already.
 *
 * @param fn The function
 * @param miliseconds Number of miliseconds to debounce/throttle
 */
function debounceThrottle(fn, miliseconds) {
    let timeout;
    let lastInvocation = Date.now() - miliseconds;
    function maybeCall() {
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            if (Date.now() - lastInvocation < miliseconds) {
                maybeCall();
                return;
            }
            fn();
            lastInvocation = Date.now();
        }, miliseconds);
    }
    return maybeCall;
}
/**
 * Like str.lastIndexOf, but for regular expressions. Note that you need to provide the g-flag to your RegExp!
 */
function regexLastIndexOf(text, regex, endPos) {
    if (endPos === undefined) {
        endPos = text.length;
    }
    else if (endPos < 0) {
        endPos = 0;
    }
    const stringToWorkWith = text.substring(0, endPos + 1);
    let lastIndexOf = -1;
    let result = null;
    while ((result = regex.exec(stringToWorkWith)) !== null) {
        lastIndexOf = result.index;
    }
    return lastIndexOf;
}
/**
 * Like str.indexOf, but for regular expressions.
 */
function regexIndexOf(text, regex, startPos) {
    if (startPos === undefined || startPos < 0) {
        startPos = 0;
    }
    const stringToWorkWith = text.substring(startPos);
    const result = regex.exec(stringToWorkWith);
    return result?.index ?? -1;
}
/**
 * Get all matches of a regexp.
 */
function getRegExpMatches(regex, str) {
    const matches = [];
    let match;
    while ((match = regex.exec(str))) {
        matches.push(match);
    }
    return matches;
}
/**
 * Function to modify each line of a text, preserving the line break style (`\n` or `\r\n`)
 */
function modifyLines(text, replacementFn) {
    let idx = 0;
    return text
        .split('\r\n')
        .map((l1) => l1
        .split('\n')
        .map((line) => replacementFn(line, idx++))
        .join('\n'))
        .join('\r\n');
}
function isSamePosition(position, another) {
    return position.line === another.line && position.character === another.character;
}
/**
 * Like array.filter, but asynchronous
 */
async function filterAsync(array, predicate) {
    const fail = Symbol();
    return (await Promise.all(array.map(async (item, idx) => ((await predicate(item, idx)) ? item : fail)))).filter((i) => i !== fail);
}
function getIndent(text) {
    return /^[ |\t]+/.exec(text)?.[0] ?? '';
}
function possiblyComponent(nodeOrTagName) {
    const charCode = (typeof nodeOrTagName === 'object' ? nodeOrTagName.tag : nodeOrTagName)?.charCodeAt(0);
    return charCode !== undefined && charCode >= /* A */ 65 && charCode <= /* Z */ 90;
}
/**
 * If the object if it has entries, else undefined
 */
function returnObjectIfHasKeys(obj) {
    if (Object.keys(obj || {}).length > 0) {
        return obj;
    }
}
const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g;
/**
 * adopted from https://github.com/microsoft/TypeScript/blob/8192d550496d884263e292488e325ae96893dc78/src/compiler/core.ts#L1769-L1807
 * see the comment there about why we can't just use String.prototype.toLowerCase() here
 */
function toFileNameLowerCase(x) {
    return fileNameLowerCaseRegExp.test(x) ? x.replace(fileNameLowerCaseRegExp, toLowerCase) : x;
}
function toLowerCase(x) {
    return x.toLowerCase();
}
/**
 * adopted from https://github.com/microsoft/TypeScript/blob/8192d550496d884263e292488e325ae96893dc78/src/compiler/core.ts#L2312
 */
function createGetCanonicalFileName(useCaseSensitiveFileNames) {
    return useCaseSensitiveFileNames ? identity : toFileNameLowerCase;
}
function identity(x) {
    return x;
}
function memoize(callback) {
    let value;
    let callbackInner = callback;
    return () => {
        if (callbackInner) {
            value = callback();
            callbackInner = undefined;
        }
        return value;
    };
}
function removeLineWithString(str, keyword) {
    const lines = str.split('\n');
    const filteredLines = lines.filter((line) => !line.includes(keyword));
    return filteredLines.join('\n');
}
/**
 * Traverses a string and returns the index of the end character, taking into account quotes, curlies and generic tags.
 */
function traverseTypeString(str, start, endChar) {
    let singleQuoteOpen = false;
    let doubleQuoteOpen = false;
    let countCurlyBrace = 0;
    let countAngleBracket = 0;
    for (let i = start; i < str.length; i++) {
        const char = str[i];
        if (!doubleQuoteOpen && char === "'") {
            singleQuoteOpen = !singleQuoteOpen;
        }
        else if (!singleQuoteOpen && char === '"') {
            doubleQuoteOpen = !doubleQuoteOpen;
        }
        else if (!doubleQuoteOpen && !singleQuoteOpen) {
            if (char === '{') {
                countCurlyBrace++;
            }
            else if (char === '}') {
                countCurlyBrace--;
            }
            else if (char === '<') {
                countAngleBracket++;
            }
            else if (char === '>') {
                countAngleBracket--;
            }
        }
        if (!singleQuoteOpen &&
            !doubleQuoteOpen &&
            countCurlyBrace === 0 &&
            countAngleBracket === 0 &&
            char === endChar) {
            return i;
        }
    }
    return -1;
}
//# sourceMappingURL=utils.js.map