"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortTsConfigs = exports.createWorkspace = exports.rootTsConfigNames = void 0;
const path = require("typesafe-path/posix");
const vscode = require("vscode-languageserver");
const vscode_uri_1 = require("vscode-uri");
const project_1 = require("./project");
const inferredCompilerOptions_1 = require("./utils/inferredCompilerOptions");
const uriMap_1 = require("./utils/uriMap");
const isFileInDir_1 = require("./utils/isFileInDir");
exports.rootTsConfigNames = ['tsconfig.json', 'jsconfig.json'];
async function createWorkspace(context) {
    const fileNameToUri = context.workspaces.server.runtimeEnv.fileNameToUri;
    const uriToFileName = context.workspaces.server.runtimeEnv.uriToFileName;
    let inferredProject;
    const sys = context.workspaces.fileSystemHost?.getWorkspaceFileSystem(context.rootUri);
    const documentRegistry = sys ? context.workspaces.ts?.createDocumentRegistry(sys.useCaseSensitiveFileNames, uriToFileName(context.rootUri.toString())) : undefined;
    const projects = (0, uriMap_1.createUriMap)(fileNameToUri);
    const rootTsConfigs = new Set();
    const searchedDirs = new Set();
    const disposeTsConfigWatch = context.workspaces.fileSystemHost?.onDidChangeWatchedFiles(({ changes }) => {
        for (const change of changes) {
            if (exports.rootTsConfigNames.includes(change.uri.substring(change.uri.lastIndexOf('/') + 1))) {
                if (change.type === vscode.FileChangeType.Created) {
                    if ((0, isFileInDir_1.isFileInDir)(uriToFileName(change.uri), uriToFileName(context.rootUri.toString()))) {
                        rootTsConfigs.add(uriToFileName(change.uri));
                    }
                }
                else if ((change.type === vscode.FileChangeType.Changed || change.type === vscode.FileChangeType.Deleted) && projects.uriHas(change.uri)) {
                    if (change.type === vscode.FileChangeType.Deleted) {
                        rootTsConfigs.delete(uriToFileName(change.uri));
                    }
                    const project = projects.uriGet(change.uri);
                    projects.uriDelete(change.uri);
                    project?.then(project => project.dispose());
                }
            }
        }
    });
    return {
        projects,
        documentRegistry,
        getProjectAndTsConfig,
        getInferredProject,
        getInferredProjectDontCreate: () => inferredProject,
        reload: clearProjects,
        dispose() {
            clearProjects();
            disposeTsConfigWatch?.();
        },
    };
    function clearProjects() {
        const _projects = [
            inferredProject,
            ...projects.values(),
        ];
        _projects.forEach(async (project) => {
            (await project)?.dispose();
        });
        inferredProject = undefined;
        projects.clear();
    }
    async function getProjectAndTsConfig(uri) {
        const tsconfig = await findMatchConfigs(vscode_uri_1.URI.parse(uri));
        if (tsconfig) {
            const project = await getProjectByCreate(tsconfig);
            return {
                tsconfig: tsconfig,
                project,
            };
        }
    }
    function getInferredProject() {
        if (!inferredProject) {
            inferredProject = (async () => {
                const inferOptions = await (0, inferredCompilerOptions_1.getInferredCompilerOptions)(context.workspaces.configurationHost);
                return (0, project_1.createProject)({
                    workspace: context,
                    rootUri: context.rootUri,
                    tsConfig: inferOptions,
                    documentRegistry,
                });
            })();
        }
        return inferredProject;
    }
    async function findMatchConfigs(uri) {
        const filePath = uriToFileName(uri.toString());
        let dir = path.dirname(filePath);
        while (true) {
            if (searchedDirs.has(dir)) {
                break;
            }
            searchedDirs.add(dir);
            for (const tsConfigName of exports.rootTsConfigNames) {
                const tsconfigPath = path.join(dir, tsConfigName);
                if (sys?.fileExists(tsconfigPath)) {
                    rootTsConfigs.add(tsconfigPath);
                }
            }
            dir = path.dirname(dir);
        }
        await prepareClosestootParsedCommandLine();
        return await findDirectIncludeTsconfig() ?? await findIndirectReferenceTsconfig();
        async function prepareClosestootParsedCommandLine() {
            let matches = [];
            for (const rootTsConfig of rootTsConfigs) {
                if ((0, isFileInDir_1.isFileInDir)(uriToFileName(uri.toString()), path.dirname(rootTsConfig))) {
                    matches.push(rootTsConfig);
                }
            }
            matches = matches.sort((a, b) => sortTsConfigs(uriToFileName(uri.toString()), a, b));
            if (matches.length) {
                await getParsedCommandLine(matches[0]);
            }
        }
        function findDirectIncludeTsconfig() {
            return findTsconfig(async (tsconfig) => {
                const map = (0, uriMap_1.createUriMap)(fileNameToUri);
                const parsedCommandLine = await getParsedCommandLine(tsconfig);
                for (const fileName of parsedCommandLine?.fileNames ?? []) {
                    map.pathSet(fileName, true);
                }
                return map.uriHas(uri.toString());
            });
        }
        function findIndirectReferenceTsconfig() {
            return findTsconfig(async (tsconfig) => {
                const project = await projects.pathGet(tsconfig);
                const ls = project?.getLanguageServiceDontCreate();
                const validDoc = ls?.context.typescript?.languageService.getProgram()?.getSourceFile(uriToFileName(uri.toString()));
                return !!validDoc;
            });
        }
        async function findTsconfig(match) {
            const checked = new Set();
            for (const rootTsConfig of [...rootTsConfigs].sort((a, b) => sortTsConfigs(uriToFileName(uri.toString()), a, b))) {
                const project = await projects.pathGet(rootTsConfig);
                if (project) {
                    let chains = await getReferencesChains(project.getParsedCommandLine(), rootTsConfig, []);
                    if (context.workspaces.initOptions.reverseConfigFilePriority) {
                        chains = chains.reverse();
                    }
                    for (const chain of chains) {
                        for (let i = chain.length - 1; i >= 0; i--) {
                            const tsconfig = chain[i];
                            if (checked.has(tsconfig))
                                continue;
                            checked.add(tsconfig);
                            if (await match(tsconfig)) {
                                return tsconfig;
                            }
                        }
                    }
                }
            }
        }
        async function getReferencesChains(parsedCommandLine, tsConfig, before) {
            if (parsedCommandLine.projectReferences?.length) {
                const newChains = [];
                for (const projectReference of parsedCommandLine.projectReferences) {
                    let tsConfigPath = projectReference.path.replace(/\\/g, '/');
                    // fix https://github.com/johnsoncodehk/volar/issues/712
                    if (sys && !sys.fileExists(tsConfigPath)) {
                        const newTsConfigPath = path.join(tsConfigPath, 'tsconfig.json');
                        const newJsConfigPath = path.join(tsConfigPath, 'jsconfig.json');
                        if (sys.fileExists(newTsConfigPath)) {
                            tsConfigPath = newTsConfigPath;
                        }
                        else if (sys.fileExists(newJsConfigPath)) {
                            tsConfigPath = newJsConfigPath;
                        }
                    }
                    const beforeIndex = before.indexOf(tsConfigPath); // cycle
                    if (beforeIndex >= 0) {
                        newChains.push(before.slice(0, Math.max(beforeIndex, 1)));
                    }
                    else {
                        const referenceParsedCommandLine = await getParsedCommandLine(tsConfigPath);
                        if (referenceParsedCommandLine) {
                            for (const chain of await getReferencesChains(referenceParsedCommandLine, tsConfigPath, [...before, tsConfig])) {
                                newChains.push(chain);
                            }
                        }
                    }
                }
                return newChains;
            }
            else {
                return [[...before, tsConfig]];
            }
        }
        async function getParsedCommandLine(tsConfig) {
            const project = await getProjectByCreate(tsConfig);
            return project?.getParsedCommandLine();
        }
    }
    function getProjectByCreate(_tsConfig) {
        const tsConfig = _tsConfig.replace(/\\/g, '/');
        let project = projects.pathGet(tsConfig);
        if (!project && documentRegistry) {
            project = (0, project_1.createProject)({
                workspace: context,
                rootUri: vscode_uri_1.URI.parse(fileNameToUri(path.dirname(tsConfig))),
                tsConfig,
                documentRegistry,
            });
            projects.pathSet(tsConfig, project);
        }
        return project;
    }
}
exports.createWorkspace = createWorkspace;
function sortTsConfigs(file, a, b) {
    const inA = (0, isFileInDir_1.isFileInDir)(file, path.dirname(a));
    const inB = (0, isFileInDir_1.isFileInDir)(file, path.dirname(b));
    if (inA !== inB) {
        const aWeight = inA ? 1 : 0;
        const bWeight = inB ? 1 : 0;
        return bWeight - aWeight;
    }
    const aLength = a.split('/').length;
    const bLength = b.split('/').length;
    if (aLength === bLength) {
        const aWeight = path.basename(a) === 'tsconfig.json' ? 1 : 0;
        const bWeight = path.basename(b) === 'tsconfig.json' ? 1 : 0;
        return bWeight - aWeight;
    }
    return bLength - aLength;
}
exports.sortTsConfigs = sortTsConfigs;
//# sourceMappingURL=workspace.js.map