// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
type Udp

///|
pub impl ToHandle for Udp with to_handle(self : Udp) -> Handle = "%identity"

///|
pub impl ToHandle for Udp with of_handle(self : Handle) -> Udp = "%identity"

///|
extern "c" fn uv_udp_make() -> Udp = "moonbit_uv_udp_make"

///|
#owned(uv, udp)
extern "c" fn uv_udp_init(uv : Loop, udp : Udp) -> Int = "moonbit_uv_udp_init"

///|
pub fn Udp::new(uv : Loop) -> Udp raise Errno {
  let udp = uv_udp_make()
  let result = uv_udp_init(uv, udp)
  if result < 0 {
    raise Errno::of_int(result)
  }
  udp
}

///|
#owned(handle, addr)
extern "c" fn uv_udp_connect(handle : Udp, addr : Sockaddr) -> Int = "moonbit_uv_udp_connect"

///|
pub fn[Sockaddr : ToSockaddr] Udp::connect(
  self : Udp,
  addr : Sockaddr,
) -> Unit raise Errno {
  let result = uv_udp_connect(self, addr.to_sockaddr())
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(handle, addr)
extern "c" fn uv_udp_bind(handle : Udp, addr : Sockaddr, flags : UInt) -> Int = "moonbit_uv_udp_bind"

///|
pub fn[Sockaddr : ToSockaddr] Udp::bind(
  self : Udp,
  addr : Sockaddr,
  flags : UdpFlags,
) -> Unit raise Errno {
  let result = uv_udp_bind(self, addr.to_sockaddr(), flags.0)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
type UdpSend

///|
pub impl ToReq for UdpSend with to_req(self : UdpSend) -> Req = "%identity"

///|
extern "c" fn uv_udp_send_make() -> UdpSend = "moonbit_uv_udp_send_make"

///|
#owned(send, udp, bufs_base, bufs_offset, addr, bufs_length)
extern "c" fn uv_udp_send(
  send : UdpSend,
  udp : Udp,
  bufs_base : FixedArray[Bytes],
  bufs_offset : FixedArray[Int],
  bufs_length : FixedArray[Int],
  addr : Sockaddr?,
  cb : (UdpSend, Int) -> Unit,
) -> Int = "moonbit_uv_udp_send"

///|
pub fn[Sockaddr : ToSockaddr] Udp::send(
  self : Udp,
  data : Array[BytesView],
  send_cb : () -> Unit,
  error_cb : (Errno) -> Unit,
  addr? : Sockaddr,
) -> UdpSend raise Errno {
  fn cb(_ : UdpSend, status : Int) {
    if status < 0 {
      error_cb(Errno::of_int(status))
    } else {
      send_cb()
    }
  }

  let req = uv_udp_send_make()
  let bufs_size = data.length()
  let bufs_base : FixedArray[Bytes] = FixedArray::make(bufs_size, [])
  let bufs_offset = FixedArray::make(bufs_size, 0)
  let bufs_length = FixedArray::make(bufs_size, 0)
  for i in 0.. Bytes,
  recv_cb : (Udp, Int64, Bytes?, Int, Int, Sockaddr, UInt) -> Unit,
) -> Int = "moonbit_uv_udp_recv_start"

///|
pub fn Udp::recv_start(
  self : Udp,
  alloc_cb : (Handle, Int) -> BytesView,
  read_cb : (Udp, Int, BytesView, Sockaddr, UdpFlags) -> Unit,
  error_cb : (Udp, Errno) -> Unit,
) -> Unit raise Errno {
  fn uv_alloc_cb(
    handle : Handle,
    suggested_size : UInt64,
    buf_offset : @c.Pointer[Int],
    buf_length : @c.Pointer[Int],
  ) -> Bytes {
    let bytes = alloc_cb(handle, suggested_size.to_int())
    buf_offset[0] = bytes.start_offset()
    buf_length[0] = bytes.length()
    return bytes.data()
  }

  fn uv_recv_cb(
    udp : Udp,
    count : Int64,
    buf_data : Bytes?,
    buf_offset : Int,
    buf_length : Int,
    sockaddr : Sockaddr,
    flags : UInt,
  ) -> Unit {
    if count < 0 {
      error_cb(udp, Errno::of_int(count.to_int()))
    } else {
      read_cb(
        udp,
        count.to_int(),
        buf_data.unwrap()[buf_offset:buf_length],
        sockaddr,
        UdpFlags(flags),
      )
    }
  }

  let result = uv_udp_recv_start(self, uv_alloc_cb, uv_recv_cb)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp)
extern "c" fn uv_udp_recv_stop(udp : Udp) -> Int = "moonbit_uv_udp_recv_stop"

///|
pub fn Udp::recv_stop(self : Udp) -> Unit raise Errno {
  let result = uv_udp_recv_stop(self)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
struct UdpFlags(UInt)

///|
pub fn UdpFlags::new(
  ipv6_only? : Bool = false,
  partial? : Bool = false,
  reuse_addr? : Bool = false,
  mmsg_chunk? : Bool = false,
  mmsg_free? : Bool = false,
  linux_recv_err? : Bool = false,
  reuse_port? : Bool = false,
  recvmmsg? : Bool = false,
) -> UdpFlags {
  let mut flags = 0U
  if ipv6_only {
    flags = flags | UV_UDP_IPV6ONLY
  }
  if partial {
    flags = flags | UV_UDP_PARTIAL
  }
  if reuse_addr {
    flags = flags | UV_UDP_REUSEADDR
  }
  if mmsg_chunk {
    flags = flags | UV_UDP_MMSG_CHUNK
  }
  if mmsg_free {
    flags = flags | UV_UDP_MMSG_FREE
  }
  if linux_recv_err {
    flags = flags | UV_UDP_LINUX_RECVERR
  }
  if reuse_port {
    flags = flags | UV_UDP_REUSEPORT
  }
  if recvmmsg {
    flags = flags | UV_UDP_RECVMMSG
  }
  UdpFlags(flags)
}

///|
const UV_UDP_IPV6ONLY : UInt = 1

///|
const UV_UDP_PARTIAL : UInt = 2

///|
const UV_UDP_REUSEADDR : UInt = 4

///|
const UV_UDP_MMSG_CHUNK : UInt = 8

///|
const UV_UDP_MMSG_FREE : UInt = 16

///|
const UV_UDP_LINUX_RECVERR : UInt = 32

///|
const UV_UDP_REUSEPORT : UInt = 64

///|
const UV_UDP_RECVMMSG : UInt = 256

///|
pub(all) enum Membership {
  LeaveGroup
  JoinGroup
}

///|
pub fn Membership::to_int(self : Membership) -> Int {
  match self {
    LeaveGroup => 0
    JoinGroup => 1
  }
}

///|
#owned(uv, udp)
extern "c" fn uv_udp_init_ex(uv : Loop, udp : Udp, flags : UInt) -> Int = "moonbit_uv_udp_init_ex"

///|
/// Extended UDP initialization with flags
pub fn Udp::new_ex(
  uv : Loop,
  domain : AddressFamily,
  flags : UdpFlags,
) -> Udp raise Errno {
  let udp = uv_udp_make()
  let flags = domain.to_int().reinterpret_as_uint() | (flags.0 >> 8 << 8)
  let result = uv_udp_init_ex(uv, udp, flags)
  if result < 0 {
    raise Errno::of_int(result)
  }
  udp
}

///|
#owned(udp, os_sock)
extern "c" fn uv_udp_open(udp : Udp, os_sock : OsSock) -> Int = "moonbit_uv_udp_open"

///|
/// Open UDP handle from existing socket
pub fn Udp::open(self : Udp, os_sock : OsSock) -> Unit raise Errno {
  let result = uv_udp_open(self, os_sock)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp, addr)
extern "c" fn uv_udp_getpeername(udp : Udp, addr : Sockaddr) -> Int = "moonbit_uv_udp_getpeername"

///|
/// Get the address of the peer connected to the UDP handle
pub fn Udp::getpeername(self : Udp) -> Sockaddr raise Errno {
  let addr = uv_sockaddr_make()
  let result = uv_udp_getpeername(self, addr)
  if result < 0 {
    raise Errno::of_int(result)
  }
  addr
}

///|
#owned(udp, addr)
extern "c" fn uv_udp_getsockname(udp : Udp, addr : Sockaddr) -> Int = "moonbit_uv_udp_getsockname"

///|
/// Get the current address to which the UDP handle is bound
pub fn Udp::getsockname(self : Udp) -> Sockaddr raise Errno {
  let addr = uv_sockaddr_make()
  let result = uv_udp_getsockname(self, addr)
  if result < 0 {
    raise Errno::of_int(result)
  }
  addr
}

///|
#owned(udp, multicast_addr, interface_addr)
extern "c" fn uv_udp_set_membership(
  udp : Udp,
  multicast_addr : Bytes,
  interface_addr : Bytes,
  membership : Int,
) -> Int = "moonbit_uv_udp_set_membership"

///|
/// Set UDP multicast group membership
pub fn Udp::set_membership(
  self : Udp,
  multicast_addr : Bytes,
  interface_addr : Bytes,
  membership : Membership,
) -> Unit raise Errno {
  let result = uv_udp_set_membership(
    self,
    multicast_addr,
    interface_addr,
    membership.to_int(),
  )
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp, multicast_addr, interface_addr, source_addr)
extern "c" fn uv_udp_set_source_membership(
  udp : Udp,
  multicast_addr : Bytes,
  interface_addr : Bytes,
  source_addr : Bytes,
  membership : Int,
) -> Int = "moonbit_uv_udp_set_source_membership"

///|
/// Set UDP source-specific multicast group membership
pub fn Udp::set_source_membership(
  self : Udp,
  multicast_addr : Bytes,
  interface_addr : Bytes,
  source_addr : Bytes,
  membership : Membership,
) -> Unit raise Errno {
  let result = uv_udp_set_source_membership(
    self,
    multicast_addr,
    interface_addr,
    source_addr,
    membership.to_int(),
  )
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp)
extern "c" fn uv_udp_set_multicast_loop(udp : Udp, on : Bool) -> Int = "moonbit_uv_udp_set_multicast_loop"

///|
/// Enable or disable multicast loopback
pub fn Udp::set_multicast_loop(self : Udp, on : Bool) -> Unit raise Errno {
  let result = uv_udp_set_multicast_loop(self, on)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp)
extern "c" fn uv_udp_set_multicast_ttl(udp : Udp, ttl : Int) -> Int = "moonbit_uv_udp_set_multicast_ttl"

///|
/// Set the multicast Time To Live (TTL)
pub fn Udp::set_multicast_ttl(self : Udp, ttl : Int) -> Unit raise Errno {
  let result = uv_udp_set_multicast_ttl(self, ttl)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp, interface_addr)
extern "c" fn uv_udp_set_multicast_interface(
  udp : Udp,
  interface_addr : Bytes,
) -> Int = "moonbit_uv_udp_set_multicast_interface"

///|
/// Set the multicast interface to use
pub fn Udp::set_multicast_interface(
  self : Udp,
  interface_addr : Bytes,
) -> Unit raise Errno {
  let result = uv_udp_set_multicast_interface(self, interface_addr)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp)
extern "c" fn uv_udp_set_broadcast(udp : Udp, on : Bool) -> Int = "moonbit_uv_udp_set_broadcast"

///|
/// Enable or disable broadcast mode
pub fn Udp::set_broadcast(self : Udp, on : Bool) -> Unit raise Errno {
  let result = uv_udp_set_broadcast(self, on)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp)
extern "c" fn uv_udp_set_ttl(udp : Udp, ttl : Int) -> Int = "moonbit_uv_udp_set_ttl"

///|
/// Set the Time To Live (TTL) for UDP packets
pub fn Udp::set_ttl(self : Udp, ttl : Int) -> Unit raise Errno {
  let result = uv_udp_set_ttl(self, ttl)
  if result < 0 {
    raise Errno::of_int(result)
  }
}

///|
#owned(udp, bufs_base, bufs_offset, bufs_length, addr)
extern "c" fn uv_udp_try_send(
  udp : Udp,
  bufs_base : FixedArray[Bytes],
  bufs_offset : FixedArray[Int],
  bufs_length : FixedArray[Int],
  addr : Sockaddr?,
) -> Int = "moonbit_uv_udp_try_send"

///|
/// Try to send data synchronously (non-blocking)
/// Returns the number of bytes written or a negative error code
pub fn[Sockaddr : ToSockaddr] Udp::try_send(
  self : Udp,
  data : Array[BytesView],
  addr? : Sockaddr,
) -> Int raise Errno {
  let bufs_size = data.length()
  let bufs_base : FixedArray[Bytes] = FixedArray::make(bufs_size, [])
  let bufs_offset = FixedArray::make(bufs_size, 0)
  let bufs_length = FixedArray::make(bufs_size, 0)
  for i in 0.. Int = "moonbit_uv_udp_try_send2"

///|
pub fn[T : ToSockaddr] Udp::try_send2(
  self : Udp,
  data : Array[(Array[BytesView], T?)],
  flags : UdpFlags,
) -> Int raise Errno {
  let bufs_base : FixedArray[FixedArray[Bytes]] = FixedArray::makei(
    data.length(),
    i => FixedArray::make(data[i].0.length(), ""),
  )
  let bufs_offset = FixedArray::makei(data.length(), i => FixedArray::make(
    data[i].0.length(),
    0,
  ))
  let bufs_length = FixedArray::makei(data.length(), i => FixedArray::make(
    data[i].0.length(),
    0,
  ))
  let addrs : FixedArray[Sockaddr?] = FixedArray::make(data.length(), None)
  for i in 0.. Int = "moonbit_uv_udp_using_recvmmsg"

///|
/// Check if the UDP handle is using recvmmsg for receiving data
pub fn Udp::using_recvmmsg(self : Udp) -> Bool {
  uv_udp_using_recvmmsg(self) != 0
}

///|
#owned(udp)
extern "c" fn uv_udp_get_send_queue_size(udp : Udp) -> UInt64 = "moonbit_uv_udp_get_send_queue_size"

///|
/// Get the size of the send queue
pub fn Udp::get_send_queue_size(self : Udp) -> UInt64 {
  uv_udp_get_send_queue_size(self)
}

///|
#owned(udp)
extern "c" fn uv_udp_get_send_queue_count(udp : Udp) -> UInt64 = "moonbit_uv_udp_get_send_queue_count"

///|
/// Get the count of items in the send queue
pub fn Udp::get_send_queue_count(self : Udp) -> UInt64 {
  uv_udp_get_send_queue_count(self)
}