///|
/// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
#cfg(target="js")
type WebSocket

///|
#cfg(target="js")
pub impl IsEventTarget for WebSocket

///|
#cfg(target="js")
pub impl @js.Cast for WebSocket with into(value) {
  value |> ffi_to_web_socket |> y => { y.to_option() }
}

///|
#cfg(target="js")
pub impl @js.Cast for WebSocket with from(value) {
  value |> js_identity
}

///|
#cfg(target="js")
pub extern "js" fn WebSocket::new(url : String) -> WebSocket = "(url) => new WebSocket(url)"

///|
#cfg(target="js")
pub extern "js" fn WebSocket::new_with_protocols(
  url : String,
  protocols : Array[String],
) -> WebSocket = "(url, protocols) => new WebSocket(url, protocols)"

///|
#cfg(target="js")
pub fn WebSocket::open(
  url : String,
  protocols? : Array[String] = [],
) -> Result[WebSocket, String] {
  let mut result : Result[WebSocket, String] = Err(
    "websocket creation failed without an error message",
  )
  ffi_open_socket(
    url,
    protocols,
    has_protocols=!protocols.is_empty(),
    opened=socket => result = Ok(socket),
    failed=message => result = Err(message),
  )
  result
}

///|
#cfg(target="js")
pub extern "js" fn WebSocket::close(self : Self) = "(socket) => socket.close()"

///|
#cfg(target="js")
pub extern "js" fn WebSocket::close_with_code_reason(
  self : Self,
  code : Int,
  reason : String,
) = "(socket, code, reason) => socket.close(code, reason)"

///|
#cfg(target="js")
pub fn WebSocket::close_safe(
  self : Self,
  code? : Int,
  reason? : String,
) -> Result[Unit, String] {
  let mut result : Result[Unit, String] = Ok(())
  ffi_close_socket(
    self,
    code,
    reason,
    has_code=code is Some(_),
    has_reason=reason is Some(_),
    failed=message => result = Err(message),
  )
  result
}

///|
#cfg(target="js")
pub fn WebSocket::close_quietly(self : Self) -> Unit {
  ffi_close_socket_quietly(self)
}

///|
#cfg(target="js")
pub extern "js" fn WebSocket::send(self : Self, data : String) = "(socket, data) => socket.send(data)"

///|
#cfg(target="js")
pub fn WebSocket::send_safe(self : Self, data : String) -> Result[Unit, String] {
  let mut result : Result[Unit, String] = Ok(())
  ffi_send_socket(self, data, failed=message => result = Err(message))
  result
}

///|
#cfg(target="js")
pub extern "js" fn WebSocket::get_url(self : Self) -> String = "(socket) => socket.url"

///|
#cfg(target="js")
pub extern "js" fn WebSocket::get_protocol(self : Self) -> String = "(socket) => socket.protocol"

///|
#cfg(target="js")
pub extern "js" fn WebSocket::get_buffered_amount(self : Self) -> Int = "(socket) => socket.bufferedAmount"

///|
#cfg(target="js")
pub extern "js" fn WebSocket::get_ready_state(self : Self) -> Int = "(socket) => socket.readyState"

///|
#cfg(target="js")
extern "js" fn ffi_to_web_socket(x : @js.Value) -> @js.Nullable[WebSocket] =
  #| (x) => x instanceof WebSocket ? x : null

///|
#cfg(target="js")
extern "js" fn ffi_open_socket(
  url : String,
  protocols : Array[String],
  has_protocols~ : Bool,
  opened~ : (WebSocket) -> Unit,
  failed~ : (String) -> Unit,
) =
  #| (url, protocols, hasProtocols, opened, failed) => {
  #|   try {
  #|     opened(hasProtocols ? new WebSocket(url, protocols) : new WebSocket(url))
  #|   } catch (error) {
  #|     failed(error.toString())
  #|   }
  #| }

///|
#cfg(target="js")
extern "js" fn ffi_send_socket(
  socket : WebSocket,
  data : String,
  failed~ : (String) -> Unit,
) =
  #| (socket, data, failed) => {
  #|   try {
  #|     socket.send(data)
  #|   } catch (error) {
  #|     failed(error.toString())
  #|   }
  #| }

///|
#cfg(target="js")
extern "js" fn ffi_close_socket(
  socket : WebSocket,
  code : Int?,
  reason : String?,
  has_code~ : Bool,
  has_reason~ : Bool,
  failed~ : (String) -> Unit,
) =
  #| (socket, code, reason, hasCode, hasReason, failed) => {
  #|   try {
  #|     if (hasCode) {
  #|       socket.close(code, hasReason ? reason : "")
  #|     } else if (hasReason) {
  #|       socket.close(1000, reason)
  #|     } else {
  #|       socket.close()
  #|     }
  #|   } catch (error) {
  #|     failed(error.toString())
  #|   }
  #| }

///|
#cfg(target="js")
extern "js" fn ffi_close_socket_quietly(socket : WebSocket) =
  #| socket => {
  #|   try {
  #|     socket.close()
  #|   } catch (_) {
  #|   }
  #| }