"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.register = void 0;
const jsonc = __importStar(require("jsonc-parser"));
const minimatch_1 = require("minimatch");
const vscode = __importStar(require("@volar/language-service"));
const vscode_uri_1 = require("vscode-uri");
function mapChildren(node, f) {
    return node && node.type === 'array' && node.children
        ? node.children.map(f)
        : [];
}
function register(ctx) {
    const patterns = [
        '**/[jt]sconfig.json',
        '**/[jt]sconfig.*.json',
    ];
    const languages = ['json', 'jsonc'];
    return {
        provideDocumentLinks,
        async resolve(link, _token) {
            const data = link.data;
            if (data) {
                const tsconfigPath = await getTsconfigPath(vscode_uri_1.Utils.dirname(vscode_uri_1.URI.parse(data.resourceUri)), data.extendsValue);
                if (tsconfigPath === undefined) {
                    // console.error(vscode.l10n.t("Failed to resolve {0} as module", data.extendsValue));
                }
                link.target = tsconfigPath?.toString();
            }
            return link;
        },
    };
    function provideDocumentLinks(document, _token) {
        const match = languages.includes(document.languageId) && patterns.some(pattern => (0, minimatch_1.minimatch)(document.uri, pattern));
        if (!match) {
            return [];
        }
        const root = jsonc.parseTree(document.getText());
        if (!root) {
            return [];
        }
        const links = [
            getExtendsLink(document, root),
            ...getFilesLinks(document, root),
            ...getReferencesLinks(document, root)
        ];
        return links.filter(link => !!link);
    }
    function getExtendsLink(document, root) {
        const extendsNode = jsonc.findNodeAtLocation(root, ['extends']);
        if (!isPathValue(extendsNode)) {
            return undefined;
        }
        const extendsValue = extendsNode.value;
        if (extendsValue.startsWith('/')) {
            return undefined;
        }
        const args = {
            resourceUri: document.uri,
            extendsValue: extendsValue
        };
        const link = vscode.DocumentLink.create(getRange(document, extendsNode), undefined, args);
        // link.tooltip = vscode.l10n.t("Follow link");
        link.tooltip = "Follow link";
        return link;
    }
    function getFilesLinks(document, root) {
        return mapChildren(jsonc.findNodeAtLocation(root, ['files']), child => pathNodeToLink(document, child));
    }
    function getReferencesLinks(document, root) {
        return mapChildren(jsonc.findNodeAtLocation(root, ['references']), child => {
            const pathNode = jsonc.findNodeAtLocation(child, ['path']);
            if (!isPathValue(pathNode)) {
                return undefined;
            }
            return vscode.DocumentLink.create(getRange(document, pathNode), pathNode.value.endsWith('.json')
                ? getFileTarget(document, pathNode)
                : getFolderTarget(document, pathNode));
        });
    }
    function pathNodeToLink(document, node) {
        return isPathValue(node)
            ? vscode.DocumentLink.create(getRange(document, node), getFileTarget(document, node))
            : undefined;
    }
    function isPathValue(extendsNode) {
        return extendsNode
            && extendsNode.type === 'string'
            && extendsNode.value
            && !extendsNode.value.includes('*'); // don't treat globs as links.
    }
    function getFileTarget(document, node) {
        return vscode_uri_1.Utils.joinPath(vscode_uri_1.Utils.dirname(vscode_uri_1.URI.parse(document.uri)), node.value).toString();
    }
    function getFolderTarget(document, node) {
        return vscode_uri_1.Utils.joinPath(vscode_uri_1.Utils.dirname(vscode_uri_1.URI.parse(document.uri)), node.value, 'tsconfig.json').toString();
    }
    function getRange(document, node) {
        const offset = node.offset;
        const start = document.positionAt(offset + 1);
        const end = document.positionAt(offset + (node.length - 1));
        return vscode.Range.create(start, end);
    }
    async function resolveNodeModulesPath(baseDirUri, pathCandidates) {
        let currentUri = baseDirUri;
        const baseCandidate = pathCandidates[0];
        const sepIndex = baseCandidate.startsWith('@') ? 2 : 1;
        const moduleBasePath = baseCandidate.split('/').slice(0, sepIndex).join('/');
        while (true) {
            const moduleAbsoluteUrl = vscode_uri_1.Utils.joinPath(currentUri, 'node_modules', moduleBasePath);
            let moduleStat;
            try {
                moduleStat = await ctx.fileSystemProvider?.stat(moduleAbsoluteUrl.toString());
            }
            catch (err) {
                // noop
            }
            if (moduleStat && moduleStat.type === 2 /* Directory */) {
                for (const uriCandidate of pathCandidates
                    .map((relativePath) => relativePath.split('/').slice(sepIndex).join('/'))
                    // skip empty paths within module
                    .filter(Boolean)
                    .map((relativeModulePath) => vscode_uri_1.Utils.joinPath(moduleAbsoluteUrl, relativeModulePath))) {
                    if (await exists(uriCandidate)) {
                        return uriCandidate;
                    }
                }
                // Continue to looking for potentially another version
            }
            const oldUri = currentUri;
            currentUri = vscode_uri_1.Utils.joinPath(currentUri, '..');
            // Can't go next. Reached the system root
            if (oldUri.path === currentUri.path) {
                return;
            }
        }
    }
    // Reference: https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005
    /**
    * @returns Returns undefined in case of lack of result while trying to resolve from node_modules
    */
    async function getTsconfigPath(baseDirUri, extendsValue) {
        // Don't take into account a case, where tsconfig might be resolved from the root (see the reference)
        // e.g. C:/projects/shared-tsconfig/tsconfig.json (note that C: prefix is optional)
        const isRelativePath = ['./', '../'].some(str => extendsValue.startsWith(str));
        if (isRelativePath) {
            const absolutePath = vscode_uri_1.Utils.joinPath(baseDirUri, extendsValue);
            if (await exists(absolutePath) || absolutePath.path.endsWith('.json')) {
                return absolutePath;
            }
            return absolutePath.with({
                path: `${absolutePath.path}.json`
            });
        }
        // Otherwise resolve like a module
        return resolveNodeModulesPath(baseDirUri, [
            extendsValue,
            ...extendsValue.endsWith('.json') ? [] : [
                `${extendsValue}.json`,
                `${extendsValue}/tsconfig.json`,
            ]
        ]);
    }
    async function exists(resource) {
        try {
            const stat = await ctx.fileSystemProvider?.stat(resource.toString());
            // stat.type is an enum flag
            return !!(stat?.type === 1);
        }
        catch {
            return false;
        }
    }
}
exports.register = register;
//# sourceMappingURL=tsconfig.js.map