"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.startNamedPipeServer = startNamedPipeServer;
const language_core_1 = require("@vue/language-core");
const fs = require("node:fs");
const net = require("node:net");
const collectExtractProps_1 = require("./requests/collectExtractProps");
const getComponentDirectives_1 = require("./requests/getComponentDirectives");
const getComponentEvents_1 = require("./requests/getComponentEvents");
const getComponentNames_1 = require("./requests/getComponentNames");
const getComponentProps_1 = require("./requests/getComponentProps");
const getElementAttrs_1 = require("./requests/getElementAttrs");
const getImportPathForFile_1 = require("./requests/getImportPathForFile");
const getPropertiesAtLocation_1 = require("./requests/getPropertiesAtLocation");
const getQuickInfoAtPosition_1 = require("./requests/getQuickInfoAtPosition");
const utils_1 = require("./utils");
async function startNamedPipeServer(ts, info, language, projectKind) {
    let lastProjectVersion;
    const requestContext = {
        typescript: ts,
        languageService: info.languageService,
        languageServiceHost: info.languageServiceHost,
        language: language,
        isTsPlugin: true,
        getFileId: (fileName) => fileName,
    };
    const dataChunks = [];
    const currentData = new language_core_1.FileMap(false);
    const allConnections = new Set();
    const pendingRequests = new Set();
    const server = net.createServer(connection => {
        allConnections.add(connection);
        connection.on('end', () => {
            allConnections.delete(connection);
        });
        connection.on('data', buffer => {
            dataChunks.push(buffer);
            const text = dataChunks.toString();
            if (text.endsWith('\n\n')) {
                dataChunks.length = 0;
                const requests = text.split('\n\n');
                for (let json of requests) {
                    json = json.trim();
                    if (!json) {
                        continue;
                    }
                    try {
                        onRequest(connection, JSON.parse(json));
                    }
                    catch (e) {
                        console.error('[Vue Named Pipe Server] JSON parse error:', e);
                    }
                }
            }
        });
        connection.on('error', err => console.error('[Vue Named Pipe Server]', err.message));
        for (const [fileName, [componentNames, componentProps]] of currentData) {
            notify(connection, 'componentNamesUpdated', fileName, componentNames);
            for (const [name, props] of Object.entries(componentProps)) {
                notify(connection, 'componentPropsUpdated', fileName, [name, props]);
            }
        }
    });
    for (let i = 0; i < 10; i++) {
        const path = (0, utils_1.getServerPath)(projectKind, i);
        const socket = await connect(path, 100);
        if (typeof socket === 'object') {
            socket.end();
        }
        const namedPipeOccupied = typeof socket === 'object' || socket === 'timeout';
        if (namedPipeOccupied) {
            continue;
        }
        const success = await tryListen(server, path);
        if (success) {
            break;
        }
    }
    updateWhile();
    async function updateWhile() {
        while (true) {
            await sleep(500);
            const projectVersion = info.project.getProjectVersion();
            if (lastProjectVersion === projectVersion) {
                continue;
            }
            const connections = [...allConnections].filter(c => !c.destroyed);
            if (!connections.length) {
                continue;
            }
            const token = info.languageServiceHost.getCancellationToken?.();
            const openedScriptInfos = info.project.getRootScriptInfos().filter(info => info.isScriptOpen());
            if (!openedScriptInfos.length) {
                continue;
            }
            for (const scriptInfo of openedScriptInfos) {
                await sleep(10);
                if (token?.isCancellationRequested()) {
                    break;
                }
                let data = currentData.get(scriptInfo.fileName);
                if (!data) {
                    data = [[], {}];
                    currentData.set(scriptInfo.fileName, data);
                }
                const [oldComponentNames, componentProps] = data;
                const newComponentNames = getComponentNames_1.getComponentNames.apply(requestContext, [scriptInfo.fileName]) ?? [];
                if (JSON.stringify(oldComponentNames) !== JSON.stringify(newComponentNames)) {
                    data[0] = newComponentNames;
                    for (const connection of connections) {
                        notify(connection, 'componentNamesUpdated', scriptInfo.fileName, newComponentNames);
                    }
                }
                for (const [name, props] of Object.entries(componentProps)) {
                    await sleep(10);
                    if (token?.isCancellationRequested()) {
                        break;
                    }
                    const newProps = getComponentProps_1.getComponentProps.apply(requestContext, [scriptInfo.fileName, name]) ?? [];
                    if (JSON.stringify(props) !== JSON.stringify(newProps)) {
                        componentProps[name] = newProps;
                        for (const connection of connections) {
                            notify(connection, 'componentPropsUpdated', scriptInfo.fileName, [name, newProps]);
                        }
                    }
                }
            }
            lastProjectVersion = projectVersion;
        }
    }
    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    function notify(connection, type, fileName, data) {
        connection.write(JSON.stringify([type, fileName, data]) + '\n\n');
    }
    function onRequest(connection, [seq, requestType, ...args]) {
        if (pendingRequests.has(seq)) {
            return;
        }
        setTimeout(() => pendingRequests.delete(seq), 500);
        pendingRequests.add(seq);
        let data;
        try {
            data = handleRequest(requestType, ...args);
        }
        catch {
            data = null;
        }
        connection.write(JSON.stringify([seq, data ?? null]) + '\n\n');
    }
    function handleRequest(requestType, ...args) {
        const fileName = args[0];
        if (requestType === 'projectInfo') {
            return {
                name: info.project.getProjectName(),
                kind: info.project.projectKind,
                currentDirectory: info.project.getCurrentDirectory(),
            };
        }
        else if (requestType === 'containsFile') {
            return info.project.containsFile(ts.server.toNormalizedPath(fileName));
        }
        else if (requestType === 'collectExtractProps') {
            return collectExtractProps_1.collectExtractProps.apply(requestContext, args);
        }
        else if (requestType === 'getImportPathForFile') {
            return getImportPathForFile_1.getImportPathForFile.apply(requestContext, args);
        }
        else if (requestType === 'getPropertiesAtLocation') {
            return getPropertiesAtLocation_1.getPropertiesAtLocation.apply(requestContext, args);
        }
        else if (requestType === 'getQuickInfoAtPosition') {
            return getQuickInfoAtPosition_1.getQuickInfoAtPosition.apply(requestContext, args);
        }
        else if (requestType === 'subscribeComponentProps') {
            const tag = args[1];
            const props = getComponentProps_1.getComponentProps.apply(requestContext, [fileName, tag]) ?? [];
            let data = currentData.get(fileName);
            if (!data) {
                data = [[], {}];
                currentData.set(fileName, data);
            }
            data[1][tag] = props;
            return props;
        }
        else if (requestType === 'getComponentEvents') {
            return getComponentEvents_1.getComponentEvents.apply(requestContext, args);
        }
        else if (requestType === 'getComponentDirectives') {
            return getComponentDirectives_1.getComponentDirectives.apply(requestContext, args);
        }
        else if (requestType === 'getElementAttrs') {
            return getElementAttrs_1.getElementAttrs.apply(requestContext, args);
        }
        console.warn('[Vue Named Pipe Server] Unknown request:', requestType);
        debugger;
        return undefined;
    }
}
function connect(namedPipePath, timeout) {
    return new Promise(resolve => {
        const socket = net.connect(namedPipePath);
        if (timeout) {
            socket.setTimeout(timeout);
        }
        const onConnect = () => {
            cleanup();
            resolve(socket);
        };
        const onError = (err) => {
            if (err.code === 'ECONNREFUSED') {
                try {
                    console.log('[Vue Named Pipe Client] Deleting:', namedPipePath);
                    fs.promises.unlink(namedPipePath);
                }
                catch { }
            }
            cleanup();
            resolve('error');
            socket.end();
        };
        const onTimeout = () => {
            cleanup();
            resolve('timeout');
            socket.end();
        };
        const cleanup = () => {
            socket.off('connect', onConnect);
            socket.off('error', onError);
            socket.off('timeout', onTimeout);
        };
        socket.on('connect', onConnect);
        socket.on('error', onError);
        socket.on('timeout', onTimeout);
    });
}
function tryListen(server, namedPipePath) {
    return new Promise(resolve => {
        const onSuccess = () => {
            server.off('error', onError);
            resolve(true);
        };
        const onError = (err) => {
            if (err.code === 'ECONNREFUSED') {
                try {
                    console.log('[Vue Named Pipe Client] Deleting:', namedPipePath);
                    fs.promises.unlink(namedPipePath);
                }
                catch { }
            }
            server.off('error', onError);
            server.close();
            resolve(false);
        };
        server.listen(namedPipePath, onSuccess);
        server.on('error', onError);
    });
}
//# sourceMappingURL=server.js.map