"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createProject = void 0;
const embeddedLS = require("@volar/language-service");
const path = require("typesafe-path");
const vscode = require("vscode-languageserver");
const vscode_uri_1 = require("vscode-uri");
const types_1 = require("../types");
const serverConfig_1 = require("./utils/serverConfig");
const uriMap_1 = require("./utils/uriMap");
async function createProject(context) {
    const uriToFileName = context.workspace.workspaces.server.runtimeEnv.uriToFileName;
    const fileNameToUri = context.workspace.workspaces.server.runtimeEnv.fileNameToUri;
    const sys = context.workspace.workspaces.initOptions.serverMode === types_1.ServerMode.Syntactic || !context.workspace.workspaces.fileSystemHost
        ? {
            newLine: '\n',
            useCaseSensitiveFileNames: false,
            fileExists: () => false,
            readFile: () => undefined,
            readDirectory: () => [],
            getCurrentDirectory: () => '',
            realpath: () => '',
            resolvePath: () => '',
        }
        : context.workspace.workspaces.fileSystemHost.getWorkspaceFileSystem(context.rootUri);
    let typeRootVersion = 0;
    let projectVersion = 0;
    let projectVersionUpdateTime = context.workspace.workspaces.cancelTokenHost.getMtime();
    let languageService;
    let parsedCommandLine = createParsedCommandLine(context.workspace.workspaces.ts, sys, uriToFileName(context.rootUri.toString()), context.tsConfig, context.workspace.workspaces.plugins);
    const scripts = (0, uriMap_1.createUriMap)(fileNameToUri);
    const languageServiceHost = createLanguageServiceHost();
    const disposeWatchEvent = context.workspace.workspaces.fileSystemHost?.onDidChangeWatchedFiles(params => {
        onWorkspaceFilesChanged(params.changes);
    });
    const disposeDocChange = context.workspace.workspaces.documents.onDidChangeContent(() => {
        projectVersion++;
        projectVersionUpdateTime = context.workspace.workspaces.cancelTokenHost.getMtime();
    });
    return {
        tsConfig: context.tsConfig,
        scripts,
        languageServiceHost,
        getLanguageService,
        getLanguageServiceDontCreate: () => languageService,
        getParsedCommandLine: () => parsedCommandLine,
        tryAddFile: (fileName) => {
            if (!parsedCommandLine.fileNames.includes(fileName)) {
                parsedCommandLine.fileNames.push(fileName);
                projectVersion++;
                projectVersionUpdateTime = context.workspace.workspaces.cancelTokenHost.getMtime();
            }
        },
        dispose,
    };
    function getLanguageService() {
        if (!languageService) {
            let config = (context.workspace.rootUri.scheme === 'file' ? (0, serverConfig_1.loadConfig)(context.workspace.rootUri.path, context.workspace.workspaces.initOptions.configFilePath) : {}) ?? {};
            const options = {
                modules: { typescript: context.workspace.workspaces.ts },
                uriToFileName,
                fileNameToUri,
                locale: context.workspace.workspaces.initParams.locale,
                rootUri: context.rootUri,
                capabilities: context.workspace.workspaces.initParams.capabilities,
                host: languageServiceHost,
                get config() {
                    return config;
                },
                configurationHost: context.workspace.workspaces.configurationHost,
                fileSystemProvider: context.workspace.workspaces.server.runtimeEnv.fileSystemProvide,
                fileSystemHost: context.workspace.workspaces.fileSystemHost,
                documentContext: getDocumentContext(fileNameToUri, uriToFileName, context.workspace.workspaces.ts, languageServiceHost, context.rootUri.toString()),
                schemaRequestService: async (uri) => {
                    const protocol = uri.substring(0, uri.indexOf(':'));
                    const builtInHandler = context.workspace.workspaces.server.runtimeEnv.schemaRequestHandlers[protocol];
                    if (builtInHandler) {
                        return await builtInHandler(uri) ?? '';
                    }
                    return '';
                },
            };
            const lsCtx = {
                project: context,
                options,
                sys,
                host: languageServiceHost,
            };
            for (const plugin of context.workspace.workspaces.plugins) {
                if (plugin.resolveConfig) {
                    config = plugin.resolveConfig(config, lsCtx);
                }
            }
            languageService = embeddedLS.createLanguageService(options, context.documentRegistry);
        }
        return languageService;
    }
    async function onWorkspaceFilesChanged(changes) {
        const _projectVersion = projectVersion;
        for (const change of changes) {
            const script = scripts.uriGet(change.uri);
            if (script && (change.type === vscode.FileChangeType.Changed || change.type === vscode.FileChangeType.Created)) {
                if (script.version >= 0) {
                    script.version = -1;
                }
                else {
                    script.version--;
                }
            }
            else if (script && change.type === vscode.FileChangeType.Deleted) {
                scripts.uriDelete(change.uri);
            }
            if (script
                || change.uri.endsWith('.d.ts')
                || change.uri.endsWith('/package.json')) {
                projectVersion++;
            }
        }
        const creates = changes.filter(change => change.type === vscode.FileChangeType.Created);
        const deletes = changes.filter(change => change.type === vscode.FileChangeType.Deleted);
        if (creates.length || deletes.length) {
            parsedCommandLine = createParsedCommandLine(context.workspace.workspaces.ts, sys, uriToFileName(context.rootUri.toString()), context.tsConfig, context.workspace.workspaces.plugins);
            projectVersion++;
            typeRootVersion++;
        }
        if (_projectVersion !== projectVersion) {
            projectVersionUpdateTime = context.workspace.workspaces.cancelTokenHost.getMtime();
        }
    }
    function createLanguageServiceHost() {
        const token = {
            isCancellationRequested() {
                return context.workspace.workspaces.cancelTokenHost.getMtime() !== projectVersionUpdateTime;
            },
            throwIfCancellationRequested() { },
        };
        let host = {
            // ts
            getNewLine: () => sys.newLine,
            useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
            readFile: sys.readFile,
            writeFile: sys.writeFile,
            directoryExists: sys.directoryExists,
            getDirectories: sys.getDirectories,
            readDirectory: sys.readDirectory,
            realpath: sys.realpath,
            fileExists: sys.fileExists,
            getCurrentDirectory: () => uriToFileName(context.rootUri.toString()),
            getProjectReferences: () => parsedCommandLine.projectReferences,
            getCancellationToken: () => token,
            // custom
            getDefaultLibFileName: options => {
                if (context.workspace.workspaces.initOptions.typescript && context.workspace.workspaces.ts) {
                    try {
                        return context.workspace.workspaces.ts.getDefaultLibFilePath(options);
                    }
                    catch {
                        // web
                        const tsdk = context.workspace.workspaces.initOptions.typescript.tsdk;
                        return tsdk + '/' + context.workspace.workspaces.ts.getDefaultLibFileName(options);
                    }
                }
                return '';
            },
            getProjectVersion: () => projectVersion.toString(),
            getTypeRootsVersion: () => typeRootVersion,
            getScriptFileNames: () => parsedCommandLine.fileNames,
            getCompilationSettings: () => parsedCommandLine.options,
            getScriptVersion,
            getScriptSnapshot,
            getScriptLanguageId: (fileName) => {
                return context.workspace.workspaces.documents.data.pathGet(fileName)?.languageId;
            },
        };
        if (context.workspace.workspaces.tsLocalized) {
            host.getLocalizedDiagnosticMessages = () => context.workspace.workspaces.tsLocalized;
        }
        return host;
        function getScriptVersion(fileName) {
            const doc = context.workspace.workspaces.documents.data.pathGet(fileName);
            if (doc) {
                return doc.version.toString();
            }
            return scripts.pathGet(fileName)?.version.toString() ?? '';
        }
        function getScriptSnapshot(fileName) {
            const doc = context.workspace.workspaces.documents.data.pathGet(fileName);
            if (doc) {
                return doc.getSnapshot();
            }
            const script = scripts.pathGet(fileName);
            if (script && script.snapshotVersion === script.version) {
                return script.snapshot;
            }
            if (context.workspace.workspaces.ts && sys.fileExists(fileName)) {
                if (context.workspace.workspaces.initOptions.maxFileSize) {
                    const fileSize = sys.getFileSize?.(fileName);
                    if (fileSize !== undefined && fileSize > context.workspace.workspaces.initOptions.maxFileSize) {
                        console.warn(`IGNORING "${fileName}" because it is too large (${fileSize}bytes > ${context.workspace.workspaces.initOptions.maxFileSize}bytes)`);
                        return context.workspace.workspaces.ts.ScriptSnapshot.fromString('');
                    }
                }
                const text = sys.readFile(fileName, 'utf8');
                if (text !== undefined) {
                    const snapshot = context.workspace.workspaces.ts.ScriptSnapshot.fromString(text);
                    if (script) {
                        script.snapshot = snapshot;
                        script.snapshotVersion = script.version;
                    }
                    else {
                        scripts.pathSet(fileName, {
                            version: -1,
                            fileName: fileName,
                            snapshot: snapshot,
                            snapshotVersion: -1,
                        });
                    }
                    return snapshot;
                }
            }
        }
    }
    function dispose() {
        languageService?.dispose();
        scripts.clear();
        disposeWatchEvent?.();
        disposeDocChange();
    }
}
exports.createProject = createProject;
function createParsedCommandLine(ts, sys, rootPath, tsConfig, plugins) {
    const extraFileExtensions = plugins.map(plugin => plugin.extraFileExtensions ?? []).flat();
    if (ts) {
        try {
            let content;
            if (typeof tsConfig === 'string') {
                const config = ts.readJsonConfigFile(tsConfig, sys.readFile);
                content = ts.parseJsonSourceFileConfigFileContent(config, sys, path.dirname(tsConfig), {}, tsConfig, undefined, extraFileExtensions);
            }
            else {
                content = ts.parseJsonConfigFileContent({ files: [] }, sys, rootPath, tsConfig, path.join(rootPath, 'jsconfig.json'), undefined, extraFileExtensions);
            }
            // fix https://github.com/johnsoncodehk/volar/issues/1786
            // https://github.com/microsoft/TypeScript/issues/30457
            // patching ts server broke with outDir + rootDir + composite/incremental
            content.options.outDir = undefined;
            content.fileNames = content.fileNames.map(fileName => fileName.replace(/\\/g, '/'));
            return content;
        }
        catch {
            // will be failed if web fs host first result not ready
        }
    }
    return {
        errors: [],
        fileNames: [],
        options: {},
    };
}
function getDocumentContext(fileNameToUri, uriToFileName, ts, host, rootUri) {
    const documentContext = {
        resolveReference: (ref, base) => {
            if (ts && host) { // support tsconfig.json paths
                const isUri = base.indexOf('://') >= 0;
                const resolveResult = ts.resolveModuleName(ref, isUri ? uriToFileName(base) : base, host.getCompilationSettings(), host);
                const failedLookupLocations = typeof resolveResult === 'object' ? resolveResult.failedLookupLocations : [];
                const dirs = new Set();
                if (!failedLookupLocations) {
                    console.warn(`[volar] failedLookupLocations not exists, ts: ${ts.version}`);
                }
                for (let failed of failedLookupLocations ?? []) {
                    const fileName = path.basename(failed);
                    if (fileName === 'index.d.ts' || fileName === '*.d.ts') {
                        dirs.add(path.dirname(failed));
                    }
                    if (failed.endsWith('.d.ts')) {
                        failed = failed.substring(0, failed.length - '.d.ts'.length);
                    }
                    else {
                        continue;
                    }
                    if (host.fileExists(failed)) {
                        return isUri ? fileNameToUri(failed) : failed;
                    }
                }
                for (const dir of dirs) {
                    if (host.directoryExists?.(dir) ?? true) {
                        return isUri ? fileNameToUri(dir) : dir;
                    }
                }
            }
            // original html resolveReference
            if (ref.match(/^\w[\w\d+.-]*:/)) {
                // starts with a schema
                return ref;
            }
            if (ref[0] === '/') { // resolve absolute path against the current workspace folder
                return rootUri + ref;
            }
            const baseUri = vscode_uri_1.URI.parse(base);
            const baseUriDir = baseUri.path.endsWith('/') ? baseUri : vscode_uri_1.Utils.dirname(baseUri);
            return vscode_uri_1.Utils.resolvePath(baseUriDir, ref).toString(true);
        },
    };
    return documentContext;
}
//# sourceMappingURL=project.js.map