"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;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.IpcServer = void 0;
const net = __importStar(require("net"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const crypto = __importStar(require("crypto"));
const MAX_BUFFER_SIZE = 1024 * 1024; // 1 MB
const MAX_WAIT_TIMEOUT_MS = 60000;
class IpcServer {
    bridge;
    server = null;
    client = null;
    portFile;
    authToken = '';
    authenticated = false;
    onClientConnect;
    onClientDisconnect;
    constructor(bridge, callbacks) {
        this.bridge = bridge;
        this.portFile = path.join(os.homedir(), '.splitgame', `ipc-port-${process.pid}`);
        this.onClientConnect = callbacks?.onConnect;
        this.onClientDisconnect = callbacks?.onDisconnect;
    }
    start() {
        return new Promise((resolve, reject) => {
            this.cleanStalePortFiles();
            this.authToken = crypto.randomBytes(16).toString('hex');
            this.server = net.createServer((socket) => this.handleConnection(socket));
            this.server.on('error', reject);
            this.server.listen(0, '127.0.0.1', () => {
                const addr = this.server.address();
                const dir = path.dirname(this.portFile);
                if (!fs.existsSync(dir))
                    fs.mkdirSync(dir, { recursive: true });
                fs.writeFileSync(this.portFile, `${addr.port}\n${this.authToken}`, {
                    encoding: 'utf-8',
                    mode: 0o600,
                });
                resolve();
            });
        });
    }
    cleanStalePortFiles() {
        const dir = path.join(os.homedir(), '.splitgame');
        if (!fs.existsSync(dir))
            return;
        let files;
        try {
            files = fs.readdirSync(dir).filter(f => f.startsWith('ipc-port-'));
        }
        catch {
            return;
        }
        const myFile = path.basename(this.portFile);
        for (const file of files) {
            if (file === myFile)
                continue;
            const match = file.match(/^ipc-port-(\d+)$/);
            if (!match) {
                try {
                    fs.unlinkSync(path.join(dir, file));
                }
                catch { }
                continue;
            }
            const pid = parseInt(match[1], 10);
            let alive = false;
            try {
                process.kill(pid, 0);
                alive = true;
            }
            catch { }
            if (!alive) {
                try {
                    fs.unlinkSync(path.join(dir, file));
                }
                catch { }
            }
        }
    }
    stop() {
        if (this.client) {
            this.client.destroy();
            this.client = null;
        }
        if (this.server) {
            this.server.close();
            this.server = null;
        }
        try {
            fs.unlinkSync(this.portFile);
        }
        catch { }
    }
    get hasClient() {
        return this.client !== null && !this.client.destroyed && this.authenticated;
    }
    handleConnection(socket) {
        if (this.client && !this.client.destroyed) {
            this.client.destroy();
            this.onClientDisconnect?.();
        }
        this.client = socket;
        this.authenticated = false;
        let buffer = '';
        socket.on('data', (data) => {
            buffer += data.toString();
            if (buffer.length > MAX_BUFFER_SIZE) {
                socket.destroy();
                return;
            }
            let newlineIdx;
            while ((newlineIdx = buffer.indexOf('\n')) !== -1) {
                const line = buffer.slice(0, newlineIdx).trim();
                buffer = buffer.slice(newlineIdx + 1);
                if (!line)
                    continue;
                if (!this.authenticated) {
                    try {
                        const msg = JSON.parse(line);
                        if (msg.auth === this.authToken) {
                            this.authenticated = true;
                            this.onClientConnect?.();
                            continue;
                        }
                    }
                    catch { }
                    socket.destroy();
                    return;
                }
                this.handleMessage(socket, line);
            }
        });
        socket.on('close', () => {
            if (this.client === socket) {
                this.client = null;
                this.onClientDisconnect?.();
            }
        });
        socket.on('error', () => {
            if (this.client === socket) {
                this.client = null;
                this.onClientDisconnect?.();
            }
        });
    }
    handleMessage(socket, raw) {
        let parsed;
        try {
            parsed = JSON.parse(raw);
        }
        catch {
            return;
        }
        const obj = parsed;
        if ((typeof obj.id !== 'string' && typeof obj.id !== 'number') ||
            typeof obj.method !== 'string') {
            const errId = (typeof obj.id === 'string' || typeof obj.id === 'number') ? obj.id : 0;
            const errResp = {
                id: errId,
                error: { code: -32600, message: 'Invalid request: missing or invalid id/method' },
            };
            socket.write(JSON.stringify(errResp) + '\n');
            return;
        }
        const req = obj;
        if (req.method === 'wait_for_turn') {
            const raw_timeout = req.params?.timeoutMs ?? 30000;
            const timeout = Math.min(Number(raw_timeout) || 30000, MAX_WAIT_TIMEOUT_MS);
            this.bridge.waitForTurn(timeout).then((state) => {
                if (socket.destroyed)
                    return;
                const response = {
                    id: req.id,
                    result: state || { timeout: true },
                };
                socket.write(JSON.stringify(response) + '\n');
            });
            return;
        }
        let response;
        switch (req.method) {
            case 'get_game_state': {
                const state = this.bridge.getGameState();
                response = { id: req.id, result: state || { error: 'No active game' } };
                break;
            }
            case 'make_move': {
                const move = req.params?.move;
                if (typeof move !== 'string') {
                    response = { id: req.id, error: { code: -1, message: 'Missing move parameter' } };
                }
                else {
                    const result = this.bridge.makeMove(move);
                    response = { id: req.id, result };
                }
                break;
            }
            case 'select_game': {
                const gameId = req.params?.gameId;
                if (typeof gameId !== 'string') {
                    response = { id: req.id, error: { code: -1, message: 'Missing gameId parameter' } };
                }
                else {
                    const result = this.bridge.selectGame(gameId);
                    response = { id: req.id, result };
                }
                break;
            }
            case 'get_game_info': {
                response = {
                    id: req.id,
                    result: {
                        currentGame: this.bridge.getCurrentGame(),
                        availableGames: this.bridge.getAvailableGames(),
                    },
                };
                break;
            }
            default:
                response = { id: req.id, error: { code: -1, message: `Unknown method: ${req.method}` } };
        }
        socket.write(JSON.stringify(response) + '\n');
    }
}
exports.IpcServer = IpcServer;
//# sourceMappingURL=ipc-server.js.map