"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.onServerReady = exports.inferredServers = exports.configuredServers = void 0;
exports.getServerPath = getServerPath;
exports.getBestServer = getBestServer;
const language_core_1 = require("@vue/language-core");
const shared_1 = require("@vue/shared");
const fs = require("node:fs");
const net = require("node:net");
const os = require("node:os");
const path = require("node:path");
const { version } = require('../package.json');
const platform = os.platform();
const pipeDir = platform === 'win32'
    ? `\\\\.\\pipe\\`
    : `/tmp/`;
function getServerPath(kind, id) {
    if (kind === 1) {
        return `${pipeDir}vue-named-pipe-${version}-configured-${id}`;
    }
    else {
        return `${pipeDir}vue-named-pipe-${version}-inferred-${id}`;
    }
}
class NamedPipeServer {
    constructor(kind, id) {
        this.connecting = false;
        this.containsFileCache = new Map();
        this.componentNamesAndProps = new language_core_1.FileMap(false);
        this.seq = 0;
        this.dataChunks = [];
        this.requestHandlers = new Map();
        this.path = getServerPath(kind, id);
    }
    containsFile(fileName) {
        if (this.projectInfo) {
            if (!this.containsFileCache.has(fileName)) {
                this.containsFileCache.set(fileName, (async () => {
                    const res = await this.sendRequest('containsFile', fileName);
                    if (typeof res !== 'boolean') {
                        // If the request fails, delete the cache
                        this.containsFileCache.delete(fileName);
                    }
                    return res;
                })());
            }
            return this.containsFileCache.get(fileName);
        }
    }
    async getComponentProps(fileName, tag) {
        const componentAndProps = this.componentNamesAndProps.get(fileName);
        if (!componentAndProps) {
            return;
        }
        const props = componentAndProps[tag]
            ?? componentAndProps[(0, shared_1.camelize)(tag)]
            ?? componentAndProps[(0, shared_1.capitalize)((0, shared_1.camelize)(tag))];
        if (props) {
            return props;
        }
        return await this.sendRequest('subscribeComponentProps', fileName, tag);
    }
    update() {
        if (!this.connecting && !this.projectInfo) {
            this.connecting = true;
            this.connect();
        }
    }
    connect() {
        this.socket = net.connect(this.path);
        this.socket.on('data', this.onData.bind(this));
        this.socket.on('connect', async () => {
            const projectInfo = await this.sendRequest('projectInfo', '');
            if (projectInfo) {
                console.log('TSServer project ready:', projectInfo.name);
                this.projectInfo = projectInfo;
                this.containsFileCache.clear();
                exports.onServerReady.forEach(cb => cb());
            }
            else {
                this.close();
            }
        });
        this.socket.on('error', err => {
            if (err.code === 'ECONNREFUSED') {
                try {
                    console.log('Deleteing invalid named pipe file:', this.path);
                    fs.promises.unlink(this.path);
                }
                catch { }
            }
            this.close();
        });
        this.socket.on('timeout', () => {
            this.close();
        });
    }
    close() {
        this.connecting = false;
        this.projectInfo = undefined;
        this.socket?.end();
    }
    onData(chunk) {
        this.dataChunks.push(chunk);
        const data = Buffer.concat(this.dataChunks);
        const text = data.toString();
        if (text.endsWith('\n\n')) {
            this.dataChunks.length = 0;
            const results = text.split('\n\n');
            for (let result of results) {
                result = result.trim();
                if (!result) {
                    continue;
                }
                try {
                    const data = JSON.parse(result.trim());
                    if (typeof data[0] === 'number') {
                        const [seq, res] = data;
                        this.requestHandlers.get(seq)?.(res);
                    }
                    else {
                        const [type, fileName, res] = data;
                        this.onNotification(type, fileName, res);
                    }
                }
                catch (e) {
                    console.error('JSON parse error:', e);
                }
            }
        }
    }
    onNotification(type, fileName, data) {
        // console.log(`[${type}] ${fileName} ${JSON.stringify(data)}`);
        if (type === 'componentNamesUpdated') {
            let components = this.componentNamesAndProps.get(fileName);
            if (!components) {
                components = {};
                this.componentNamesAndProps.set(fileName, components);
            }
            const newNames = data;
            const newNameSet = new Set(newNames);
            for (const name in components) {
                if (!newNameSet.has(name)) {
                    delete components[name];
                }
            }
            for (const name of newNames) {
                if (!components[name]) {
                    components[name] = null;
                }
            }
        }
        else if (type === 'componentPropsUpdated') {
            const components = this.componentNamesAndProps.get(fileName) ?? {};
            const [name, props] = data;
            if (name in components) {
                components[name] = props;
            }
        }
        else {
            console.error('Unknown notification type:', type);
            debugger;
        }
    }
    sendRequest(requestType, fileName, ...args) {
        return new Promise(resolve => {
            const seq = this.seq++;
            // console.time(`[${seq}] ${requestType} ${fileName}`);
            this.requestHandlers.set(seq, data => {
                // console.timeEnd(`[${seq}] ${requestType} ${fileName}`);
                this.requestHandlers.delete(seq);
                resolve(data);
                clearInterval(retryTimer);
            });
            const retry = () => {
                const data = [seq, requestType, fileName, ...args];
                this.socket.write(JSON.stringify(data) + '\n\n');
            };
            retry();
            const retryTimer = setInterval(retry, 1000);
        });
    }
}
exports.configuredServers = [];
exports.inferredServers = [];
exports.onServerReady = [];
for (let i = 0; i < 10; i++) {
    exports.configuredServers.push(new NamedPipeServer(1, i));
    exports.inferredServers.push(new NamedPipeServer(0, i));
}
async function getBestServer(fileName) {
    for (const server of exports.configuredServers) {
        server.update();
    }
    let servers = (await Promise.all(exports.configuredServers.map(async (server) => {
        const projectInfo = server.projectInfo;
        if (!projectInfo) {
            return;
        }
        const containsFile = await server.containsFile(fileName);
        if (!containsFile) {
            return;
        }
        return server;
    }))).filter(server => !!server);
    // Sort servers by tsconfig
    servers.sort((a, b) => sortTSConfigs(fileName, a.projectInfo.name, b.projectInfo.name));
    if (servers.length) {
        // Return the first server
        return servers[0];
    }
    for (const server of exports.inferredServers) {
        server.update();
    }
    servers = (await Promise.all(exports.inferredServers.map(server => {
        const projectInfo = server.projectInfo;
        if (!projectInfo) {
            return;
        }
        // Check if the file is in the project's directory
        if (path.relative(projectInfo.currentDirectory, fileName).startsWith('..')) {
            return;
        }
        return server;
    }))).filter(server => !!server);
    // Sort servers by directory
    servers.sort((a, b) => b.projectInfo.currentDirectory.replace(/\\/g, '/').split('/').length
        - a.projectInfo.currentDirectory.replace(/\\/g, '/').split('/').length);
    if (servers.length) {
        // Return the first server
        return servers[0];
    }
}
function sortTSConfigs(file, a, b) {
    const inA = isFileInDir(file, path.dirname(a));
    const inB = isFileInDir(file, path.dirname(b));
    if (inA !== inB) {
        const aWeight = inA ? 1 : 0;
        const bWeight = inB ? 1 : 0;
        return bWeight - aWeight;
    }
    const aLength = a.split('/').length;
    const bLength = b.split('/').length;
    return bLength - aLength;
}
function isFileInDir(fileName, dir) {
    const relative = path.relative(dir, fileName);
    return !!relative && !relative.startsWith('..') && !path.isAbsolute(relative);
}
//# sourceMappingURL=utils.js.map