/**
 * Socket.IO server setup.
 *
 * The Redis adapter is what makes horizontal scaling work: an emit on instance
 * A reaches a socket connected to instance B, so `toConversation()` is correct
 * no matter how many Node processes are running.
 */
import type { Server as HttpServer } from 'node:http';
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { env } from '../config/env.js';
import { redisPub, redisSub } from '../redis/client.js';
import { NS } from './events.js';
import { registerIo } from './broadcast.js';
import { registerCustomerNamespace } from './handlers/customer.js';
import { registerAgentNamespace } from './handlers/agent.js';
import { logger } from '../utils/logger.js';

export function createSocketServer(httpServer: HttpServer): Server {
  const io = new Server(httpServer, {
    cors: {
      origin(origin, callback) {
        if (!origin) return callback(null, true);
        if (env.corsOrigins.includes('*') || env.corsOrigins.includes(origin)) return callback(null, true);
        callback(new Error(`Origin ${origin} is not allowed`));
      },
      credentials: true,
    },
    // Long enough that a phone locking its screen or a tunnel hiccup resumes
    // the same session instead of starting a new one.
    connectionStateRecovery: {
      maxDisconnectionDuration: 2 * 60 * 1000,
      skipMiddlewares: false,
    },
    pingInterval: 25_000,
    pingTimeout: 20_000,
    maxHttpBufferSize: 1e6, // 1 MB - file bytes go over HTTP, never the socket
  });

  io.adapter(createAdapter(redisPub, redisSub));
  registerIo(io);

  registerCustomerNamespace(io.of(NS.customer));
  registerAgentNamespace(io.of(NS.agent));

  io.engine.on('connection_error', (err) => {
    logger.warn('socket connection error', { code: err.code, message: err.message });
  });

  logger.info('socket.io ready', { namespaces: [NS.customer, NS.agent] });
  return io;
}
