const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const irc = require('irc');
const path = require('path');

const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });

app.use(express.static(path.join(__dirname)));

wss.on('connection', (ws) => {
  console.log('Client connected');
  let client = null;

  // Storage for accumulated nicknames per channel
  const pendingNames = new Map();

  ws.on('message', (message) => {
    try {
      const data = JSON.parse(message);
      
      if (data.type === 'connect') {
        const [host, portStr] = (data.server || '').split(':');
        const port = parseInt(portStr) || 6667;
        const nick = data.nick || 'ZeroIRC_guest';
        const channels = data.channels || [];
        const ssl = data.ssl || false;

        if (!host) {
          ws.send(JSON.stringify({ type: 'error', message: 'Server not specified' }));
          return;
        }

        if (client) client.disconnect('Reconnect');

        client = new irc.Client(host, nick, {
          port,
          secure: ssl,
          channels,
          userName: nick,
          realName: data.realName || nick,
          debug: false
        });

        const send = (type, payload = {}) => {
          if (ws.readyState === WebSocket.OPEN) {
            ws.send(JSON.stringify({ type, ...payload }));
          }
        };

        client.on('raw', (raw) => {
          const cmd = raw.rawCommand || raw.command;

          // Intercept NAMES (353) – accumulate nicks with prefixes
          if (cmd === '353') {
            const channel = raw.args[2];
            const namesStr = raw.args[3] || '';
            const names = namesStr.trim().split(/\s+/).filter(Boolean);
            if (channel && names.length) {
              if (!pendingNames.has(channel)) pendingNames.set(channel, new Set());
              const set = pendingNames.get(channel);
              names.forEach(n => set.add(n));
            }
            return;
          }

          // End of NAMES list (366) – send accumulated nicks
          if (cmd === '366') {
            const channel = raw.args[1];
            if (channel && pendingNames.has(channel)) {
              const nicks = Array.from(pendingNames.get(channel));
              send('names', { channel, nicks });
              pendingNames.delete(channel);
            }
            return;
          }

          // Other raw commands (except PING/PONG)
          if (cmd !== 'PING' && cmd !== 'PONG') {
            send('raw', { raw });
          }
        });

        client.on('error', (err) => send('error', { message: err.message }));
        client.on('registered', () => send('registered'));
        client.on('message', (from, to, text) => send('message', { from, to, text }));
        client.on('pm', (from, text) => send('pm', { from, text }));
        client.on('join', (channel, who) => send('join', { channel, who }));
        client.on('part', (channel, who, reason) => send('part', { channel, who, reason }));
        client.on('quit', (who, reason) => send('quit', { who, reason }));
        client.on('kick', (channel, who, by, reason) => send('kick', { channel, who, by, reason }));
        client.on('nick', (oldNick, newNick) => send('nick', { oldNick, newNick }));
        client.on('topic', (channel, topic) => send('topic', { channel, topic }));
        client.on('whois', (info) => send('whois', info));

      } else if (data.type === 'command' && client && client.conn) {
        const cmd = data.command;
        const args = data.args || [];
        
        switch (cmd) {
          case 'join':
            if (args[0]) client.join(args[0]);
            break;
          case 'part':
            client.part(args[0] || '', args[1] || 'Leaving');
            break;
          case 'say':
            if (args[0] && args[1]) client.say(args[0], args[1]);
            break;
          case 'whois':
            if (args[0]) client.whois(args[0]);
            break;
          case 'list':
            client.list();
            break;
          case 'names':
            if (args[0]) client.names(args[0]);
            break;
          case 'nick':
            if (args[0]) client.send('NICK', args[0]);
            break;
          case 'away':
            client.send('AWAY', args[0] ? ':' + args[0] : '');
            break;
          case 'back':
            client.send('AWAY');
            break;
          case 'invite':
            if (args[0] && args[1]) client.send('INVITE', args[0], args[1]);
            break;
          case 'kick':
            if (args[0] && args[1]) {
              client.send('KICK', args[0], args[1], args[2] || '');
            }
            break;
          case 'topic':
            if (args[0] && args[1]) {
              client.send('TOPIC', args[0], ':' + args[1]);
            } else if (args[0]) {
              client.send('TOPIC', args[0]);
            }
            break;
          case 'mode':
            if (args.length > 0) client.send('MODE', ...args);
            break;
          case 'notice':
            if (args[0] && args[1]) client.send('NOTICE', args[0], ':' + args[1]);
            break;
          case 'ctcp':
            if (args[0] && args[1]) {
              client.send('PRIVMSG', args[0], `:\x01${args[1]}\x01`);
            }
            break;
          case 'who':
            if (args[0]) client.send('WHO', args[0]);
            break;
          case 'quit':
            client.disconnect(args[0] || 'Leaving');
            break;
          case 'version':
            args[0] ? client.send('VERSION', args[0]) : client.send('VERSION');
            break;
          case 'ping':
            args[0] ? client.send('PING', ':' + args[0]) : client.send('PING', ':ZeroIRC');
            break;
          case 'raw':
            if (args.length > 0) client.send(args[0], ...args.slice(1));
            break;
          default:
            client.send(cmd, ...args);
        }
      } else if (data.type === 'raw' && client && client.conn) {
        client.send(data.line);
      }
    } catch (e) {
      console.error('Message handling error:', e.message);
    }
  });

  ws.on('close', () => {
    console.log('Client disconnected');
    if (client) {
      client.disconnect('WebSocket closed');
    }
  });
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`ZeroIRC WebSocket Gateway running on port ${PORT}`);
});