# frozen_string_literal: true

module LiteCable
  module Server
    # Rack middleware to hijack the socket
    class Middleware
      class HijackNotAvailable < RuntimeError; end

      def initialize(_app, connection_class:)
        @connection_class = connection_class
        @heart_beat = HeartBeat.new
      end

      def call(env)
        return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] unless
          env["HTTP_UPGRADE"] == "websocket"

        raise HijackNotAvailable unless env["rack.hijack"]

        env["rack.hijack"].call
        handshake = send_handshake(env)

        socket = ClientSocket::Base.new env, env["rack.hijack_io"], handshake.version
        init_connection socket
        init_heartbeat socket
        socket.listen
        [-1, {}, []]
      end

      private

      def send_handshake(env)
        handshake = WebSocket::Handshake::Server.new(
          protocols: LiteCable::INTERNAL[:protocols]
        )

        handshake.from_rack env
        env["rack.hijack_io"].write handshake.to_s
        handshake
      end

      def init_connection(socket)
        connection = @connection_class.new(socket)

        socket.onopen { connection.handle_open }
        socket.onclose { connection.handle_close }
        socket.onmessage { |data| connection.handle_command(data) }
      end

      def init_heartbeat(socket)
        @heart_beat.add(socket)
        socket.onclose { @heart_beat.remove(socket) }
      end
    end
  end
end
