// 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 GetAddrInfo

///|
extern "c" fn uv_getaddrinfo_make() -> GetAddrInfo = "moonbit_uv_getaddrinfo_make"

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

///|
type AddrInfoHints

///|
extern "c" fn uv_addrinfo_hints(
  flags : Int,
  family : Int,
  socktype : Int,
  protocol : Int,
) -> AddrInfoHints = "moonbit_uv_addrinfo_hints"

///|
struct AddrInfoFlags(Int)

///|
extern "c" fn uv_AI_PASSIVE() -> Int = "moonbit_uv_AI_PASSIVE"

///|
let _AI_PASSIVE : Int = uv_AI_PASSIVE()

///|
extern "c" fn uv_AI_CANONNAME() -> Int = "moonbit_uv_AI_CANONNAME"

///|
let _AI_CANONNAME : Int = uv_AI_CANONNAME()

///|
extern "c" fn uv_AI_NUMERICHOST() -> Int = "moonbit_uv_AI_NUMERICHOST"

///|
let _AI_NUMERICHOST : Int = uv_AI_NUMERICHOST()

///|
extern "c" fn uv_AI_NUMERICSERV() -> Int = "moonbit_uv_AI_NUMERICSERV"

///|
let _AI_NUMERICSERV : Int = uv_AI_NUMERICSERV()

///|
extern "c" fn uv_AI_ALL() -> Int = "moonbit_uv_AI_ALL"

///|
let _AI_ALL : Int = uv_AI_ALL()

///|
extern "c" fn uv_AI_ADDRCONFIG() -> Int = "moonbit_uv_AI_ADDRCONFIG"

///|
let _AI_ADDRCONFIG : Int = uv_AI_ADDRCONFIG()

///|
extern "c" fn uv_AI_V4MAPPED() -> Int = "moonbit_uv_AI_V4MAPPED"

///|
let _AI_V4MAPPED : Int = uv_AI_V4MAPPED()

///|
pub fn AddrInfoFlags::new(
  passive? : Bool = false,
  canonname? : Bool = false,
  numeric_host? : Bool = false,
  numeric_serv? : Bool = false,
  all? : Bool = false,
  addrconfig? : Bool = false,
  v4mapped? : Bool = false,
) -> AddrInfoFlags {
  let mut flags = 0
  if passive {
    flags = flags | _AI_PASSIVE
  }
  if canonname {
    flags = flags | _AI_CANONNAME
  }
  if numeric_host {
    flags = flags | _AI_NUMERICHOST
  }
  if numeric_serv {
    flags = flags | _AI_NUMERICSERV
  }
  if all {
    flags = flags | _AI_ALL
  }
  if addrconfig {
    flags = flags | _AI_ADDRCONFIG
  }
  if v4mapped {
    flags = flags | _AI_V4MAPPED
  }
  return flags
}

///|
pub fn AddrInfoHints::new(
  flags? : AddrInfoFlags,
  family? : AddressFamily,
  socktype? : SockType,
  protocol? : Protocol,
) -> AddrInfoHints {
  let flags = match flags {
    Some(flags) => flags.0
    None => 0
  }
  let family = match family {
    Some(family) => family.to_int()
    None => _AF_UNSPEC
  }
  let socktype = match socktype {
    Some(socktype) => socktype.to_int()
    None => 0
  }
  let protocol = match protocol {
    Some(protocol) => protocol.to_int()
    None => 0
  }
  uv_addrinfo_hints(flags, family, socktype, protocol)
}

///|
priv type AddrInfoResults

///|
#borrow(addrinfo_results, cb)
extern "c" fn uv_addrinfo_results_iterate(
  addrinfo_results : AddrInfoResults,
  cb : (Int, Int, Int, Int, Sockaddr, @c.Pointer[Byte]) -> Bool,
) -> Bool = "moonbit_uv_addrinfo_results_iterate"

///|
struct AddrInfo {
  family : Int
  socktype : Int
  protocol : Int
  addr : Sockaddr
  canonname : Bytes?
}

///|
extern "c" fn uv_AF_INET() -> Int = "moonbit_uv_AF_INET"

///|
let _AF_INET : Int = uv_AF_INET()

///|
extern "c" fn uv_AF_INET6() -> Int = "moonbit_uv_AF_INET6"

///|
let _AF_INET6 : Int = uv_AF_INET6()

///|
extern "c" fn uv_AF_UNSPEC() -> Int = "moonbit_uv_AF_UNSPEC"

///|
let _AF_UNSPEC : Int = uv_AF_UNSPEC()

///|
/// Represents the address family for network communication, specifying the
/// format of network addresses.
///
/// Constructors:
///
/// * `Inet` : IPv4 address family
/// * `Inet6` : IPv6 address family
///
/// Example:
///
/// ```moonbit nocheck
/// let ipv4 = AddressFamily::inet()
/// let ipv6 = AddressFamily::inet6()
/// @assert.t(ipv4 is Inet)
/// @assert.t(ipv6 is Inet6)
/// ```
pub(all) enum AddressFamily {
  Inet
  Inet6
}

///|
pub fn AddressFamily::inet() -> AddressFamily {
  return Inet
}

///|
pub fn AddressFamily::inet6() -> AddressFamily {
  return Inet6
}

///|
fn AddressFamily::of_int(self : Int) -> AddressFamily raise Errno {
  if self == _AF_INET {
    return Inet
  }
  if self == _AF_INET6 {
    return Inet6
  }
  raise EINVAL
}

///|
fn AddressFamily::to_int(self : AddressFamily) -> Int {
  match self {
    Inet => _AF_INET
    Inet6 => _AF_INET6
  }
}

///|
pub fn AddrInfo::family(self : AddrInfo) -> AddressFamily raise Errno {
  AddressFamily::of_int(self.family)
}

///|
pub fn AddrInfo::addr(self : AddrInfo) -> Sockaddr {
  self.addr
}

///|
pub fn AddrInfo::socktype(self : AddrInfo) -> SockType raise Errno {
  return SockType::of_int(self.socktype)
}

///|
pub fn AddrInfo::protocol(self : AddrInfo) -> Protocol raise Errno {
  Protocol::of_int(self.protocol)
}

///|
pub fn AddrInfo::canonname(self : AddrInfo) -> Bytes? {
  self.canonname
}

///|
fn AddrInfoResults::iterator(self : AddrInfoResults) -> Iter[AddrInfo] {
  let items : Array[AddrInfo] = []
  ignore(
    uv_addrinfo_results_iterate(self, fn(
      _,
      family,
      socktype,
      protocol,
      addr,
      canonname,
    ) {
      let canonname = if canonname.is_null() {
        None
      } else {
        let buffer = @buffer.new()
        for i = 0; canonname[i] != 0; i = i + 1 {
          buffer.write_byte(canonname[i])
        }
        Some(buffer.contents())
      }
      items.push({ family, socktype, protocol, addr, canonname })
      false
    }),
  )
  let mut i = 0
  Iter::new(fn() {
    if i < items.length() {
      let item = items[i]
      i = i + 1
      Some(item)
    } else {
      None
    }
  })
}

///|
fn AddrInfoResults::iter(self : AddrInfoResults) -> Iter[AddrInfo] {
  self.iterator().iter()
}

///|
#owned(uv, req, node, service, hints)
extern "c" fn uv_getaddrinfo(
  uv : Loop,
  req : GetAddrInfo,
  getaddrinfo_cb : (GetAddrInfo, Int, AddrInfoResults) -> Unit,
  node : Bytes,
  service : Bytes,
  hints : AddrInfoHints,
) -> Int = "moonbit_uv_getaddrinfo"

///|
#owned(uv, req, node, service)
extern "c" fn uv_getaddrinfo_no_hints(
  uv : Loop,
  req : GetAddrInfo,
  getaddrinfo_cb : (GetAddrInfo, Int, AddrInfoResults) -> Unit,
  node : Bytes,
  service : Bytes,
  hints : @c.Pointer[Unit],
) -> Int = "moonbit_uv_getaddrinfo"

///|
#as_free_fn
pub fn Loop::getaddrinfo(
  self : Loop,
  getaddrinfo_cb : (Iter[AddrInfo]) -> Unit,
  error_cb : (Errno) -> Unit,
  node : Bytes,
  service : Bytes,
  hints? : AddrInfoHints,
) -> GetAddrInfo raise Errno {
  fn uv_cb(_ : GetAddrInfo, status : Int, addrinfo : AddrInfoResults) {
    if status < 0 {
      error_cb(Errno::of_int(status))
    } else {
      getaddrinfo_cb(addrinfo.iter())
    }
  }

  let req = uv_getaddrinfo_make()
  let status = match hints {
    Some(hints) => uv_getaddrinfo(self, req, uv_cb, node, service, hints)
    None =>
      uv_getaddrinfo_no_hints(
        self,
        req,
        uv_cb,
        node,
        service,
        @c.Pointer::null(),
      )
  }
  if status < 0 {
    raise Errno::of_int(status)
  }
  return req
}

///|
type GetNameInfo

///|
extern "c" fn uv_getnameinfo_make() -> GetNameInfo = "moonbit_uv_getnameinfo_make"

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

///|
#owned(uv, req, getnameinfo_cb, addr)
extern "c" fn uv_getnameinfo(
  uv : Loop,
  req : GetNameInfo,
  getnameinfo_cb : (GetNameInfo, Int, Bytes?, Bytes?) -> Unit,
  addr : Sockaddr,
  flags : Int,
) -> Int = "moonbit_uv_getnameinfo"

///|
#as_free_fn
pub fn Loop::getnameinfo(
  self : Loop,
  getnameinfo_cb : (Bytes?, Bytes?) -> Unit,
  error_cb : (Errno) -> Unit,
  addr : Sockaddr,
  flags? : Int = 0,
) -> GetNameInfo raise Errno {
  fn uv_cb(_ : GetNameInfo, status : Int, hostname : Bytes?, service : Bytes?) {
    if status < 0 {
      error_cb(Errno::of_int(status))
    } else {
      getnameinfo_cb(hostname, service)
    }
  }

  let req = uv_getnameinfo_make()
  let status = uv_getnameinfo(self, req, uv_cb, addr, flags)
  if status < 0 {
    raise Errno::of_int(status)
  }
  return req
}