///|
/// Explicitly distinguish host addresses, interfaces and networks for mixed
/// operations. An interface contributes its host address, not its subnet.
pub(all) enum IpInput {
  AddressInput(IpAddress)
  InterfaceInput(IpInterface)
  NetworkInput(IpNetwork)
} derive(Eq)

///|
pub extend IpInput with Eq::{equal, not_equal}

///|
pub extend IpInput with Show::{output, to_string}

///|
pub impl Show for IpInput with fn output(self, logger) {
  match self {
    AddressInput(address) => logger.write_string(address.to_string())
    InterfaceInput(interface) => logger.write_string(interface.to_string())
    NetworkInput(network) => logger.write_string(network.to_string())
  }
}

///|
/// Numeric mixed sort key: version, address value, then -1 for a host or the
/// network prefix length. Hosts precede networks with the same base address;
/// broader networks precede narrower ones with that same base. For unscoped
/// addresses and networks this reproduces Python's ordering. Zone identifiers are not
/// part of this numeric key; equal keys do not imply identical scoped objects.
/// Interfaces use their host value and ignore the prefix. Python's comparison
/// between two Interface objects can instead compare their networks first.
pub fn get_mixed_type_key(input : IpInput) -> (Int32, @bigint.BigInt, Int32) {
  match input {
    AddressInput(address) => (address.version(), address.to_bigint(), -1)
    InterfaceInput(interface) =>
      (interface.version(), interface.address().to_bigint(), -1)
    NetworkInput(network) =>
      (
        network.version(),
        network.network_address().to_bigint(),
        network.prefixlen(),
      )
  }
}

///|
/// Minimal CIDR cover of hosts, interfaces and networks from one family.
/// Hosts and interface hosts become /32 or /128 networks, ignoring interface
/// prefixes. Like collapse_addresses, the output is a canonical
/// numeric cover: IPv6 zones are discarded, never used to merge address bits.
pub fn collapse_ip_inputs(
  inputs : Array[IpInput],
) -> Array[IpNetwork] raise IpError {
  let networks : Array[IpNetwork] = []
  for input in inputs {
    match input {
      AddressInput(address) =>
        networks.push(
          IpNetwork::from_address(address.unscoped(), address.max_prefixlen()),
        )
      InterfaceInput(interface) =>
        networks.push(
          IpNetwork::from_address(interface.ip(), interface.max_prefixlen()),
        )
      NetworkInput(network) => networks.push(network)
    }
  }
  collapse_addresses(networks)
}