"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ignoredBuildDirectories = exports.SnapshotManager = exports.GlobalSnapshotsManager = void 0;
const typescript_1 = __importDefault(require("typescript"));
const DocumentSnapshot_1 = require("./DocumentSnapshot");
const logger_1 = require("../../logger");
const utils_1 = require("../../utils");
const events_1 = require("events");
const fileCollection_1 = require("../../lib/documents/fileCollection");
/**
 * Every snapshot corresponds to a unique file on disk.
 * A snapshot can be part of multiple projects, but for a given file path
 * there can be only one snapshot.
 */
class GlobalSnapshotsManager {
    constructor(tsSystem) {
        this.tsSystem = tsSystem;
        this.emitter = new events_1.EventEmitter();
        this.documents = new fileCollection_1.FileMap(tsSystem.useCaseSensitiveFileNames);
        this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames);
    }
    get(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        return this.documents.get(fileName);
    }
    getByPrefix(path) {
        path = this.getCanonicalFileName((0, utils_1.normalizePath)(path));
        const results = [];
        for (const [key, value] of this.documents.entries()) {
            if (key.startsWith(path)) {
                results.push(value);
            }
        }
        return results;
    }
    set(fileName, document) {
        fileName = (0, utils_1.normalizePath)(fileName);
        this.documents.set(fileName, document);
        this.emitter.emit('change', fileName, document);
    }
    delete(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        this.documents.delete(fileName);
        this.emitter.emit('change', fileName, undefined);
    }
    updateTsOrJsFile(fileName, changes) {
        fileName = (0, utils_1.normalizePath)(fileName);
        const previousSnapshot = this.get(fileName);
        if (changes) {
            if (!(previousSnapshot instanceof DocumentSnapshot_1.JSOrTSDocumentSnapshot)) {
                return;
            }
            const newSnapshot = previousSnapshot.update(changes);
            this.set(fileName, newSnapshot);
            return newSnapshot;
        }
        else {
            const newSnapshot = DocumentSnapshot_1.DocumentSnapshot.fromNonSvelteFilePath(fileName, this.tsSystem);
            if (previousSnapshot) {
                newSnapshot.version = previousSnapshot.version + 1;
            }
            else {
                // ensure it's greater than initial version
                // so that ts server picks up the change
                newSnapshot.version += 1;
            }
            this.set(fileName, newSnapshot);
            return newSnapshot;
        }
    }
    onChange(listener) {
        this.emitter.on('change', listener);
    }
    removeChangeListener(listener) {
        this.emitter.off('change', listener);
    }
}
exports.GlobalSnapshotsManager = GlobalSnapshotsManager;
/**
 * Should only be used by `service.ts`
 */
class SnapshotManager {
    constructor(globalSnapshotsManager, fileSpec, workspaceRoot, tsSystem, projectFiles, wildcardDirectories) {
        this.globalSnapshotsManager = globalSnapshotsManager;
        this.fileSpec = fileSpec;
        this.workspaceRoot = workspaceRoot;
        this.tsSystem = tsSystem;
        this.lastLogged = new Date(new Date().getTime() - 60_001);
        this.watchExtensions = [
            typescript_1.default.Extension.Dts,
            typescript_1.default.Extension.Dcts,
            typescript_1.default.Extension.Dmts,
            typescript_1.default.Extension.Js,
            typescript_1.default.Extension.Cjs,
            typescript_1.default.Extension.Mjs,
            typescript_1.default.Extension.Jsx,
            typescript_1.default.Extension.Ts,
            typescript_1.default.Extension.Mts,
            typescript_1.default.Extension.Cts,
            typescript_1.default.Extension.Tsx,
            typescript_1.default.Extension.Json,
            '.svelte'
        ];
        this.onSnapshotChange = this.onSnapshotChange.bind(this);
        this.globalSnapshotsManager.onChange(this.onSnapshotChange);
        this.documents = new fileCollection_1.FileMap(tsSystem.useCaseSensitiveFileNames);
        this.projectFileToOriginalCasing = new Map();
        this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(tsSystem.useCaseSensitiveFileNames);
        projectFiles.forEach((originalCasing) => this.projectFileToOriginalCasing.set(this.getCanonicalFileName(originalCasing), originalCasing));
        this.watchingCanonicalDirectories = new Map(Object.entries(wildcardDirectories ?? {}).map(([dir, flags]) => [
            this.getCanonicalFileName(dir),
            flags
        ]));
    }
    onSnapshotChange(fileName, document) {
        // Only delete/update snapshots, don't add new ones,
        // as they could be from another TS service and this
        // snapshot manager can't reach this file.
        // For these, instead wait on a `get` method invocation
        // and set them "manually" in the set/update methods.
        if (!document) {
            this.documents.delete(fileName);
            this.projectFileToOriginalCasing.delete(this.getCanonicalFileName(fileName));
        }
        else if (this.documents.has(fileName)) {
            this.documents.set(fileName, document);
        }
    }
    areIgnoredFromNewFileWatch(watcherNewFiles) {
        const { include } = this.fileSpec;
        // Since we default to not include anything,
        //  just don't waste time on this
        if (include?.length === 0 || !this.watchingCanonicalDirectories) {
            return true;
        }
        for (const newFile of watcherNewFiles) {
            const path = this.getCanonicalFileName((0, utils_1.normalizePath)(newFile));
            if (this.projectFileToOriginalCasing.has(path)) {
                continue;
            }
            for (const [dir, flags] of this.watchingCanonicalDirectories) {
                if (path.startsWith(dir)) {
                    if (!(flags & typescript_1.default.WatchDirectoryFlags.Recursive)) {
                        const relative = path.slice(dir.length);
                        if (relative.includes('/')) {
                            continue;
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    updateProjectFiles() {
        const { include, exclude } = this.fileSpec;
        if (include?.length === 0) {
            return;
        }
        const projectFiles = this.tsSystem
            .readDirectory(this.workspaceRoot, this.watchExtensions, exclude, include)
            .map(utils_1.normalizePath);
        projectFiles.forEach((projectFile) => this.projectFileToOriginalCasing.set(this.getCanonicalFileName(projectFile), projectFile));
    }
    updateTsOrJsFile(fileName, changes) {
        const snapshot = this.globalSnapshotsManager.updateTsOrJsFile(fileName, changes);
        // This isn't duplicated logic to the listener, because this could
        // be a new snapshot which the listener wouldn't add.
        if (snapshot) {
            this.documents.set((0, utils_1.normalizePath)(fileName), snapshot);
        }
    }
    has(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        return (this.projectFileToOriginalCasing.has(this.getCanonicalFileName(fileName)) ||
            this.documents.has(fileName));
    }
    set(fileName, snapshot) {
        this.globalSnapshotsManager.set(fileName, snapshot);
        // This isn't duplicated logic to the listener, because this could
        // be a new snapshot which the listener wouldn't add.
        this.documents.set((0, utils_1.normalizePath)(fileName), snapshot);
        this.logStatistics();
    }
    get(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        let snapshot = this.documents.get(fileName);
        if (!snapshot) {
            snapshot = this.globalSnapshotsManager.get(fileName);
            if (snapshot) {
                this.documents.set(fileName, snapshot);
            }
        }
        return snapshot;
    }
    delete(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        this.globalSnapshotsManager.delete(fileName);
    }
    getClientFileNames() {
        return Array.from(this.documents.values())
            .filter((doc) => doc.isOpenedInClient())
            .map((doc) => doc.filePath);
    }
    getProjectFileNames() {
        return Array.from(this.projectFileToOriginalCasing.values());
    }
    isProjectFile(fileName) {
        fileName = (0, utils_1.normalizePath)(fileName);
        return this.projectFileToOriginalCasing.has(this.getCanonicalFileName(fileName));
    }
    logStatistics() {
        const date = new Date();
        // Don't use setInterval because that will keep tests running forever
        if (date.getTime() - this.lastLogged.getTime() > 60_000) {
            this.lastLogged = date;
            const allFiles = new Set();
            for (const key of this.projectFileToOriginalCasing.keys()) {
                allFiles.add(key);
            }
            for (const key of this.documents.keys()) {
                allFiles.add(key);
            }
            let svelteCount = 0;
            let nodeModulesCount = 0;
            for (const name of allFiles) {
                if (name.endsWith('.svelte')) {
                    svelteCount++;
                }
                if (name.includes('node_modules')) {
                    nodeModulesCount++;
                }
            }
            logger_1.Logger.log('SnapshotManager File Statistics:\n' +
                `Project files: ${this.projectFileToOriginalCasing.size}\n` +
                `Svelte files: ${svelteCount}\n` +
                `From node_modules: ${nodeModulesCount}\n` +
                `Total: ${allFiles.size}`);
        }
    }
    allFilesAreJsOrDts() {
        for (const doc of this.documents.values()) {
            if (doc.scriptKind === typescript_1.default.ScriptKind.TS || doc.scriptKind === typescript_1.default.ScriptKind.TSX) {
                return false;
            }
        }
        return true;
    }
    dispose() {
        this.globalSnapshotsManager.removeChangeListener(this.onSnapshotChange);
    }
}
exports.SnapshotManager = SnapshotManager;
exports.ignoredBuildDirectories = ['__sapper__', '.svelte-kit'];
//# sourceMappingURL=SnapshotManager.js.map