import { WebSocketServer } from 'ws';
import { WsClientMessageSchema } from '../../shared/dist/index.js';
import { errorDetails, logger, requestDetails, socketDetails } from './logging.js';
export class WebSocketHub {
    deps;
    wss = new WebSocketServer({ noServer: true });
    clients = new Set();
    nextConnectionId = 1;
    constructor(deps) {
        this.deps = deps;
    }
    upgrade(req, socket, head) {
        if (req.url !== '/ws')
            return false;
        logger.info('ws.upgrade.accepted', { ...requestDetails(req), headBytes: head.length });
        this.wss.handleUpgrade(req, socket, head, (ws) => this.handleConnection(ws, req));
        return true;
    }
    broadcast(message) {
        const payload = JSON.stringify(message);
        for (const ws of this.clients) {
            if (ws.readyState === 1)
                ws.send(payload);
        }
    }
    close() {
        for (const ws of this.clients)
            ws.close();
        this.wss.close();
    }
    async broadcastGitStatus(projectId) {
        const project = this.deps.db.getProject(projectId);
        if (!project)
            return;
        try {
            const result = await this.deps.git.status(project.path);
            this.broadcast({ type: 'git.status', projectId, result });
        }
        catch (error) {
            this.broadcast({
                type: 'error',
                method: 'git.refresh',
                message: error instanceof Error ? error.message : String(error)
            });
        }
    }
    handleConnection(ws, req) {
        const connectionId = this.nextConnectionId++;
        this.clients.add(ws);
        logger.info('ws.connection.open', {
            connectionId,
            clients: this.clients.size,
            ...requestDetails(req)
        });
        ws.on('message', async (raw) => {
            let parsed;
            try {
                parsed = JSON.parse(raw.toString());
            }
            catch {
                logger.warn('ws.message.invalid_json', { connectionId });
                this.send(ws, { type: 'error', method: 'parse', message: 'Invalid JSON message' });
                return;
            }
            const result = WsClientMessageSchema.safeParse(parsed);
            if (!result.success) {
                logger.warn('ws.message.invalid_schema', {
                    connectionId,
                    issues: result.error.issues.map((issue) => issue.message)
                });
                this.send(ws, {
                    type: 'error',
                    method: 'parse',
                    message: result.error.issues.map((issue) => issue.message).join(', ')
                });
                return;
            }
            try {
                logger.info('ws.message.received', { connectionId, method: result.data.method });
                await this.handleMessage(ws, result.data);
            }
            catch (error) {
                logger.error('ws.message.failed', {
                    connectionId,
                    method: result.data.method,
                    ...errorDetails(error)
                });
                this.send(ws, {
                    type: 'error',
                    method: result.data.method,
                    message: error instanceof Error ? error.message : String(error)
                });
            }
        });
        ws.on('error', (error) => {
            logger.warn('ws.connection.error', { connectionId, ...errorDetails(error) });
        });
        ws.on('close', () => {
            this.clients.delete(ws);
            this.deps.pty.unsubscribeAll(ws);
            const transport = ws;
            logger.info('ws.connection.close', {
                connectionId,
                clients: this.clients.size,
                ...(transport._socket ? socketDetails(transport._socket) : {})
            });
        });
    }
    async handleMessage(ws, msg) {
        switch (msg.method) {
            case 'terminal.create': {
                const project = this.deps.db.getProject(msg.projectId);
                if (!project)
                    throw new Error('Project not found');
                const session = this.deps.pty.create({
                    projectId: msg.projectId,
                    projectPath: project.path,
                    name: msg.name,
                    cols: msg.cols,
                    rows: msg.rows
                });
                this.broadcast({ type: 'terminal.status', session });
                this.deps.pty.attach(session.id, ws);
                break;
            }
            case 'terminal.attach':
                if (!this.deps.pty.attach(msg.terminalId, ws))
                    throw new Error('Terminal not found');
                break;
            case 'terminal.input':
                this.deps.pty.input(msg.terminalId, msg.data);
                break;
            case 'terminal.resize':
                this.deps.pty.resize(msg.terminalId, msg.cols, msg.rows);
                break;
            case 'terminal.close': {
                const session = this.deps.pty.close(msg.terminalId);
                if (session)
                    this.broadcast({ type: 'terminal.status', session });
                break;
            }
            case 'agent.send':
                this.deps.agents.send(msg.sessionId, msg.prompt, msg.attachmentIds);
                break;
            case 'agent.cancel':
                this.deps.agents.cancel(msg.sessionId);
                break;
            case 'git.refresh':
                await this.broadcastGitStatus(msg.projectId);
                break;
        }
    }
    send(ws, message) {
        if (ws.readyState === 1)
            ws.send(JSON.stringify(message));
    }
}
//# sourceMappingURL=websocket.js.map