"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 () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createVirtualTsSystem = createVirtualTsSystem;
exports.getRandomVirtualDirPath = getRandomVirtualDirPath;
exports.setupVirtualEnvironment = setupVirtualEnvironment;
exports.createSnapshotTester = createSnapshotTester;
exports.updateSnapshotIfFailedOrEmpty = updateSnapshotIfFailedOrEmpty;
exports.createJsonSnapshotFormatter = createJsonSnapshotFormatter;
exports.serviceWarmup = serviceWarmup;
exports.recursiveServiceWarmup = recursiveServiceWarmup;
const path_1 = __importStar(require("path"));
const fs_1 = require("fs");
const typescript_1 = __importDefault(require("typescript"));
const prettier_1 = require("prettier");
const documents_1 = require("../../../src/lib/documents");
const fileCollection_1 = require("../../../src/lib/documents/fileCollection");
const ls_config_1 = require("../../../src/ls-config");
const plugins_1 = require("../../../src/plugins");
const utils_1 = require("../../../src/utils");
const compiler_1 = require("svelte/compiler");
const utils_2 = require("../../../src/plugins/typescript/utils");
const isSvelte5Plus = Number(compiler_1.VERSION.split('.')[0]) >= 5;
function createVirtualTsSystem(currentDirectory) {
    const virtualFs = new fileCollection_1.FileMap();
    // array behave more similar to the actual fs event than Set
    const watchers = new fileCollection_1.FileMap();
    const watchTimeout = new fileCollection_1.FileMap();
    const getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(typescript_1.default.sys.useCaseSensitiveFileNames);
    const modifiedTime = new fileCollection_1.FileMap();
    function toAbsolute(path) {
        return (0, path_1.isAbsolute)(path) ? path : (0, path_1.join)(currentDirectory, path);
    }
    const virtualSystem = {
        ...typescript_1.default.sys,
        getCurrentDirectory() {
            return currentDirectory;
        },
        writeFile(path, data) {
            const normalizedPath = (0, utils_1.normalizePath)(toAbsolute(path));
            const existsBefore = virtualFs.has(normalizedPath);
            virtualFs.set(normalizedPath, data);
            const lastModified = modifiedTime.get(normalizedPath);
            let newModifiedTime = new Date();
            if (lastModified && newModifiedTime <= lastModified) {
                newModifiedTime = new Date(lastModified.getTime() + 1);
            }
            // make sure modified time is always increasing to trigger watchers reliably in tests
            modifiedTime.set(normalizedPath, newModifiedTime);
            triggerWatch(normalizedPath, existsBefore ? typescript_1.default.FileWatcherEventKind.Changed : typescript_1.default.FileWatcherEventKind.Created);
        },
        readFile(path) {
            return virtualFs.get((0, utils_1.normalizePath)(toAbsolute(path)));
        },
        fileExists(path) {
            return virtualFs.has((0, utils_1.normalizePath)(toAbsolute(path)));
        },
        directoryExists(path) {
            const normalizedPath = getCanonicalFileName((0, utils_1.normalizePath)(toAbsolute(path)));
            return Array.from(virtualFs.keys()).some((fileName) => fileName.startsWith(normalizedPath));
        },
        deleteFile(path) {
            const normalizedPath = (0, utils_1.normalizePath)(toAbsolute(path));
            const existsBefore = virtualFs.has(normalizedPath);
            virtualFs.delete(normalizedPath);
            if (existsBefore) {
                triggerWatch(normalizedPath, typescript_1.default.FileWatcherEventKind.Deleted);
            }
        },
        watchFile(path, callback) {
            const normalizedPath = (0, utils_1.normalizePath)(toAbsolute(path));
            let watchersOfPath = watchers.get(normalizedPath);
            if (!watchersOfPath) {
                watchersOfPath = [];
                watchers.set(normalizedPath, watchersOfPath);
            }
            watchersOfPath.push(callback);
            return {
                close() {
                    const watchersOfPath = watchers.get(normalizedPath);
                    if (watchersOfPath) {
                        watchers.set(normalizedPath, watchersOfPath.filter((watcher) => watcher === callback));
                    }
                    const timeouts = watchTimeout.get(normalizedPath);
                    if (timeouts != null) {
                        timeouts.forEach((timeout) => clearTimeout(timeout));
                    }
                }
            };
        },
        getModifiedTime(path) {
            return modifiedTime.get((0, utils_1.normalizePath)(toAbsolute(path)));
        },
        readDirectory(path, _extensions, _exclude, include, _depth) {
            if (include && (include.length != 1 || include[0] !== '**/*')) {
                throw new Error('include pattern matching not implemented. Mock it if the test needs it. Pattern: ' +
                    include);
            }
            const normalizedPath = getCanonicalFileName((0, utils_1.normalizePath)(toAbsolute(path)));
            return Array.from(virtualFs.keys()).filter((fileName) => fileName.startsWith(normalizedPath));
        }
    };
    return virtualSystem;
    function triggerWatch(normalizedPath, kind) {
        // if watcher is not set yet. don't trigger it
        if (!watchers.has(normalizedPath)) {
            return;
        }
        let timeoutsOfPath = watchTimeout.get(normalizedPath);
        if (!timeoutsOfPath) {
            timeoutsOfPath = [];
            watchTimeout.set(normalizedPath, timeoutsOfPath);
        }
        timeoutsOfPath.push(setTimeout(() => watchers
            .get(normalizedPath)
            ?.forEach((callback) => callback(normalizedPath, kind)), 0));
    }
}
function getRandomVirtualDirPath(testDir) {
    return path_1.default.join(testDir, `virtual-path-${Math.floor(Math.random() * 100_000)}`);
}
function setupVirtualEnvironment({ testDir, fileContent, filename }) {
    const docManager = new documents_1.DocumentManager((textDocument) => documents_1.Document.createForTest(textDocument.uri, textDocument.text));
    const lsConfigManager = new ls_config_1.LSConfigManager();
    const virtualSystem = createVirtualTsSystem(testDir);
    const lsAndTsDocResolver = new plugins_1.LSAndTSDocResolver(docManager, [(0, utils_1.pathToUrl)(testDir)], lsConfigManager, {
        tsSystem: virtualSystem
    });
    const filePath = (0, path_1.join)(testDir, filename);
    virtualSystem.writeFile(filePath, fileContent);
    const document = docManager.openClientDocument({
        uri: (0, utils_1.pathToUrl)(filePath),
        text: virtualSystem.readFile(filePath) || ''
    });
    return {
        lsAndTsDocResolver,
        document,
        docManager,
        virtualSystem,
        lsConfigManager
    };
}
function createSnapshotTester(executeTest) {
    return (testOptions) => {
        serviceWarmup(testOptions.context, testOptions.dir, (0, utils_1.pathToUrl)(testOptions.workspaceDir));
        executeTests(testOptions);
    };
    function executeTests(testOptions) {
        const { dir } = testOptions;
        const workspaceUri = (0, utils_1.pathToUrl)(testOptions.workspaceDir);
        const inputFile = (0, path_1.join)(dir, 'input.svelte');
        const tsconfig = (0, path_1.join)(dir, 'tsconfig.json');
        const jsconfig = (0, path_1.join)(dir, 'jsconfig.json');
        if ((0, fs_1.existsSync)(tsconfig) || (0, fs_1.existsSync)(jsconfig)) {
            serviceWarmup(testOptions.context, dir, workspaceUri);
        }
        if ((0, fs_1.existsSync)(inputFile)) {
            const _it = dir.endsWith('.v5') && !isSvelte5Plus
                ? it.skip
                : dir.endsWith('.only')
                    ? it.only
                    : it;
            _it(dir.substring(__dirname.length), () => executeTest(inputFile, testOptions));
        }
        else {
            const _describe = dir.endsWith('.only') ? describe.only : describe;
            _describe(dir.substring(__dirname.length), function () {
                const subDirs = (0, fs_1.readdirSync)(dir);
                for (const subDir of subDirs) {
                    const stat = (0, fs_1.statSync)((0, path_1.join)(dir, subDir));
                    if (stat.isDirectory()) {
                        executeTests({
                            ...testOptions,
                            context: this,
                            dir: (0, path_1.join)(dir, subDir)
                        });
                    }
                }
            });
        }
    }
}
async function updateSnapshotIfFailedOrEmpty({ assertion, expectedFile, rootDir, getFileContent }) {
    if ((0, fs_1.existsSync)(expectedFile)) {
        try {
            assertion();
        }
        catch (e) {
            if (process.argv.includes('--auto')) {
                await writeFile(`Updated ${expectedFile} for`);
            }
            else {
                throw e;
            }
        }
    }
    else {
        await writeFile(`Created ${expectedFile} for`);
    }
    async function writeFile(msg) {
        console.info(msg, (0, path_1.dirname)(expectedFile).substring(rootDir.length));
        (0, fs_1.writeFileSync)(expectedFile, await getFileContent(), 'utf-8');
    }
}
async function createJsonSnapshotFormatter(dir) {
    const prettierOptions = await (0, prettier_1.resolveConfig)(dir);
    return (obj) => (0, prettier_1.format)(JSON.stringify(obj), {
        ...prettierOptions,
        parser: 'json'
    });
}
function serviceWarmup(suite, testDir, rootUri = (0, utils_1.pathToUrl)(testDir), tsconfigPath = undefined) {
    const defaultTimeout = suite.timeout();
    // allow to set a higher timeout for slow machines from cli flag
    const warmupTimeout = Math.max(defaultTimeout, 5_000);
    suite.timeout(warmupTimeout);
    before(() => warmup(tsconfigPath));
    suite.timeout(defaultTimeout);
    async function warmup(configFilePath = undefined) {
        const start = Date.now();
        console.log('Warming up language service...');
        const docManager = new documents_1.DocumentManager((textDocument) => documents_1.Document.createForTest(textDocument.uri, textDocument.text));
        const lsAndTsDocResolver = new plugins_1.LSAndTSDocResolver(docManager, [rootUri], new ls_config_1.LSConfigManager());
        configFilePath ??= (0, utils_2.findTsConfigPath)((0, path_1.join)(testDir, 'DoesNotMater.svelte'), [rootUri], typescript_1.default.sys.fileExists, (0, utils_1.createGetCanonicalFileName)(typescript_1.default.sys.useCaseSensitiveFileNames));
        const ls = await lsAndTsDocResolver.getTSServiceByConfigPath(configFilePath, configFilePath ? (0, path_1.dirname)(configFilePath) : (0, utils_1.urlToPath)(rootUri));
        ls.getService();
        const projectReferences = ls.getResolvedProjectReferences();
        if (projectReferences.length) {
            await Promise.all(projectReferences.map((ref) => warmup(ref.configFilePath)));
        }
        console.log(`Service warming up done in ${Date.now() - start}ms`);
    }
}
function recursiveServiceWarmup(suite, testDir, rootUri = (0, utils_1.pathToUrl)(testDir)) {
    serviceWarmup(suite, testDir, rootUri);
    recursiveServiceWarmupNonRoot(suite, testDir, rootUri);
}
function recursiveServiceWarmupNonRoot(suite, testDir, rootUri = (0, utils_1.pathToUrl)(testDir)) {
    const subDirs = (0, fs_1.readdirSync)(testDir);
    for (const subDirOrFile of subDirs) {
        const stat = (0, fs_1.statSync)((0, path_1.join)(testDir, subDirOrFile));
        if (stat.isFile() &&
            (subDirOrFile === 'tsconfig.json' || subDirOrFile === 'jsconfig.json')) {
            serviceWarmup(suite, testDir, rootUri);
        }
        if (stat.isDirectory()) {
            recursiveServiceWarmupNonRoot(suite, (0, path_1.join)(testDir, subDirOrFile), rootUri);
        }
    }
}
//# sourceMappingURL=test-utils.js.map