"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.__resetCache = __resetCache;
exports.getService = getService;
exports.forAllServices = forAllServices;
exports.getServiceForTsconfig = getServiceForTsconfig;
const path_1 = require("path");
const typescript_1 = __importDefault(require("typescript"));
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
const importPackage_1 = require("../../importPackage");
const configLoader_1 = require("../../lib/documents/configLoader");
const fileCollection_1 = require("../../lib/documents/fileCollection");
const logger_1 = require("../../logger");
const utils_1 = require("../../utils");
const DocumentSnapshot_1 = require("./DocumentSnapshot");
const module_loader_1 = require("./module-loader");
const SnapshotManager_1 = require("./SnapshotManager");
const utils_2 = require("./utils");
const serviceCache_1 = require("./serviceCache");
const svelte2tsx_1 = require("svelte2tsx");
var TsconfigSvelteDiagnostics;
(function (TsconfigSvelteDiagnostics) {
    TsconfigSvelteDiagnostics[TsconfigSvelteDiagnostics["NO_SVELTE_INPUT"] = 100001] = "NO_SVELTE_INPUT";
})(TsconfigSvelteDiagnostics || (TsconfigSvelteDiagnostics = {}));
const maxProgramSizeForNonTsFiles = 20 * 1024 * 1024; // 20 MB
const services = new fileCollection_1.FileMap();
const serviceSizeMap = new fileCollection_1.FileMap();
const configWatchers = new fileCollection_1.FileMap();
const dependedConfigWatchers = new fileCollection_1.FileMap();
const configPathToDependedProject = new fileCollection_1.FileMap();
const configFileModifiedTime = new fileCollection_1.FileMap();
const configFileForOpenFiles = new fileCollection_1.FileMap();
const pendingReloads = new fileCollection_1.FileSet();
const documentRegistries = new Map();
const pendingForAllServices = new Set();
const parsedTsConfigInfo = new fileCollection_1.FileMap();
/**
 * For testing only: Reset the cache for services.
 * Try to refactor this some day so that this file provides
 * a setup function which creates all this nicely instead.
 */
function __resetCache() {
    services.clear();
    parsedTsConfigInfo.clear();
    serviceSizeMap.clear();
    configFileForOpenFiles.clear();
}
async function getService(path, workspaceUris, docContext) {
    const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(docContext.tsSystem.useCaseSensitiveFileNames);
    const fileExistsWithCache = (fileName) => {
        return ((parsedTsConfigInfo.has(fileName) && !pendingReloads.has(fileName)) ||
            docContext.tsSystem.fileExists(fileName));
    };
    let tsconfigPath = configFileForOpenFiles.get(path) ??
        (0, utils_2.findTsConfigPath)(path, workspaceUris, fileExistsWithCache, getCanonicalFileName);
    if (tsconfigPath) {
        /**
         * Prevent infinite loop when the project reference is circular
         */
        const triedTsConfig = new Set();
        const needAssign = !configFileForOpenFiles.has(path);
        let service = await getConfiguredService(tsconfigPath);
        if (!needAssign) {
            return service;
        }
        // First try to find a service whose includes config matches our file
        const defaultService = await findDefaultServiceForFile(service, triedTsConfig);
        if (defaultService) {
            configFileForOpenFiles.set(path, defaultService.tsconfigPath);
            return defaultService;
        }
        // If no such service found, see if the file is part of any existing service indirectly.
        // This can happen if the includes doesn't match the file but it was imported from one of the included files.
        for (const configPath of triedTsConfig) {
            const service = await getConfiguredService(configPath);
            const ls = service.getService();
            if (ls.getProgram()?.getSourceFile(path)) {
                return service;
            }
        }
        tsconfigPath = '';
    }
    // Find closer boundary: workspace uri or node_modules
    const nearestWorkspaceUri = (0, utils_2.getNearestWorkspaceUri)(workspaceUris, path, getCanonicalFileName);
    const lastNodeModulesIdx = path.split('/').lastIndexOf('node_modules') + 2;
    const nearestNodeModulesBoundary = lastNodeModulesIdx === 1
        ? undefined
        : path.split('/').slice(0, lastNodeModulesIdx).join('/');
    const nearestBoundary = (nearestNodeModulesBoundary?.length ?? 0) > (nearestWorkspaceUri?.length ?? 0)
        ? nearestNodeModulesBoundary
        : nearestWorkspaceUri;
    return getServiceForTsconfig(tsconfigPath, (nearestBoundary && (0, utils_1.urlToPath)(nearestBoundary)) ??
        docContext.tsSystem.getCurrentDirectory(), docContext);
    function getConfiguredService(tsconfigPath) {
        return getServiceForTsconfig(tsconfigPath, (0, path_1.dirname)(tsconfigPath), docContext);
    }
    async function findDefaultServiceForFile(service, triedTsConfig) {
        service.ensureProjectFileUpdates(path);
        if (service.snapshotManager.isProjectFile(path)) {
            return service;
        }
        if (triedTsConfig.has(service.tsconfigPath)) {
            return;
        }
        triedTsConfig.add(service.tsconfigPath);
        // TODO: maybe add support for ts 5.6's ancestor searching
        return findDefaultFromProjectReferences(service, triedTsConfig);
    }
    async function findDefaultFromProjectReferences(service, triedTsConfig) {
        const projectReferences = service.getResolvedProjectReferences();
        if (projectReferences.length === 0) {
            return undefined;
        }
        let possibleSubPaths = [];
        for (const ref of projectReferences) {
            if (ref.snapshotManager.isProjectFile(path)) {
                return getConfiguredService(ref.configFilePath);
            }
            if (ref.parsedCommandLine.projectReferences?.length) {
                possibleSubPaths.push(ref.configFilePath);
            }
        }
        for (const ref of possibleSubPaths) {
            const subService = await getConfiguredService(ref);
            const defaultService = await findDefaultServiceForFile(subService, triedTsConfig);
            if (defaultService) {
                return defaultService;
            }
        }
    }
}
async function forAllServices(cb) {
    const promise = forAllServicesWorker(cb);
    pendingForAllServices.add(promise);
    await promise;
    pendingForAllServices.delete(promise);
}
async function forAllServicesWorker(cb) {
    for (const service of services.values()) {
        cb(await service);
    }
}
/**
 * @param tsconfigPath has to be absolute
 * @param docContext
 */
async function getServiceForTsconfig(tsconfigPath, workspacePath, docContext) {
    if (tsconfigPath) {
        tsconfigPath = (0, utils_1.normalizePath)(tsconfigPath);
    }
    const tsconfigPathOrWorkspacePath = tsconfigPath || workspacePath;
    const reloading = pendingReloads.has(tsconfigPath);
    let service;
    if (reloading || !services.has(tsconfigPathOrWorkspacePath)) {
        if (reloading) {
            logger_1.Logger.log('Reloading ts service at ', tsconfigPath, ' due to config updated');
            parsedTsConfigInfo.delete(tsconfigPath);
        }
        else {
            logger_1.Logger.log('Initialize new ts service at ', tsconfigPath);
        }
        pendingReloads.delete(tsconfigPath);
        const newService = createLanguageService(tsconfigPath, workspacePath, docContext);
        services.set(tsconfigPathOrWorkspacePath, newService);
        service = await newService;
    }
    else {
        service = await services.get(tsconfigPathOrWorkspacePath);
    }
    if (pendingForAllServices.size > 0) {
        await Promise.all(pendingForAllServices);
    }
    return service;
}
async function createLanguageService(tsconfigPath, workspacePath, docContext) {
    const { tsSystem } = docContext;
    const projectConfig = getParsedConfig();
    const { options: compilerOptions, raw, errors: configErrors } = projectConfig;
    const allowJs = compilerOptions.allowJs ?? !!compilerOptions.checkJs;
    const virtualDocuments = new fileCollection_1.FileMap(tsSystem.useCaseSensitiveFileNames);
    const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames);
    watchWildCardDirectories(projectConfig);
    const snapshotManager = createSnapshotManager(projectConfig, tsconfigPath);
    // Load all configs within the tsconfig scope and the one above so that they are all loaded
    // by the time they need to be accessed synchronously by DocumentSnapshots.
    await configLoader_1.configLoader.loadConfigs(workspacePath);
    const svelteModuleLoader = (0, module_loader_1.createSvelteModuleLoader)(getSnapshot, compilerOptions, tsSystem, typescript_1.default, () => host?.getCompilerHost?.());
    let svelteTsPath;
    /**
     * set and clear during program creation, shouldn't not be cached elsewhere
     */
    let compilerHost;
    try {
        // For when svelte2tsx/svelte-check is part of node_modules, for example VS Code extension
        svelteTsPath = (0, path_1.dirname)(require.resolve(docContext.ambientTypesSource));
    }
    catch (e) {
        // Fall back to dirname
        svelteTsPath = __dirname;
    }
    const sveltePackageInfo = (0, importPackage_1.getPackageInfo)('svelte', tsconfigPath || workspacePath);
    // Svelte 4 has some fixes with regards to parsing the generics attribute.
    // Svelte 5 has new features, but we don't want to add the new compiler into language-tools. In the future it's probably
    // best to shift more and more of this into user's node_modules for better handling of multiple Svelte versions.
    const svelteCompiler = sveltePackageInfo.version.major >= 4
        ? (0, importPackage_1.importSvelte)(tsconfigPath || workspacePath)
        : undefined;
    const changedFilesForExportCache = new Set();
    const svelteTsxFilesToOriginalCasing = getSvelteShimFiles();
    let languageServiceReducedMode = false;
    let projectVersion = 0;
    let dirty = projectConfig.fileNames.length > 0;
    let skipSvelteInputCheck = !tsconfigPath;
    const host = {
        log: (message) => logger_1.Logger.debug(`[ts] ${message}`),
        getCompilationSettings: () => compilerOptions,
        getScriptFileNames,
        getScriptVersion: (fileName) => getSnapshotIfExists(fileName)?.version.toString() || '',
        getScriptSnapshot: getSnapshotIfExists,
        getCurrentDirectory: () => workspacePath,
        getDefaultLibFileName: typescript_1.default.getDefaultLibFilePath,
        fileExists: svelteModuleLoader.fileExists,
        readFile: svelteModuleLoader.readFile,
        resolveModuleNames: svelteModuleLoader.resolveModuleNames,
        readDirectory: svelteModuleLoader.readDirectory,
        realpath: tsSystem.realpath,
        getDirectories: tsSystem.getDirectories,
        getProjectReferences: () => projectConfig.projectReferences,
        getParsedCommandLine,
        useCaseSensitiveFileNames: () => tsSystem.useCaseSensitiveFileNames,
        getScriptKind: (fileName) => getSnapshot(fileName).scriptKind,
        getProjectVersion,
        getNewLine: () => tsSystem.newLine,
        resolveTypeReferenceDirectiveReferences: svelteModuleLoader.resolveTypeReferenceDirectiveReferences,
        hasInvalidatedResolutions: svelteModuleLoader.mightHaveInvalidatedResolutions,
        getModuleResolutionCache: svelteModuleLoader.getModuleResolutionCache,
        useSourceOfProjectReferenceRedirect() {
            return !languageServiceReducedMode;
        },
        setCompilerHost: (host) => (compilerHost = host),
        getCompilerHost: () => compilerHost
    };
    const documentRegistry = getOrCreateDocumentRegistry(
    // this should mostly be a singleton while host.getCurrentDirectory() might be the directory where the tsconfig is
    tsSystem.getCurrentDirectory(), tsSystem.useCaseSensitiveFileNames);
    const transformationConfig = {
        parse: svelteCompiler?.parse,
        version: svelteCompiler?.VERSION,
        transformOnTemplateError: docContext.transformOnTemplateError,
        typingsNamespace: raw?.svelteOptions?.namespace || 'svelteHTML'
    };
    const project = initLsCacheProject();
    const languageService = typescript_1.default.createLanguageService(host, documentRegistry);
    docContext.globalSnapshotsManager.onChange(scheduleUpdate);
    reduceLanguageServiceCapabilityIfFileSizeTooBig();
    watchConfigFiles(projectConfig.extendedConfigPaths, projectConfig);
    return {
        tsconfigPath,
        compilerOptions,
        configErrors,
        getService,
        updateSnapshot,
        deleteSnapshot,
        scheduleProjectFileUpdate,
        updateTsOrJsFile,
        ensureProjectFileUpdates,
        hasFile,
        fileBelongsToProject,
        snapshotManager,
        invalidateModuleCache,
        onAutoImportProviderSettingsChanged,
        onPackageJsonChange,
        getTsConfigSvelteOptions,
        getResolvedProjectReferences,
        openVirtualDocument,
        isShimFiles,
        getProjectConfig,
        getProjectVersion,
        dispose
    };
    function getProjectVersion() {
        return projectVersion.toString();
    }
    function createSnapshotManager(parsedCommandLine, configFileName) {
        const cached = configFileName ? parsedTsConfigInfo.get(configFileName) : undefined;
        if (cached?.snapshotManager) {
            return cached.snapshotManager;
        }
        // raw is the tsconfig merged with extending config
        // see: https://github.com/microsoft/TypeScript/blob/08e4f369fbb2a5f0c30dee973618d65e6f7f09f8/src/compiler/commandLineParser.ts#L2537
        return new SnapshotManager_1.SnapshotManager(docContext.globalSnapshotsManager, parsedCommandLine.raw, configFileName ? (0, path_1.dirname)(configFileName) : workspacePath, tsSystem, parsedCommandLine.fileNames.map(utils_1.normalizePath), parsedCommandLine.wildcardDirectories);
    }
    function watchWildCardDirectories(parseCommandLine) {
        const { wildcardDirectories } = parseCommandLine;
        if (!wildcardDirectories || !docContext.watchDirectory) {
            return;
        }
        const canonicalWorkspacePath = getCanonicalFileName(workspacePath);
        const patterns = [];
        Object.entries(wildcardDirectories).forEach(([dir, flags]) => {
            if (
            // already watched
            getCanonicalFileName(dir).startsWith(canonicalWorkspacePath) ||
                !tsSystem.directoryExists(dir)) {
                return;
            }
            patterns.push({
                baseUri: (0, utils_1.pathToUrl)(dir),
                pattern: (flags & typescript_1.default.WatchDirectoryFlags.Recursive ? `**/` : '') +
                    docContext.nonRecursiveWatchPattern
            });
        });
        docContext.watchDirectory?.(patterns);
    }
    function getService(skipSynchronize) {
        ensureProjectFileUpdates();
        if (!skipSynchronize) {
            updateIfDirty();
        }
        return languageService;
    }
    function deleteSnapshot(filePath) {
        svelteModuleLoader.deleteFromModuleCache(filePath);
        snapshotManager.delete(filePath);
        configFileForOpenFiles.delete(filePath);
    }
    function invalidateModuleCache(filePaths) {
        for (const filePath of filePaths) {
            const normalizedPath = (0, utils_1.normalizePath)(filePath);
            svelteModuleLoader.deleteFromModuleCache(normalizedPath);
            svelteModuleLoader.scheduleResolutionFailedLocationCheck(normalizedPath);
            scheduleUpdate(normalizedPath);
        }
    }
    function updateSnapshot(documentOrFilePath) {
        return typeof documentOrFilePath === 'string'
            ? updateSnapshotFromFilePath(documentOrFilePath)
            : updateSnapshotFromDocument(documentOrFilePath);
    }
    function updateSnapshotFromDocument(document) {
        const filePath = document.getFilePath() || '';
        const prevSnapshot = snapshotManager.get(filePath);
        if (prevSnapshot?.version === document.version &&
            // In the test, there might be a new document instance with a different openedByClient
            // In that case, Create a new snapshot otherwise the getClientFileNames won't include the new client file
            prevSnapshot.isOpenedInClient() === document.openedByClient) {
            return prevSnapshot;
        }
        const newSnapshot = DocumentSnapshot_1.DocumentSnapshot.fromDocument(document, transformationConfig);
        if (!prevSnapshot) {
            svelteModuleLoader.scheduleResolutionFailedLocationCheck(filePath);
            if (configFileForOpenFiles.get(filePath) === '' && services.size > 1) {
                configFileForOpenFiles.delete(filePath);
            }
        }
        else if (prevSnapshot.scriptKind !== newSnapshot.scriptKind && !allowJs) {
            // if allowJs is false, we need to invalid the cache so that js svelte files can be loaded through module resolution
            svelteModuleLoader.deleteFromModuleCache(filePath);
            configFileForOpenFiles.delete(filePath);
        }
        snapshotManager.set(filePath, newSnapshot);
        return newSnapshot;
    }
    function updateSnapshotFromFilePath(filePath) {
        const prevSnapshot = snapshotManager.get(filePath);
        if (prevSnapshot) {
            return prevSnapshot;
        }
        svelteModuleLoader.scheduleResolutionFailedLocationCheck(filePath);
        return createSnapshot(filePath);
    }
    /**
     * Deleted files will still be requested during the program update.
     * Don't create snapshots for them.
     * Otherwise, deleteUnresolvedResolutionsFromCache won't be called when the file is created again
     */
    function getSnapshotIfExists(fileName) {
        const svelteFileName = (0, utils_2.ensureRealSvelteFilePath)(fileName);
        let doc = snapshotManager.get(fileName) ?? snapshotManager.get(svelteFileName);
        if (doc) {
            return doc;
        }
        if (!svelteModuleLoader.fileExists(fileName)) {
            return undefined;
        }
        // don't invalidate the module cache here
        // this only get called if we already know the file exists
        return createSnapshot(svelteModuleLoader.svelteFileExists(fileName) ? svelteFileName : fileName);
    }
    function getSnapshot(fileName) {
        const svelteFileName = (0, utils_2.ensureRealSvelteFilePath)(fileName);
        let doc = snapshotManager.get(fileName) ?? snapshotManager.get(svelteFileName);
        if (doc) {
            return doc;
        }
        // don't invalidate the module cache here
        // this only get called if we already know the file exists
        return createSnapshot(fileName);
    }
    function createSnapshot(fileName) {
        const doc = DocumentSnapshot_1.DocumentSnapshot.fromFilePath(fileName, docContext.createDocument, transformationConfig, tsSystem);
        snapshotManager.set(fileName, doc);
        return doc;
    }
    function scheduleProjectFileUpdate(watcherNewFiles) {
        if (!snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) {
            scheduleUpdate();
            const info = parsedTsConfigInfo.get(tsconfigPath);
            if (info) {
                info.pendingProjectFileUpdate = true;
            }
        }
        if (!projectConfig.projectReferences) {
            return;
        }
        for (const ref of projectConfig.projectReferences) {
            const config = parsedTsConfigInfo.get(ref.path);
            if (config &&
                // handled by the respective service
                !services.has(config.configFilePath) &&
                !config.snapshotManager.areIgnoredFromNewFileWatch(watcherNewFiles)) {
                config.pendingProjectFileUpdate = true;
                scheduleUpdate();
            }
        }
    }
    function ensureProjectFileUpdates(newFile) {
        const info = parsedTsConfigInfo.get(tsconfigPath);
        if (!info) {
            return;
        }
        if (newFile &&
            !info.pendingProjectFileUpdate &&
            // no global snapshots yet when initial load pending
            !snapshotManager.isProjectFile(newFile) &&
            !docContext.globalSnapshotsManager.get(newFile)) {
            scheduleProjectFileUpdate([newFile]);
        }
        if (!info.pendingProjectFileUpdate) {
            return;
        }
        const projectFileCountBefore = snapshotManager.getProjectFileNames().length;
        ensureFilesForConfigUpdates(info);
        const projectFileCountAfter = snapshotManager.getProjectFileNames().length;
        if (projectFileCountAfter > projectFileCountBefore) {
            reduceLanguageServiceCapabilityIfFileSizeTooBig();
        }
    }
    function getScriptFileNames() {
        const projectFiles = languageServiceReducedMode
            ? []
            : snapshotManager.getProjectFileNames();
        const canonicalProjectFileNames = new Set(projectFiles.map(getCanonicalFileName));
        // We only assign project files (i.e. those found through includes config) and virtual files to getScriptFileNames.
        // We don't to include other client files otherwise they stay in the program and are never removed
        const clientFiles = tsconfigPath
            ? Array.from(virtualDocuments.values())
                .map((v) => v.getFilePath())
                .filter(utils_1.isNotNullOrUndefined)
            : snapshotManager.getClientFileNames();
        return Array.from(new Set([
            ...projectFiles,
            // project file is read from the file system so it's more likely to have
            // the correct casing
            ...clientFiles.filter((file) => !canonicalProjectFileNames.has(getCanonicalFileName(file))),
            // Use original casing here, too: people could have their VS Code extensions in a case insensitive
            // folder but their project in a case sensitive one; and if we copy the shims into the case sensitive
            // part it would break when canonicalizing it.
            ...svelteTsxFilesToOriginalCasing.values()
        ]));
    }
    function hasFile(filePath) {
        return snapshotManager.has(filePath);
    }
    function fileBelongsToProject(filePath, isNew) {
        filePath = (0, utils_1.normalizePath)(filePath);
        if (hasFile(filePath)) {
            return true;
        }
        if (!isNew) {
            return false;
        }
        ensureProjectFileUpdates(filePath);
        return snapshotManager.isProjectFile(filePath);
    }
    function updateTsOrJsFile(fileName, changes) {
        if (!snapshotManager.has(fileName)) {
            svelteModuleLoader.scheduleResolutionFailedLocationCheck(fileName);
        }
        snapshotManager.updateTsOrJsFile(fileName, changes);
    }
    function getParsedConfig() {
        let compilerOptions;
        let parsedConfig;
        let extendedConfigPaths;
        if (tsconfigPath) {
            const info = ensureTsConfigInfoUpToDate(tsconfigPath);
            // tsconfig is either found from file-system or passed from svelte-check
            // so this is already be validated to exist
            if (!info) {
                throw new Error('Failed to get tsconfig: ' + tsconfigPath);
            }
            compilerOptions = info.parsedCommandLine.options;
            parsedConfig = info.parsedCommandLine;
            extendedConfigPaths = info.extendedConfigPaths;
        }
        else {
            const config = parseDefaultCompilerOptions();
            compilerOptions = config.compilerOptions;
            parsedConfig = config.parsedConfig;
        }
        if (!compilerOptions.moduleResolution ||
            compilerOptions.moduleResolution === typescript_1.default.ModuleResolutionKind.Classic) {
            compilerOptions.moduleResolution =
                // NodeJS: up to 4.9, Node10: since 5.0
                typescript_1.default.ModuleResolutionKind.NodeJs ?? typescript_1.default.ModuleResolutionKind.Node10;
        }
        if (!compilerOptions.module ||
            [
                typescript_1.default.ModuleKind.AMD,
                typescript_1.default.ModuleKind.CommonJS,
                typescript_1.default.ModuleKind.ES2015,
                typescript_1.default.ModuleKind.None,
                typescript_1.default.ModuleKind.System,
                typescript_1.default.ModuleKind.UMD
            ].includes(compilerOptions.module)) {
            compilerOptions.module = typescript_1.default.ModuleKind.ESNext;
        }
        if (!compilerOptions.target) {
            compilerOptions.target = typescript_1.default.ScriptTarget.Latest;
        }
        else if (typescript_1.default.ScriptTarget.ES2015 > compilerOptions.target) {
            compilerOptions.target = typescript_1.default.ScriptTarget.ES2015;
        }
        // detect which JSX namespace to use (svelte | svelteNative) if not specified or not compatible
        if (!compilerOptions.jsxFactory || !compilerOptions.jsxFactory.startsWith('svelte')) {
            //override if we detect svelte-native
            if (workspacePath) {
                try {
                    const svelteNativePkgInfo = (0, importPackage_1.getPackageInfo)('@nativescript-community/svelte-native', workspacePath) ||
                        (0, importPackage_1.getPackageInfo)('svelte-native', workspacePath);
                    if (svelteNativePkgInfo.path) {
                        // For backwards compatibility
                        parsedConfig.raw.svelteOptions = parsedConfig.raw.svelteOptions || {};
                        parsedConfig.raw.svelteOptions.namespace = 'svelteNative.JSX';
                    }
                }
                catch (e) {
                    //we stay regular svelte
                }
            }
        }
        return {
            ...parsedConfig,
            fileNames: parsedConfig.fileNames.map(utils_1.normalizePath),
            options: compilerOptions,
            extendedConfigPaths
        };
    }
    function checkSvelteInput(program, config) {
        if (!tsconfigPath || config.raw.references || config.raw.files) {
            return [];
        }
        const configFileName = (0, path_1.basename)(tsconfigPath);
        // Only report to possible nearest config file since referenced project might not be a svelte project
        if (configFileName !== 'tsconfig.json' && configFileName !== 'jsconfig.json') {
            return [];
        }
        const hasSvelteFiles = config.fileNames.some(utils_2.isSvelteFilePath) ||
            program?.getSourceFiles().some((file) => (0, utils_2.isSvelteFilePath)(file.fileName));
        if (hasSvelteFiles) {
            return [];
        }
        const { include, exclude } = config.raw;
        const inputText = JSON.stringify(include);
        const excludeText = JSON.stringify(exclude);
        const svelteConfigDiagnostics = [
            {
                category: typescript_1.default.DiagnosticCategory.Warning,
                code: TsconfigSvelteDiagnostics.NO_SVELTE_INPUT,
                file: undefined,
                start: undefined,
                length: undefined,
                messageText: `No svelte input files were found in config file '${tsconfigPath}'. ` +
                    `Did you forget to add svelte files to the 'include' in your ${(0, path_1.basename)(tsconfigPath)}? ` +
                    `Specified 'include' paths were '${inputText}' and 'exclude' paths were '${excludeText}'`,
                source: 'svelte'
            }
        ];
        return svelteConfigDiagnostics;
    }
    function parseDefaultCompilerOptions() {
        let configJson = {
            compilerOptions: {
                allowJs: true,
                noEmit: true,
                declaration: false,
                skipLibCheck: true,
                maxNodeModuleJsDepth: 2,
                allowSyntheticDefaultImports: true
            },
            // Necessary to not flood the initial files
            // with potentially completely unrelated .ts/.js files:
            include: []
        };
        const parsedConfig = typescript_1.default.parseJsonConfigFileContent(configJson, tsSystem, workspacePath);
        const compilerOptions = {
            ...parsedConfig.options,
            target: typescript_1.default.ScriptTarget.Latest,
            allowNonTsExtensions: true,
            moduleResolution: typescript_1.default.ModuleResolutionKind.Node10
        };
        return { compilerOptions, parsedConfig };
    }
    /**
     * Disable usage of project files.
     * running language service in a reduced mode for
     * large projects with improperly excluded tsconfig.
     */
    function reduceLanguageServiceCapabilityIfFileSizeTooBig() {
        if (exceedsTotalSizeLimitForNonTsFiles(compilerOptions, tsconfigPath, snapshotManager, tsSystem)) {
            languageService.cleanupSemanticCache();
            languageServiceReducedMode = true;
            if (project) {
                project.languageServiceEnabled = false;
            }
            docContext.notifyExceedSizeLimit?.();
        }
    }
    function dispose() {
        compilerHost = undefined;
        languageService.dispose();
        snapshotManager.dispose();
        configWatchers.get(tsconfigPath)?.close();
        configWatchers.delete(tsconfigPath);
        configFileForOpenFiles.clear();
        docContext.globalSnapshotsManager.removeChangeListener(scheduleUpdate);
    }
    function watchConfigFiles(extendedConfigPaths, parsedCommandLine) {
        const tsconfigDependencies = Array.from(extendedConfigPaths ?? []).concat(parsedCommandLine.projectReferences?.map((r) => r.path) ?? []);
        tsconfigDependencies.forEach((configPath) => {
            let dependedTsConfig = configPathToDependedProject.get(configPath);
            if (!dependedTsConfig) {
                dependedTsConfig = new fileCollection_1.FileSet(tsSystem.useCaseSensitiveFileNames);
                configPathToDependedProject.set(configPath, dependedTsConfig);
            }
            dependedTsConfig.add(tsconfigPath);
        });
        if (!tsSystem.watchFile || !docContext.watchTsConfig) {
            return;
        }
        if (!configWatchers.has(tsconfigPath) && tsconfigPath) {
            configFileModifiedTime.set(tsconfigPath, tsSystem.getModifiedTime?.(tsconfigPath));
            configWatchers.set(tsconfigPath, 
            // for some reason setting the polling interval is necessary, else some error in TS is thrown
            tsSystem.watchFile(tsconfigPath, watchConfigCallback, 1000));
        }
        for (const config of tsconfigDependencies) {
            if (dependedConfigWatchers.has(config)) {
                continue;
            }
            configFileModifiedTime.set(config, tsSystem.getModifiedTime?.(config));
            dependedConfigWatchers.set(config, 
            // for some reason setting the polling interval is necessary, else some error in TS is thrown
            tsSystem.watchFile(config, createWatchDependedConfigCallback(docContext), 1000));
        }
    }
    async function watchConfigCallback(fileName, kind, modifiedTime) {
        if (kind === typescript_1.default.FileWatcherEventKind.Changed &&
            !configFileModified(fileName, modifiedTime ?? tsSystem.getModifiedTime?.(fileName), docContext)) {
            return;
        }
        dispose();
        if (kind === typescript_1.default.FileWatcherEventKind.Changed) {
            scheduleReload(fileName);
        }
        else if (kind === typescript_1.default.FileWatcherEventKind.Deleted) {
            services.delete(fileName);
            configFileForOpenFiles.clear();
        }
        docContext.onProjectReloaded?.([fileName]);
        docContext.reportConfigError?.({ uri: (0, utils_1.pathToUrl)(fileName), diagnostics: [] });
    }
    function updateIfDirty() {
        if (!dirty) {
            return;
        }
        svelteModuleLoader.invalidateFailedLocationResolution();
        const oldProgram = project?.program;
        let program;
        try {
            program = languageService.getProgram();
        }
        finally {
            // mark as clean even if the update fails, at least we can still try again next time there is a change
            dirty = false;
            compilerHost = undefined;
            svelteModuleLoader.clearPendingInvalidations();
        }
        if (project) {
            project.program = program;
        }
        if (!skipSvelteInputCheck) {
            const svelteConfigDiagnostics = checkSvelteInput(program, projectConfig);
            const codes = svelteConfigDiagnostics.map((d) => d.code);
            if (!svelteConfigDiagnostics.length) {
                // stop checking once it passed once
                skipSvelteInputCheck = true;
            }
            // report even if empty to clear previous diagnostics
            docContext.reportConfigError?.({
                uri: (0, utils_1.pathToUrl)(tsconfigPath),
                diagnostics: svelteConfigDiagnostics.map((d) => ({
                    message: d.messageText,
                    range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
                    severity: vscode_languageserver_protocol_1.DiagnosticSeverity.Warning,
                    source: 'svelte'
                }))
            });
            const new_errors = projectConfig.errors
                .filter((e) => !codes.includes(e.code))
                .concat(svelteConfigDiagnostics);
            projectConfig.errors.splice(0, projectConfig.errors.length, ...new_errors);
        }
        // https://github.com/microsoft/TypeScript/blob/23faef92703556567ddbcb9afb893f4ba638fc20/src/server/project.ts#L1624
        // host.getCachedExportInfoMap will create the cache if it doesn't exist
        // so we need to check the property instead
        const exportMapCache = project?.exportMapCache;
        if (!oldProgram || !exportMapCache || exportMapCache.isEmpty()) {
            changedFilesForExportCache.clear();
            return;
        }
        exportMapCache.releaseSymbols();
        // https://github.com/microsoft/TypeScript/blob/941d1543c201e40d87e63c9db04818493afdd9e7/src/server/project.ts#L1731
        // if one file change results in clearing the cache
        // don't continue to check other files, this will mark the cache as usable while it's empty
        for (const fileName of changedFilesForExportCache) {
            const oldFile = oldProgram.getSourceFile(fileName);
            const newFile = program?.getSourceFile(fileName);
            // file for another tsconfig
            if (!oldFile && !newFile) {
                continue;
            }
            if (!oldFile || !newFile) {
                // new file or deleted file
                exportMapCache.clear();
                break;
            }
            const cleared = exportMapCache.onFileChanged?.(oldFile, newFile, false);
            if (cleared) {
                break;
            }
        }
        changedFilesForExportCache.clear();
    }
    function scheduleUpdate(triggeredFile) {
        if (triggeredFile) {
            changedFilesForExportCache.add(triggeredFile);
        }
        if (dirty) {
            return;
        }
        projectVersion++;
        dirty = true;
    }
    function initLsCacheProject() {
        const projectService = docContext.projectService;
        if (!projectService) {
            return;
        }
        // Used by typescript-auto-import-cache to create a lean language service for package.json auto-import.
        const createLanguageServiceForAutoImportProvider = (host) => typescript_1.default.createLanguageService(host, documentRegistry);
        return (0, serviceCache_1.createProject)(host, createLanguageServiceForAutoImportProvider, {
            compilerOptions: compilerOptions,
            projectService: projectService,
            currentDirectory: workspacePath
        });
    }
    function onAutoImportProviderSettingsChanged() {
        project?.onAutoImportProviderSettingsChanged();
    }
    function onPackageJsonChange(packageJsonPath) {
        if (!project) {
            return;
        }
        if (project.packageJsonsForAutoImport?.has(packageJsonPath)) {
            project.moduleSpecifierCache.clear();
            if (project.autoImportProviderHost) {
                project.autoImportProviderHost.markAsDirty();
            }
        }
        if (packageJsonPath.includes('node_modules')) {
            const dir = (0, path_1.dirname)(packageJsonPath);
            const inProgram = project
                .getCurrentProgram()
                ?.getSourceFiles()
                .some((file) => file.fileName.includes(dir));
            if (inProgram) {
                host.getModuleSpecifierCache?.().clear();
            }
        }
    }
    function getTsConfigSvelteOptions() {
        // if there's more options in the future, get it from raw.svelteOptions and normalize it
        return {
            namespace: transformationConfig.typingsNamespace
        };
    }
    function ensureTsConfigInfoUpToDate(configFilePath) {
        const cached = parsedTsConfigInfo.get(configFilePath);
        if (cached !== undefined) {
            ensureFilesForConfigUpdates(cached);
            return cached;
        }
        const content = tsSystem.fileExists(configFilePath) && tsSystem.readFile(configFilePath);
        if (!content) {
            parsedTsConfigInfo.set(configFilePath, null);
            return null;
        }
        const json = typescript_1.default.parseJsonText(configFilePath, content);
        const extendedConfigPaths = new Set();
        const { extendedConfigCache } = docContext;
        const cacheMonitorProxy = {
            ...docContext.extendedConfigCache,
            get(key) {
                extendedConfigPaths.add(key);
                return extendedConfigCache.get(key);
            },
            has(key) {
                extendedConfigPaths.add(key);
                return extendedConfigCache.has(key);
            },
            set(key, value) {
                extendedConfigPaths.add(key);
                return extendedConfigCache.set(key, value);
            }
        };
        // TypeScript will throw if the parsedCommandLine doesn't include the sourceFile for the config file
        // i.e. it must be directly parse from the json text instead of a javascript object like we do in getParsedConfig
        const parsedCommandLine = typescript_1.default.parseJsonSourceFileConfigFileContent(json, tsSystem, (0, path_1.dirname)(configFilePath), 
        /*existingOptions*/ undefined, configFilePath, 
        /*resolutionStack*/ undefined, [
            {
                extension: 'svelte',
                isMixedContent: true,
                // Deferred was added in a later TS version, fall back to tsx
                // If Deferred exists, this means that all Svelte files are included
                // in parsedConfig.fileNames
                scriptKind: typescript_1.default.ScriptKind.Deferred ?? typescript_1.default.ScriptKind.TS
            }
        ], cacheMonitorProxy);
        parsedCommandLine.options.allowNonTsExtensions = true;
        const snapshotManager = createSnapshotManager(parsedCommandLine, configFilePath);
        const tsconfigInfo = {
            parsedCommandLine,
            snapshotManager,
            pendingProjectFileUpdate: false,
            configFilePath,
            extendedConfigPaths
        };
        parsedTsConfigInfo.set(configFilePath, tsconfigInfo);
        watchConfigFiles(extendedConfigPaths, parsedCommandLine);
        return tsconfigInfo;
    }
    function getParsedCommandLine(configFilePath) {
        return ensureTsConfigInfoUpToDate(configFilePath)?.parsedCommandLine;
    }
    function ensureFilesForConfigUpdates(info) {
        if (info?.pendingProjectFileUpdate) {
            info.pendingProjectFileUpdate = false;
            info.snapshotManager.updateProjectFiles();
            info.parsedCommandLine.fileNames = info.snapshotManager.getProjectFileNames();
        }
    }
    function getResolvedProjectReferences() {
        if (!tsconfigPath || !projectConfig.projectReferences) {
            return [];
        }
        return projectConfig.projectReferences
            .map((ref) => ensureTsConfigInfoUpToDate((0, utils_1.normalizePath)(ref.path)))
            .filter(utils_1.isNotNullOrUndefined);
    }
    function openVirtualDocument(document) {
        const filePath = document.getFilePath();
        if (!filePath) {
            return;
        }
        virtualDocuments.set(filePath, document);
        configFileForOpenFiles.set(filePath, tsconfigPath || workspacePath);
        updateSnapshot(document);
        scheduleUpdate(filePath);
    }
    function getSvelteShimFiles() {
        const svelteTsxFiles = svelte2tsx_1.internalHelpers.get_global_types(tsSystem, sveltePackageInfo.version.major === 3, sveltePackageInfo.path, svelteTsPath, docContext.isSvelteCheck ? undefined : tsconfigPath || workspacePath);
        const pathToOriginalCasing = new Map();
        for (const file of svelteTsxFiles) {
            const normalizedPath = (0, utils_1.normalizePath)(file);
            pathToOriginalCasing.set(getCanonicalFileName(normalizedPath), normalizedPath);
        }
        return pathToOriginalCasing;
    }
    function isShimFiles(filePath) {
        return svelteTsxFilesToOriginalCasing.has(getCanonicalFileName((0, utils_1.normalizePath)(filePath)));
    }
    function getProjectConfig() {
        return projectConfig;
    }
}
/**
 * adopted from https://github.com/microsoft/TypeScript/blob/3c8e45b304b8572094c5d7fbb9cd768dbf6417c0/src/server/editorServices.ts#L1955
 */
function exceedsTotalSizeLimitForNonTsFiles(compilerOptions, tsconfigPath, snapshotManager, tsSystem) {
    if (compilerOptions.disableSizeLimit) {
        return false;
    }
    let availableSpace = maxProgramSizeForNonTsFiles;
    serviceSizeMap.set(tsconfigPath, 0);
    serviceSizeMap.forEach((size) => {
        availableSpace -= size;
    });
    let totalNonTsFileSize = 0;
    const fileNames = snapshotManager.getProjectFileNames();
    for (const fileName of fileNames) {
        if ((0, utils_2.hasTsExtensions)(fileName)) {
            continue;
        }
        totalNonTsFileSize += tsSystem.getFileSize?.(fileName) ?? 0;
        if (totalNonTsFileSize > availableSpace) {
            const top5LargestFiles = fileNames
                .filter((name) => !(0, utils_2.hasTsExtensions)(name))
                .map((name) => ({ name, size: tsSystem.getFileSize?.(name) ?? 0 }))
                .sort((a, b) => b.size - a.size)
                .slice(0, 5);
            logger_1.Logger.log(`Non TS file size exceeded limit (${totalNonTsFileSize}). ` +
                `Largest files: ${top5LargestFiles
                    .map((file) => `${file.name}:${file.size}`)
                    .join(', ')}`);
            return true;
        }
    }
    serviceSizeMap.set(tsconfigPath, totalNonTsFileSize);
    return false;
}
/**
 * shared watcher callback can't be within `createLanguageService`
 * because it would reference the closure
 * So that GC won't drop it and cause memory leaks
 */
function createWatchDependedConfigCallback(docContext) {
    return async (fileName, kind, modifiedTime) => {
        if (kind === typescript_1.default.FileWatcherEventKind.Changed &&
            !configFileModified(fileName, modifiedTime ?? docContext.tsSystem.getModifiedTime?.(fileName), docContext)) {
            return;
        }
        const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(docContext.tsSystem.useCaseSensitiveFileNames);
        docContext.extendedConfigCache.delete(getCanonicalFileName(fileName));
        // rely on TypeScript internal behavior so delete both just in case
        docContext.extendedConfigCache.delete(fileName);
        const reloadingConfigs = [];
        const promises = Array.from(configPathToDependedProject.get(fileName) ?? []).map(async (config) => {
            reloadingConfigs.push(config);
            const oldService = services.get(config);
            scheduleReload(config);
            (await oldService)?.dispose();
        });
        await Promise.all(promises);
        docContext.onProjectReloaded?.(reloadingConfigs);
    };
}
/**
 * check if file content is modified instead of attributes changed
 */
function configFileModified(fileName, modifiedTime, docContext) {
    const previousModifiedTime = configFileModifiedTime.get(fileName);
    if (!modifiedTime || !previousModifiedTime) {
        return true;
    }
    if (previousModifiedTime >= modifiedTime) {
        return false;
    }
    configFileModifiedTime.set(fileName, modifiedTime);
    const oldSourceFile = parsedTsConfigInfo.get(fileName)?.parsedCommandLine?.options.configFile ??
        docContext.extendedConfigCache.get(fileName)?.extendedResult;
    if (oldSourceFile &&
        typeof oldSourceFile === 'object' &&
        'kind' in oldSourceFile &&
        typeof oldSourceFile.text === 'string' &&
        oldSourceFile.text === docContext.tsSystem.readFile(fileName)) {
        return false;
    }
    return true;
}
/**
 * schedule to the service reload to the next time the
 * service in requested
 * if there's still files opened it should be restarted
 * in the onProjectReloaded hooks
 */
function scheduleReload(fileName) {
    // don't delete service from map yet as it could result in a race condition
    // where a file update is received before the service is reloaded, swallowing the update
    pendingReloads.add(fileName);
}
function getOrCreateDocumentRegistry(currentDirectory, useCaseSensitiveFileNames) {
    // unless it's a multi root workspace, there's only one registry
    const key = [currentDirectory, useCaseSensitiveFileNames].join('|');
    let registry = documentRegistries.get(key);
    if (registry) {
        return registry;
    }
    registry = typescript_1.default.createDocumentRegistry(useCaseSensitiveFileNames, currentDirectory);
    const acquireDocumentWithKey = registry.acquireDocumentWithKey;
    registry.acquireDocumentWithKey = (fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) => {
        ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
        return acquireDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
    };
    const updateDocumentWithKey = registry.updateDocumentWithKey;
    registry.updateDocumentWithKey = (fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions) => {
        ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions);
        return updateDocumentWithKey(fileName, path, compilationSettingsOrHost, key, scriptSnapshot, version, scriptKind, sourceFileOptions);
    };
    documentRegistries.set(key, registry);
    return registry;
    function ensureImpliedNodeFormat(compilationSettingsOrHost, fileName, sourceFileOptions) {
        const compilationSettings = getCompilationSettings(compilationSettingsOrHost);
        const host = compilationSettingsOrHost === compilationSettings
            ? undefined
            : compilationSettingsOrHost;
        if (host &&
            (0, utils_2.isSvelteFilePath)(fileName) &&
            typeof sourceFileOptions === 'object' &&
            !sourceFileOptions.impliedNodeFormat) {
            const format = typescript_1.default.getImpliedNodeFormatForFile((0, utils_2.toVirtualSvelteFilePath)(fileName), host?.getCompilerHost?.()?.getModuleResolutionCache?.()?.getPackageJsonInfoCache(), host, compilationSettings);
            sourceFileOptions.impliedNodeFormat = format;
        }
    }
    function getCompilationSettings(settingsOrHost) {
        if (typeof settingsOrHost.getCompilationSettings === 'function') {
            return settingsOrHost.getCompilationSettings();
        }
        return settingsOrHost;
    }
}
//# sourceMappingURL=service.js.map