/**
 * Terminado WebSocket Addon for xterm.js 5.x
 *
 * This addon handles the Terminado protocol for xterm.js, allowing
 * bidirectional communication over WebSocket with a backend PTY process.
 *
 * The Terminado protocol uses JSON arrays:
 * - ["stdin", data] - send input to the terminal
 * - ["stdout", data] - receive output from the terminal
 * - ["set_size", rows, cols] - notify backend of terminal size changes
 */

class TerminadoAddon {
  constructor() {
    this._disposables = [];
    this._socket = null;
    this._bidirectional = true;
    this._buffered = true;
    this._attachSocketBuffer = '';
    this._flushTimeout = null;
  }

  activate(terminal) {
    this._terminal = terminal;
  }

  dispose() {
    this._disposables.forEach(d => d.dispose());
    this._disposables.length = 0;

    if (this._flushTimeout) {
      clearTimeout(this._flushTimeout);
      this._flushTimeout = null;
    }

    if (this._socket) {
      this.detach();
    }
  }

  attach(socket, bidirectional = true, buffered = true) {
    if (this._socket) {
      this.detach();
    }

    this._socket = socket;
    this._bidirectional = bidirectional !== false;
    this._buffered = buffered !== false;

    // Handle incoming messages from the websocket
    this._messageHandler = (ev) => {
      try {
        const data = JSON.parse(ev.data);
        if (Array.isArray(data) && data[0] === 'stdout') {
          const output = data[1];
          if (this._buffered) {
            this._pushToBuffer(output);
          } else {
            this._terminal.write(output);
          }
        }
      } catch (err) {
        console.error('Error handling terminado message:', err);
      }
    };

    // Handle terminal input (user typing)
    if (this._bidirectional) {
      const dataDisposable = this._terminal.onData((data) => {
        this._sendData(data);
      });
      this._disposables.push(dataDisposable);
    }

    // Handle terminal resize events
    const resizeDisposable = this._terminal.onResize((size) => {
      this._setSize(size);
    });
    this._disposables.push(resizeDisposable);

    // Handle socket close/error
    this._closeHandler = () => this.detach();
    this._errorHandler = () => this.detach();

    socket.addEventListener('message', this._messageHandler);
    socket.addEventListener('close', this._closeHandler);
    socket.addEventListener('error', this._errorHandler);
  }

  detach() {
    if (!this._socket) {
      return;
    }

    if (this._messageHandler) {
      this._socket.removeEventListener('message', this._messageHandler);
      this._messageHandler = null;
    }

    if (this._closeHandler) {
      this._socket.removeEventListener('close', this._closeHandler);
      this._closeHandler = null;
    }

    if (this._errorHandler) {
      this._socket.removeEventListener('error', this._errorHandler);
      this._errorHandler = null;
    }

    this._disposables.forEach(d => d.dispose());
    this._disposables.length = 0;

    this._socket = null;
  }

  _sendData(data) {
    if (this._socket && this._socket.readyState === WebSocket.OPEN) {
      try {
        this._socket.send(JSON.stringify(['stdin', data]));
      } catch (err) {
        console.error('Error sending data to terminal:', err);
      }
    }
  }

  _setSize(size) {
    if (this._socket && this._socket.readyState === WebSocket.OPEN) {
      try {
        this._socket.send(JSON.stringify(['set_size', size.rows, size.cols]));
      } catch (err) {
        console.error('Error sending terminal size:', err);
      }
    }
  }

  _pushToBuffer(data) {
    if (this._attachSocketBuffer) {
      this._attachSocketBuffer += data;
    } else {
      this._attachSocketBuffer = data;
      if (this._flushTimeout) {
        clearTimeout(this._flushTimeout);
      }
      this._flushTimeout = setTimeout(() => this._flushBuffer(), 10);
    }
  }

  _flushBuffer() {
    if (this._attachSocketBuffer && this._terminal) {
      this._terminal.write(this._attachSocketBuffer);
      this._attachSocketBuffer = '';
    }
    this._flushTimeout = null;
  }

  // Public method to manually send size
  sendSize(rows, cols) {
    if (this._socket && this._socket.readyState === WebSocket.OPEN) {
      try {
        this._socket.send(JSON.stringify(['set_size', rows, cols]));
      } catch (err) {
        console.error('Error sending manual terminal size:', err);
      }
    }
  }

  // Public method to manually send command
  sendCommand(command) {
    if (this._socket && this._socket.readyState === WebSocket.OPEN) {
      try {
        this._socket.send(JSON.stringify(['stdin', command]));
      } catch (err) {
        console.error('Error sending command:', err);
      }
    }
  }
}

// Export for use in browsers
if (typeof module !== 'undefined' && module.exports) {
  module.exports = TerminadoAddon;
}
