"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createWebFileSystemHost = void 0;
const path = require("typesafe-path");
const vscode_html_languageservice_1 = require("vscode-html-languageservice");
const vscode = require("vscode-languageserver");
const vscode_uri_1 = require("vscode-uri");
const protocol_1 = require("../protocol");
const utilities_1 = require("./typescript/utilities");
const uriMap_1 = require("../common/utils/uriMap");
function createWebFileSystemHost(_0, _1, env, initOptions) {
    const instances = (0, uriMap_1.createUriMap)(env.fileNameToUri);
    const onDidChangeWatchedFilesCb = new Set();
    const root = {
        dirs: new Map(),
        files: new Map(),
        requested: false,
    };
    const fetchTasks = [];
    const changes = [];
    const onReadyCb = [];
    let runningRead = false;
    let runningStat = false;
    let connection;
    return {
        ready(_connection) {
            connection = _connection;
            connection.onDidChangeWatchedFiles(params => {
                for (const change of params.changes) {
                    const fsPath = env.uriToFileName(change.uri);
                    const dir = getDir(path.dirname(fsPath));
                    const name = path.basename(fsPath);
                    if (!dir.files.has(name)) {
                        dir.files.set(name, {});
                    }
                    if (change.type === vscode.FileChangeType.Created || change.type === vscode.FileChangeType.Changed) {
                        dir.files.get(name).exists = true;
                    }
                    else if (change.type === vscode.FileChangeType.Deleted) {
                        if (!dir.files.has(name)) {
                            dir.files.set(name, {});
                        }
                    }
                }
                changes.push(...params.changes);
                fireChanges();
            });
            for (const cb of onReadyCb) {
                cb(connection);
            }
            onReadyCb.length = 0;
        },
        reload() {
            root.dirs.clear();
            root.files.clear();
            root.requested = false;
        },
        getWorkspaceFileSystem(rootUri) {
            let sys = instances.uriGet(rootUri.toString());
            if (!sys) {
                sys = createWorkspaceFileSystem(rootUri);
                instances.uriSet(rootUri.toString(), sys);
            }
            return sys;
        },
        onDidChangeWatchedFiles: cb => {
            onDidChangeWatchedFilesCb.add(cb);
            return () => onDidChangeWatchedFilesCb.delete(cb);
        },
    };
    function createWorkspaceFileSystem(rootUri) {
        const rootPath = env.uriToFileName(rootUri.toString());
        return {
            newLine: '\n',
            useCaseSensitiveFileNames: false,
            getCurrentDirectory: () => env.uriToFileName(rootUri.toString()),
            fileExists,
            readFile,
            readDirectory,
            getDirectories,
            resolvePath,
            realpath: path => path, // TODO: cannot implement with vscode
        };
        function resolvePath(fsPath) {
            return path.resolve(fsPath);
        }
        function fileExists(fsPath) {
            fsPath = resolvePath(fsPath);
            const uri = env.fileNameToUri(fsPath);
            const dirPath = path.dirname(fsPath);
            if (initOptions.typescript?.cdn && uri.startsWith(initOptions.typescript.cdn)) {
                if (!(uri.endsWith('.d.ts') || uri.endsWith('/package.json'))) {
                    return false;
                }
                readFile(fsPath);
                const file = getDir(dirPath).files.get(path.basename(fsPath));
                return file?.exists ?? false;
            }
            readDirectory(dirPath);
            const file = getDir(path.dirname(fsPath)).files.get(path.basename(fsPath));
            return file?.exists ?? false;
        }
        function readFile(fsPath) {
            fsPath = resolvePath(fsPath);
            const uri = env.fileNameToUri(fsPath);
            const dirPath = path.dirname(fsPath);
            const dir = getDir(dirPath);
            const name = path.basename(fsPath);
            if (initOptions.typescript?.cdn && uri.startsWith(initOptions.typescript.cdn)) {
                if (!(uri.endsWith('.d.ts') || uri.endsWith('/package.json'))) {
                    return dir.files.get(name)?.text || '';
                }
                readFileWorker();
                return dir.files.get(name)?.text || '';
            }
            readFileWorker();
            return dir.files.get(name)?.text || '';
            function readFileWorker() {
                if (connection) {
                    fetch('load', fsPath, readFileAsync(connection, fsPath, dir));
                }
                else {
                    onReadyCb.push((connection) => fetch('load', fsPath, readFileAsync(connection, fsPath, dir)));
                }
            }
            async function readFileAsync(connection, fsPath, dir) {
                const name = path.basename(fsPath);
                if (!dir.files.has(name)) {
                    dir.files.set(name, {});
                }
                const file = dir.files.get(name);
                if (file.requested) {
                    return;
                }
                file.requested = true;
                const uri = env.fileNameToUri(fsPath);
                const data = await connection.sendRequest(protocol_1.FsReadFileRequest.type, resolveRequestUrl(uri));
                const text = data ? new TextDecoder('utf8').decode(data) : undefined;
                if (text?.length) {
                    file.exists = true;
                    file.text = text;
                    changes.push({ uri, type: vscode.FileChangeType.Changed });
                }
            }
        }
        function readDirectory(fsPath, extensions, exclude, include, depth) {
            fsPath = resolvePath(fsPath);
            return (0, utilities_1.matchFiles)(fsPath, extensions, exclude, include, false, rootPath, depth, (dirPath) => {
                dirPath = resolvePath(dirPath);
                readDirectoryWorker(dirPath);
                const dir = getDir(dirPath);
                return {
                    files: [...dir.files.entries()].filter(([_, file]) => file.exists).map(([name]) => name),
                    directories: [...dir.dirs.entries()].filter(([_, dir]) => dir.exists).map(([name]) => name),
                };
            }, path => path);
        }
        // for import path completion
        function getDirectories(fsPath) {
            fsPath = resolvePath(fsPath);
            readDirectoryWorker(fsPath);
            const dir = getDir(fsPath);
            return [...dir.dirs.entries()].filter(([_, dir]) => dir.exists).map(([name]) => name);
        }
        function readDirectoryWorker(fsPath) {
            if (connection) {
                fetch('directory', fsPath, readDirectoryAsync(connection, fsPath));
            }
            else {
                onReadyCb.push((connection) => fetch('directory', fsPath, readDirectoryAsync(connection, fsPath)));
            }
        }
        async function readDirectoryAsync(connection, fsPath) {
            const dir = getDir(fsPath);
            if (dir.requested) {
                return;
            }
            dir.requested = true;
            const uri = env.fileNameToUri(fsPath);
            const result = await connection.sendRequest(protocol_1.FsReadDirectoryRequest.type, resolveRequestUrl(uri));
            for (const [name, fileType] of result) {
                if (fileType === vscode_html_languageservice_1.FileType.File || fileType === vscode_html_languageservice_1.FileType.SymbolicLink) {
                    if (!dir.files.has(name)) {
                        dir.files.set(name, {});
                    }
                    const file = dir.files.get(name);
                    if (!file.exists) {
                        file.exists = true;
                        changes.push({
                            uri: env.fileNameToUri(path.join(fsPath, name)),
                            type: vscode.FileChangeType.Created,
                        });
                    }
                }
                else if (fileType === vscode_html_languageservice_1.FileType.Directory && !dir.dirs.has(name)) {
                    const childDir = getDirFromDir(dir, name);
                    childDir.exists = true;
                }
            }
        }
    }
    async function fetch(action, fileName, p) {
        fetchTasks.push([action, fileName, p]);
        if (runningRead === false) {
            let toUpdate;
            runningRead = true;
            const progress = await connection?.window.createWorkDoneProgress();
            progress?.begin('');
            while (fetchTasks.length) {
                const current = fetchTasks.shift();
                updateProgress(current[0] + ' ' + vscode_uri_1.URI.parse(env.fileNameToUri(current[1])).fsPath);
                await current[2];
            }
            progress?.done();
            runningRead = false;
            fireChanges();
            function updateProgress(text) {
                clearTimeout(toUpdate);
                toUpdate = setTimeout(() => {
                    progress?.report(text);
                    toUpdate = undefined;
                }, 0);
            }
        }
    }
    function resolveRequestUrl(uri) {
        if (initOptions.typescript?.cdn && uri.startsWith(initOptions.typescript.cdn)) {
            let fileName = env.uriToFileName(uri);
            for (const [key, version] of Object.entries(initOptions.typescript.versions || {})) {
                if (fileName.startsWith(`/node_modules/${key}/`)) {
                    fileName = fileName.replace(`/node_modules/${key}/`, `/node_modules/${key}@${version}/`);
                    return env.fileNameToUri(fileName);
                }
            }
        }
        return uri;
    }
    async function fireChanges() {
        if (runningRead || runningStat) {
            return;
        }
        const _changes = [...changes];
        changes.length = 0;
        for (const cb of [...onDidChangeWatchedFilesCb]) {
            if (onDidChangeWatchedFilesCb.has(cb)) {
                await cb({ changes: _changes });
            }
        }
    }
    function getDir(dirPath) {
        const dirNames = [];
        let currentDirPath = dirPath;
        let currentDirName = path.basename(currentDirPath);
        while (currentDirName !== '') {
            dirNames.push(currentDirName);
            currentDirPath = path.dirname(currentDirPath);
            currentDirName = path.basename(currentDirPath);
        }
        let currentDir = root;
        for (let i = dirNames.length - 1; i >= 0; i--) {
            const nextDirName = dirNames[i];
            currentDir = getDirFromDir(currentDir, nextDirName);
        }
        return currentDir;
    }
    function getDirFromDir(dir, name) {
        let target = dir.dirs.get(name);
        if (!target) {
            target = {
                dirs: new Map(),
                files: new Map(),
            };
            dir.dirs.set(name, target);
        }
        return target;
    }
}
exports.createWebFileSystemHost = createWebFileSystemHost;
//# sourceMappingURL=fileSystems.js.map