///|
/// Opaque owned network. Matrices are row-major and frequencies are Hz.
/// Parameters are S (dimensionless), Z (ohm), Y (siemens), or two-port H/G.
/// H/G diagonal units differ; file normalization is resolved by the parser.
pub struct Network {
  priv n : Int
  priv kind : String
  priv frequencies : Array[Double]
  priv reference : Array[Double]
  priv values : Array[Array[Complex]]
}

///|
/// Copy and validate inputs: 1..32 ports, positive real references, nonnegative
/// strictly increasing frequencies, at most 100000 points / 2000000 values.
pub fn network(
  ports : Int,
  frequencies : Array[Double],
  values : Array[Array[Complex]],
  reference : Array[Double],
  parameter? : String = "S",
) -> Network raise {
  let kind = parameter.to_upper()
  if ports < 1 || ports > 32 || reference.length() != ports {
    raise Failure("ports must be 1..32 with one real reference per port")
  }
  if !(kind == "S" || kind == "Z" || kind == "Y" || kind == "H" || kind == "G") ||
    ((kind == "H" || kind == "G") && ports != 2) {
    raise Failure("unsupported parameter or non-two-port hybrid network")
  }
  if frequencies.is_empty() ||
    frequencies.length() > 100000 ||
    frequencies.length() > 2000000 / (ports * ports) ||
    values.length() != frequencies.length() {
    raise Failure("network point count/shape exceeds bounds")
  }
  for r in reference {
    if !finite(r) || r <= 0.0 {
      raise Failure("reference must be positive and finite")
    }
  }
  for i, f in frequencies {
    if !finite(f) || f < 0.0 || (i > 0 && f <= frequencies[i - 1]) {
      raise Failure(
        "frequencies must be finite, nonnegative and strictly increasing",
      )
    }
    matrix_size(ports, values[i])
  }
  {
    n: ports,
    kind,
    frequencies: frequencies.copy(),
    reference: reference.copy(),
    values: values.map(x => x.copy()),
  }
}

///|
pub fn Network::ports(self : Network) -> Int {
  self.n
}

///|
pub fn Network::parameter(self : Network) -> String {
  self.kind
}

///|
pub fn Network::frequency_hz(self : Network) -> Array[Double] {
  self.frequencies.copy()
}

///|
pub fn Network::reference_ohms(self : Network) -> Array[Double] {
  self.reference.copy()
}

///|
/// Obtain an independent matrix copy; point is a zero-based frequency index.
pub fn Network::matrix(self : Network, point : Int) -> Array[Complex] raise {
  if point < 0 || point >= self.values.length() {
    raise Failure("frequency index out of range")
  }
  self.values[point].copy()
}