const CharacterCodesSlash = "/".charCodeAt(0);
const CharacterCodesBackslash = "\\".charCodeAt(0);
const CharacterCodesColon = ":".charCodeAt(0);
const CharacterCodesPercent = "%".charCodeAt(0);
const CharacterCodes3 = "3".charCodeAt(0);
const CharacterCodesa = "a".charCodeAt(0);
const CharacterCodesz = "z".charCodeAt(0);
const CharacterCodesA = "A".charCodeAt(0);
const CharacterCodesZ = "Z".charCodeAt(0);
const CharacterCodesDot = ".".charCodeAt(0);
const directorySeparator = "/";
const altDirectorySeparator = "\\";
const urlSchemeSeparator = "://";
const backslashRegExp = /\\/g;
// check path for these segments: '', '.'. '..'
const relativePathSegmentRegExp = /\/\/|(?:^|\/)\.\.?(?:$|\/)/;
/**
 * Determines whether a charCode corresponds to `/` or `\\`.
 */
function isAnyDirectorySeparator(charCode) {
    return charCode === CharacterCodesSlash || charCode === CharacterCodesBackslash;
}
function isVolumeCharacter(charCode) {
    return (charCode >= CharacterCodesa && charCode <= CharacterCodesz) ||
        (charCode >= CharacterCodesA && charCode <= CharacterCodesZ);
}
function getFileUrlVolumeSeparatorEnd(url, start) {
    const ch0 = url.charCodeAt(start);
    if (ch0 === CharacterCodesColon)
        return start + 1;
    if (ch0 === CharacterCodesPercent && url.charCodeAt(start + 1) === CharacterCodes3) {
        const ch2 = url.charCodeAt(start + 2);
        if (ch2 === CharacterCodesa || ch2 === CharacterCodesA)
            return start + 3;
    }
    return -1;
}
/**
 * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
 *
 * For example:
 * ```ts
 * getRootLength("a") === 0                   // ""
 * getRootLength("/") === 1                   // "/"
 * getRootLength("c:") === 2                  // "c:"
 * getRootLength("c:d") === 0                 // ""
 * getRootLength("c:/") === 3                 // "c:/"
 * getRootLength("c:\\") === 3                // "c:\\"
 * getRootLength("//server") === 7            // "//server"
 * getRootLength("//server/share") === 8      // "//server/"
 * getRootLength("\\\\server") === 7          // "\\\\server"
 * getRootLength("\\\\server\\share") === 8   // "\\\\server\\"
 * getRootLength("file:///path") === 8        // "file:///"
 * getRootLength("file:///c:") === 10         // "file:///c:"
 * getRootLength("file:///c:d") === 8         // "file:///"
 * getRootLength("file:///c:/path") === 11    // "file:///c:/"
 * getRootLength("file://server") === 13      // "file://server"
 * getRootLength("file://server/path") === 14 // "file://server/"
 * getRootLength("http://server") === 13      // "http://server"
 * getRootLength("http://server/path") === 14 // "http://server/"
 * ```
 *
 * @internal
 */
export function getRootLength(path) {
    const rootLength = getEncodedRootLength(path);
    return rootLength < 0 ? ~rootLength : rootLength;
}
/**
 * Returns length of the root part of a path or URL (i.e. length of "/", "x:/", "//server/share/, file:///user/files").
 * If the root is part of a URL, the twos-complement of the root length is returned.
 */
function getEncodedRootLength(path) {
    if (!path)
        return 0;
    const ch0 = path.charCodeAt(0);
    // POSIX or UNC
    if (ch0 === CharacterCodesSlash || ch0 === CharacterCodesBackslash) {
        if (path.charCodeAt(1) !== ch0)
            return 1; // POSIX: "/" (or non-normalized "\")
        const p1 = path.indexOf(ch0 === CharacterCodesSlash ? directorySeparator : altDirectorySeparator, 2);
        if (p1 < 0)
            return path.length; // UNC: "//server" or "\\server"
        return p1 + 1; // UNC: "//server/" or "\\server\"
    }
    // DOS
    if (isVolumeCharacter(ch0) && path.charCodeAt(1) === CharacterCodesColon) {
        const ch2 = path.charCodeAt(2);
        if (ch2 === CharacterCodesSlash || ch2 === CharacterCodesBackslash)
            return 3; // DOS: "c:/" or "c:\"
        if (path.length === 2)
            return 2; // DOS: "c:" (but not "c:d")
    }
    // URL
    const schemeEnd = path.indexOf(urlSchemeSeparator);
    if (schemeEnd !== -1) {
        const authorityStart = schemeEnd + urlSchemeSeparator.length;
        const authorityEnd = path.indexOf(directorySeparator, authorityStart);
        if (authorityEnd !== -1) { // URL: "file:///", "file://server/", "file://server/path"
            // For local "file" URLs, include the leading DOS volume (if present).
            // Per https://www.ietf.org/rfc/rfc1738.txt, a host of "" or "localhost" is a
            // special case interpreted as "the machine from which the URL is being interpreted".
            const scheme = path.slice(0, schemeEnd);
            const authority = path.slice(authorityStart, authorityEnd);
            if (scheme === "file" && (authority === "" || authority === "localhost") &&
                isVolumeCharacter(path.charCodeAt(authorityEnd + 1))) {
                const volumeSeparatorEnd = getFileUrlVolumeSeparatorEnd(path, authorityEnd + 2);
                if (volumeSeparatorEnd !== -1) {
                    if (path.charCodeAt(volumeSeparatorEnd) === CharacterCodesSlash) {
                        // URL: "file:///c:/", "file://localhost/c:/", "file:///c%3a/", "file://localhost/c%3a/"
                        return ~(volumeSeparatorEnd + 1);
                    }
                    if (volumeSeparatorEnd === path.length) {
                        // URL: "file:///c:", "file://localhost/c:", "file:///c$3a", "file://localhost/c%3a"
                        // but not "file:///c:d" or "file:///c%3ad"
                        return ~volumeSeparatorEnd;
                    }
                }
            }
            return ~(authorityEnd + 1); // URL: "file://server/", "http://server/"
        }
        return ~path.length; // URL: "file://server", "http://server"
    }
    // relative
    return 0;
}
export function getPathComponents(path) {
    return pathComponents(path, getRootLength(path));
}
function pathComponents(path, rootLength) {
    const root = path.substring(0, rootLength);
    const rest = path.substring(rootLength).split("/");
    if (rest.length && !lastOrUndefined(rest))
        rest.pop();
    return [root, ...rest];
}
function lastOrUndefined(array) {
    return array.length ? array[array.length - 1] : undefined;
}
/**
 * Determines whether a path has a trailing separator (`/` or `\\`).
 */
export function hasTrailingDirectorySeparator(path) {
    return path.length > 0 && isAnyDirectorySeparator(path.charCodeAt(path.length - 1));
}
/**
 * Removes a trailing directory separator from a path, if it does not already have one.
 */
export function removeTrailingDirectorySeparator(path) {
    if (hasTrailingDirectorySeparator(path)) {
        return path.substr(0, path.length - 1);
    }
    return path;
}
/**
 * Adds a trailing directory separator to a path, if it does not already have one.
 */
export function ensureTrailingDirectorySeparator(path) {
    if (!hasTrailingDirectorySeparator(path)) {
        return path + directorySeparator;
    }
    return path;
}
/**
 * Normalize path separators, converting `\\` into `/`.
 */
export function normalizeSlashes(path) {
    return path.includes("\\")
        ? path.replace(backslashRegExp, directorySeparator)
        : path;
}
/**
 * Combines paths. If a path is absolute, it replaces any previous path. Relative paths are not simplified.
 */
export function combinePaths(path, ...paths) {
    if (path)
        path = normalizeSlashes(path);
    for (let relativePath of paths) {
        if (!relativePath)
            continue;
        relativePath = normalizeSlashes(relativePath);
        if (!path || getRootLength(relativePath) !== 0) {
            path = relativePath;
        }
        else {
            path = ensureTrailingDirectorySeparator(path) + relativePath;
        }
    }
    return path;
}
function simpleNormalizePath(path) {
    // Most paths don't require normalization
    if (!relativePathSegmentRegExp.test(path)) {
        return path;
    }
    // Some paths only require cleanup of `/./` or leading `./`
    let simplified = path.replace(/\/\.\//g, "/");
    if (simplified.startsWith("./")) {
        simplified = simplified.slice(2);
    }
    if (simplified !== path) {
        path = simplified;
        if (!relativePathSegmentRegExp.test(path)) {
            return path;
        }
    }
    return undefined;
}
/**
 * Returns the normalized absolute path, resolving `.` and `..` segments.
 */
export function getNormalizedAbsolutePath(path, currentDirectory) {
    let rootLength = getRootLength(path);
    if (rootLength === 0 && currentDirectory) {
        path = combinePaths(currentDirectory, path);
        rootLength = getRootLength(path);
    }
    else {
        // combinePaths normalizes slashes, so not necessary in the other branch
        path = normalizeSlashes(path);
    }
    const simpleNormalized = simpleNormalizePath(path);
    if (simpleNormalized !== undefined) {
        return simpleNormalized.length > rootLength ? removeTrailingDirectorySeparator(simpleNormalized) : simpleNormalized;
    }
    const length = path.length;
    const root = path.substring(0, rootLength);
    // `normalized` is only initialized once `path` is determined to be non-normalized
    let normalized;
    let index = rootLength;
    let segmentStart = index;
    let normalizedUpTo = index;
    let seenNonDotDotSegment = rootLength !== 0;
    while (index < length) {
        // At beginning of segment
        segmentStart = index;
        let ch = path.charCodeAt(index);
        while (ch === CharacterCodesSlash && index + 1 < length) {
            index++;
            ch = path.charCodeAt(index);
        }
        if (index > segmentStart) {
            // Seen superfluous separator
            normalized ??= path.substring(0, segmentStart - 1);
            segmentStart = index;
        }
        // Past any superfluous separators
        let segmentEnd = path.indexOf(directorySeparator, index + 1);
        if (segmentEnd === -1) {
            segmentEnd = length;
        }
        const segmentLength = segmentEnd - segmentStart;
        if (segmentLength === 1 && path.charCodeAt(index) === CharacterCodesDot) {
            // "." segment (skip)
            normalized ??= path.substring(0, normalizedUpTo);
        }
        else if (segmentLength === 2 && path.charCodeAt(index) === CharacterCodesDot && path.charCodeAt(index + 1) === CharacterCodesDot) {
            // ".." segment
            if (!seenNonDotDotSegment) {
                if (normalized !== undefined) {
                    normalized += normalized.length === rootLength ? ".." : "/..";
                }
                else {
                    normalizedUpTo = index + 2;
                }
            }
            else if (normalized === undefined) {
                if (normalizedUpTo - 2 >= 0) {
                    normalized = path.substring(0, Math.max(rootLength, path.lastIndexOf(directorySeparator, normalizedUpTo - 2)));
                }
                else {
                    normalized = path.substring(0, normalizedUpTo);
                }
            }
            else {
                const lastSlash = normalized.lastIndexOf(directorySeparator);
                if (lastSlash !== -1) {
                    normalized = normalized.substring(0, Math.max(rootLength, lastSlash));
                }
                else {
                    normalized = root;
                }
                if (normalized.length === rootLength) {
                    seenNonDotDotSegment = rootLength !== 0;
                }
            }
        }
        else if (normalized !== undefined) {
            if (normalized.length !== rootLength) {
                normalized += directorySeparator;
            }
            seenNonDotDotSegment = true;
            normalized += path.substring(segmentStart, segmentEnd);
        }
        else {
            seenNonDotDotSegment = true;
            normalizedUpTo = segmentEnd;
        }
        index = segmentEnd + 1;
    }
    return normalized ?? (length > rootLength ? removeTrailingDirectorySeparator(path) : path);
}
/**
 * Normalizes a path, resolving `.` and `..` segments and converting backslashes to forward slashes.
 */
export function normalizePath(path) {
    path = normalizeSlashes(path);
    let normalized = simpleNormalizePath(path);
    if (normalized !== undefined) {
        return normalized;
    }
    normalized = getNormalizedAbsolutePath(path, "");
    return normalized && hasTrailingDirectorySeparator(path) ? ensureTrailingDirectorySeparator(normalized) : normalized;
}
/**
 * Determines whether a path is an absolute disk path (e.g. starts with `/`, or a DOS path
 * like `c:`, `c:\\` or `c:/`).
 */
export function isRootedDiskPath(path) {
    return getEncodedRootLength(path) > 0;
}
/**
 * Converts a file name to a normalized path.
 *
 * @param fileName The file name to convert
 * @param basePath The base path to use for relative file names
 * @param getCanonicalFileName A function to get the canonical file name (e.g., toLowerCase for case-insensitive systems)
 * @returns The normalized path
 */
export function toPath(fileName, basePath, getCanonicalFileName) {
    const nonCanonicalizedPath = isRootedDiskPath(fileName)
        ? normalizePath(fileName)
        : getNormalizedAbsolutePath(fileName, basePath);
    return getCanonicalFileName(nonCanonicalizedPath);
}
/**
 * Creates a getCanonicalFileName function based on case sensitivity.
 */
export function createGetCanonicalFileName(useCaseSensitiveFileNames) {
    return useCaseSensitiveFileNames ? identity : toLowerCase;
}
function identity(x) {
    return x;
}
function toLowerCase(s) {
    return s.toLowerCase();
}
const bundledScheme = "bundled:///";
/**
 * Returns true if the path refers to a bundled library file.
 */
export function isBundled(path) {
    return path.startsWith(bundledScheme);
}
/**
 * Returns true if the file name represents a dynamic/virtual file
 * that doesn't exist on disk (e.g., untitled files with paths like "^/untitled/...").
 */
export function isDynamicFileName(fileName) {
    return fileName.startsWith("^/");
}
/**
 * Splits a Windows volume (e.g., "c:") from the rest of the path.
 * Returns [volume, rest, ok] where ok is true if a volume was found.
 */
export function splitVolumePath(path) {
    if (path.length >= 2 && isVolumeCharacter(path.charCodeAt(0)) && path.charCodeAt(1) === CharacterCodesColon) {
        return [path.substring(0, 2).toLowerCase(), path.substring(2), true];
    }
    return ["", path, false];
}
// Characters that need extra escaping in URI path segments
// https://github.com/microsoft/vscode-uri/blob/edfdccd976efaf4bb8fdeca87e97c47257721729/src/uri.ts#L455
const extraEscapeChars = {
    ":": "%3A",
    "/": "%2F",
    "?": "%3F",
    "#": "%23",
    "[": "%5B",
    "]": "%5D",
    "@": "%40",
    "!": "%21",
    "$": "%24",
    "&": "%26",
    "'": "%27",
    "(": "%28",
    ")": "%29",
    "*": "%2A",
    "+": "%2B",
    ",": "%2C",
    ";": "%3B",
    "=": "%3D",
    " ": "%20",
};
function extraEscape(s) {
    let result = s;
    for (const [char, escape] of Object.entries(extraEscapeChars)) {
        result = result.replaceAll(char, escape);
    }
    return result;
}
/**
 * Converts a file name to a document URI.
 *
 * @example
 * fileNameToDocumentURI("/path/to/file.ts") === "file:///path/to/file.ts"
 * fileNameToDocumentURI("c:/path/to/file.ts") === "file:///c%3A/path/to/file.ts"
 * fileNameToDocumentURI("^/untitled/ts-nul-authority/Untitled-1") === "untitled:Untitled-1"
 * fileNameToDocumentURI("^/vscode-vfs/github/microsoft/typescript-go/file.ts") === "vscode-vfs://github/microsoft/typescript-go/file.ts"
 */
export function fileNameToDocumentURI(fileName) {
    // Bundled files are returned as-is
    if (isBundled(fileName)) {
        return fileName;
    }
    // Dynamic/virtual files (untitled, vscode-vfs, etc.) need special handling
    if (isDynamicFileName(fileName)) {
        // Format: ^/scheme/authority/path
        const withoutPrefix = fileName.substring(2); // Remove "^/"
        const firstSlash = withoutPrefix.indexOf("/");
        if (firstSlash === -1) {
            throw new Error("invalid file name: " + fileName);
        }
        const scheme = withoutPrefix.substring(0, firstSlash);
        const rest = withoutPrefix.substring(firstSlash + 1);
        const secondSlash = rest.indexOf("/");
        if (secondSlash === -1) {
            throw new Error("invalid file name: " + fileName);
        }
        const authority = rest.substring(0, secondSlash);
        const path = rest.substring(secondSlash + 1);
        // ts-nul-authority is a placeholder for URIs without an authority
        if (authority === "ts-nul-authority") {
            return scheme + ":" + path;
        }
        return scheme + "://" + authority + "/" + path;
    }
    // Regular file path - convert to file:// URI
    let [volume, rest] = splitVolumePath(fileName);
    if (volume !== "") {
        volume = "/" + extraEscape(volume);
    }
    // Remove leading // for UNC paths (already handled by file://)
    if (rest.startsWith("//")) {
        rest = rest.substring(2);
    }
    const parts = rest.split("/");
    const encodedParts = parts.map(part => extraEscape(encodeURIComponent(part)));
    return "file://" + volume + encodedParts.join("/");
}
/**
 * Converts a document URI to a file name.
 *
 * @example
 * documentURIToFileName("file:///path/to/file.ts") === "/path/to/file.ts"
 * documentURIToFileName("file:///c%3A/path/to/file.ts") === "c:/path/to/file.ts"
 * documentURIToFileName("untitled:Untitled-1") === "^/untitled/ts-nul-authority/Untitled-1"
 * documentURIToFileName("vscode-vfs://github/microsoft/typescript-go/file.ts") === "^/vscode-vfs/github/microsoft/typescript-go/file.ts"
 */
export function documentURIToFileName(uri) {
    // Bundled files are returned as-is
    if (isBundled(uri)) {
        return uri;
    }
    // Handle file:// URIs
    if (uri.startsWith("file://")) {
        let parsed;
        try {
            parsed = new URL(uri);
        }
        catch {
            throw new Error("invalid file URI: " + uri);
        }
        // UNC path: file://server/share/...
        if (parsed.host !== "") {
            return "//" + parsed.host + parsed.pathname;
        }
        // Local file - fix Windows path by removing leading slash before volume
        const path = decodeURIComponent(parsed.pathname);
        if (path.length >= 3 && path.charCodeAt(0) === CharacterCodesSlash) {
            const [volume, rest, ok] = splitVolumePath(path.substring(1));
            if (ok) {
                return volume + rest;
            }
        }
        return path;
    }
    // Leave all other URIs escaped so we can round-trip them.
    // Convert to dynamic file name format: ^/scheme/authority/path
    const colonIndex = uri.indexOf(":");
    if (colonIndex === -1) {
        throw new Error("invalid URI: " + uri);
    }
    const scheme = uri.substring(0, colonIndex);
    let path = uri.substring(colonIndex + 1);
    let authority = "ts-nul-authority";
    if (path.startsWith("//")) {
        const rest = path.substring(2);
        const slashIndex = rest.indexOf("/");
        if (slashIndex === -1) {
            throw new Error("invalid URI: " + uri);
        }
        authority = rest.substring(0, slashIndex);
        path = rest.substring(slashIndex + 1);
    }
    return "^/" + scheme + "/" + authority + "/" + path;
}
//# sourceMappingURL=path.js.map