"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createLanguageServiceHost = createLanguageServiceHost;
const language_core_1 = require("@volar/language-core");
const path = require("path-browserify");
const utils_1 = require("../node/utils");
const resolveModuleName_1 = require("../resolveModuleName");
function createLanguageServiceHost(ts, sys, language, asScriptId, projectHost) {
    const pluginExtensions = language.plugins
        .map(plugin => plugin.typescript?.extraFileExtensions.map(ext => '.' + ext.extension) ?? [])
        .flat();
    const scriptVersions = new language_core_1.FileMap(sys.useCaseSensitiveFileNames);
    let lastProjectVersion;
    let tsProjectVersion = 0;
    let tsFileRegistry = new language_core_1.FileMap(sys.useCaseSensitiveFileNames);
    let tsFileDirRegistry = new language_core_1.FileMap(sys.useCaseSensitiveFileNames);
    let extraScriptRegistry = new language_core_1.FileMap(sys.useCaseSensitiveFileNames);
    let lastTsVirtualFileSnapshots = new Set();
    let lastOtherVirtualFileSnapshots = new Set();
    let languageServiceHost = {
        ...sys,
        getCurrentDirectory() {
            return projectHost.getCurrentDirectory();
        },
        useCaseSensitiveFileNames() {
            return sys.useCaseSensitiveFileNames;
        },
        getNewLine() {
            return sys.newLine;
        },
        getTypeRootsVersion: () => {
            return 'version' in sys ? sys.version : -1; // TODO: only update for /node_modules changes?
        },
        getDirectories(dirName) {
            return sys.getDirectories(dirName);
        },
        readDirectory(dirName, extensions, excludes, includes, depth) {
            const exts = new Set(extensions);
            for (const ext of pluginExtensions) {
                exts.add(ext);
            }
            extensions = [...exts];
            return sys.readDirectory(dirName, extensions, excludes, includes, depth);
        },
        getCompilationSettings() {
            const options = projectHost.getCompilationSettings();
            if (pluginExtensions.length) {
                options.allowNonTsExtensions ??= true;
                if (!options.allowNonTsExtensions) {
                    console.warn('`allowNonTsExtensions` must be `true`.');
                }
            }
            return options;
        },
        getLocalizedDiagnosticMessages: projectHost.getLocalizedDiagnosticMessages,
        getProjectReferences: projectHost.getProjectReferences,
        getDefaultLibFileName: options => {
            try {
                return ts.getDefaultLibFilePath(options);
            }
            catch {
                // web
                return `/node_modules/typescript/lib/${ts.getDefaultLibFileName(options)}`;
            }
        },
        readFile(fileName) {
            const snapshot = getScriptSnapshot(fileName);
            if (snapshot) {
                return snapshot.getText(0, snapshot.getLength());
            }
        },
        directoryExists(directoryName) {
            sync();
            if (tsFileDirRegistry.has(directoryName)) {
                return true;
            }
            return sys.directoryExists(directoryName);
        },
        fileExists(fileName) {
            return getScriptVersion(fileName) !== '';
        },
        getProjectVersion() {
            sync();
            return tsProjectVersion + ('version' in sys ? `:${sys.version}` : '');
        },
        getScriptFileNames() {
            sync();
            return [...tsFileRegistry.keys()];
        },
        getScriptKind(fileName) {
            sync();
            if (extraScriptRegistry.has(fileName)) {
                return extraScriptRegistry.get(fileName).scriptKind;
            }
            const sourceScript = language.scripts.get(asScriptId(fileName));
            if (sourceScript?.generated) {
                const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
                if (serviceScript) {
                    return serviceScript.scriptKind;
                }
            }
            switch (path.extname(fileName)) {
                case '.js':
                case '.cjs':
                case '.mjs':
                    return ts.ScriptKind.JS;
                case '.jsx':
                    return ts.ScriptKind.JSX;
                case '.ts':
                case '.cts':
                case '.mts':
                    return ts.ScriptKind.TS;
                case '.tsx':
                    return ts.ScriptKind.TSX;
                case '.json':
                    return ts.ScriptKind.JSON;
                default:
                    return ts.ScriptKind.Unknown;
            }
        },
        getScriptVersion,
        getScriptSnapshot,
    };
    for (const plugin of language.plugins) {
        if (plugin.typescript?.resolveLanguageServiceHost) {
            languageServiceHost = plugin.typescript.resolveLanguageServiceHost(languageServiceHost);
        }
    }
    if (pluginExtensions.length) {
        // TODO: can this share between monorepo packages?
        const moduleResolutionCache = ts.createModuleResolutionCache(languageServiceHost.getCurrentDirectory(), languageServiceHost.useCaseSensitiveFileNames?.() ? s => s : s => s.toLowerCase(), languageServiceHost.getCompilationSettings());
        const resolveModuleName = (0, resolveModuleName_1.createResolveModuleName)(ts, sys.getFileSize, languageServiceHost, language.plugins, fileName => language.scripts.get(asScriptId(fileName)));
        let lastSysVersion = 'version' in sys ? sys.version : undefined;
        languageServiceHost.resolveModuleNameLiterals = (moduleLiterals, containingFile, redirectedReference, options, containingSourceFile) => {
            const disposeFixup = (0, utils_1.fixupImpliedNodeFormatForFile)(ts, pluginExtensions, containingSourceFile, moduleResolutionCache.getPackageJsonInfoCache(), languageServiceHost, options);
            try {
                if ('version' in sys && lastSysVersion !== sys.version) {
                    lastSysVersion = sys.version;
                    moduleResolutionCache.clear();
                }
                return moduleLiterals.map(moduleLiteral => {
                    const mode = ts.getModeForUsageLocation(containingSourceFile, moduleLiteral, options);
                    return resolveModuleName(moduleLiteral.text, containingFile, options, moduleResolutionCache, redirectedReference, mode);
                });
            }
            finally {
                disposeFixup?.();
            }
        };
        languageServiceHost.resolveModuleNames = (moduleNames, containingFile, _reusedNames, redirectedReference, options) => {
            if ('version' in sys && lastSysVersion !== sys.version) {
                lastSysVersion = sys.version;
                moduleResolutionCache.clear();
            }
            return moduleNames.map(moduleName => {
                return resolveModuleName(moduleName, containingFile, options, moduleResolutionCache, redirectedReference)
                    .resolvedModule;
            });
        };
        languageServiceHost.getModuleResolutionCache = () => moduleResolutionCache;
    }
    return {
        languageServiceHost,
        getExtraServiceScript,
    };
    function getExtraServiceScript(fileName) {
        sync();
        return extraScriptRegistry.get(fileName);
    }
    function sync() {
        const newProjectVersion = projectHost.getProjectVersion?.();
        const shouldUpdate = newProjectVersion === undefined || newProjectVersion !== lastProjectVersion;
        if (!shouldUpdate) {
            return;
        }
        lastProjectVersion = newProjectVersion;
        extraScriptRegistry.clear();
        const newTsVirtualFileSnapshots = new Set();
        const newOtherVirtualFileSnapshots = new Set();
        const tsFileNamesSet = new Set();
        for (const fileName of projectHost.getScriptFileNames()) {
            const sourceScript = language.scripts.get(asScriptId(fileName));
            if (sourceScript?.generated) {
                const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
                if (serviceScript) {
                    newTsVirtualFileSnapshots.add(serviceScript.code.snapshot);
                    tsFileNamesSet.add(fileName);
                }
                for (const extraServiceScript of sourceScript.generated.languagePlugin.typescript?.getExtraServiceScripts?.(fileName, sourceScript.generated.root) ?? []) {
                    newTsVirtualFileSnapshots.add(extraServiceScript.code.snapshot);
                    tsFileNamesSet.add(extraServiceScript.fileName);
                    extraScriptRegistry.set(extraServiceScript.fileName, extraServiceScript);
                }
                for (const code of (0, language_core_1.forEachEmbeddedCode)(sourceScript.generated.root)) {
                    newOtherVirtualFileSnapshots.add(code.snapshot);
                }
            }
            else {
                tsFileNamesSet.add(fileName);
            }
        }
        if (!setEquals(lastTsVirtualFileSnapshots, newTsVirtualFileSnapshots)) {
            tsProjectVersion++;
        }
        else if (setEquals(lastOtherVirtualFileSnapshots, newOtherVirtualFileSnapshots)) {
            // no any meta language files update, it mean project version was update by source files this time
            tsProjectVersion++;
        }
        lastTsVirtualFileSnapshots = newTsVirtualFileSnapshots;
        lastOtherVirtualFileSnapshots = newOtherVirtualFileSnapshots;
        tsFileRegistry.clear();
        tsFileDirRegistry.clear();
        for (const fileName of tsFileNamesSet) {
            tsFileRegistry.set(fileName, true);
            const parts = fileName.split('/');
            for (let i = 1; i < parts.length; i++) {
                const dirName = parts.slice(0, i).join('/');
                tsFileDirRegistry.set(dirName, true);
            }
        }
    }
    function getScriptSnapshot(fileName) {
        sync();
        if (extraScriptRegistry.has(fileName)) {
            return extraScriptRegistry.get(fileName).code.snapshot;
        }
        const sourceScript = language.scripts.get(asScriptId(fileName));
        if (sourceScript?.generated) {
            const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
            if (serviceScript) {
                return serviceScript.code.snapshot;
            }
        }
        else if (sourceScript) {
            return sourceScript.snapshot;
        }
    }
    function getScriptVersion(fileName) {
        sync();
        if (!scriptVersions.has(fileName)) {
            scriptVersions.set(fileName, { lastVersion: 0, map: new WeakMap() });
        }
        const version = scriptVersions.get(fileName);
        if (extraScriptRegistry.has(fileName)) {
            const snapshot = extraScriptRegistry.get(fileName).code.snapshot;
            if (!version.map.has(snapshot)) {
                version.map.set(snapshot, version.lastVersion++);
            }
            return version.map.get(snapshot).toString();
        }
        const sourceScript = language.scripts.get(asScriptId(fileName));
        if (sourceScript?.generated) {
            const serviceScript = sourceScript.generated.languagePlugin.typescript?.getServiceScript(sourceScript.generated.root);
            if (serviceScript) {
                if (!version.map.has(serviceScript.code.snapshot)) {
                    version.map.set(serviceScript.code.snapshot, version.lastVersion++);
                }
                return version.map.get(serviceScript.code.snapshot).toString();
            }
        }
        const openedFile = language.scripts.get(asScriptId(fileName), false);
        if (openedFile && !openedFile.generated) {
            if (!version.map.has(openedFile.snapshot)) {
                version.map.set(openedFile.snapshot, version.lastVersion++);
            }
            return version.map.get(openedFile.snapshot).toString();
        }
        if (sys.fileExists(fileName)) {
            return sys.getModifiedTime?.(fileName)?.valueOf().toString() ?? '0';
        }
        return '';
    }
}
function setEquals(a, b) {
    if (a.size !== b.size) {
        return false;
    }
    for (const item of a) {
        if (!b.has(item)) {
            return false;
        }
    }
    return true;
}
//# sourceMappingURL=createProject.js.map