///|
/// Public topology of the input molecule.
pub(all) enum Topology {
  Linear
  Circular
} derive(Eq, Debug, ToJson, @json.FromJson)

///|
/// Stable domain failures; details never contain source sequences.
pub(all) suberror RestrictError {
  InvalidInput(String)
  LimitExceeded(String)
  CutConflict(String)
} derive(Eq, Debug)

///|
/// Validated uppercase IUPAC DNA. Coordinates are zero-based base boundaries.
pub struct Dna {
  bases : String
  topology : Topology
} derive(Eq, Debug, ToJson)

///|
/// Validate before using upstream permissive IUPAC conversion.
pub fn Dna::new(
  raw : String,
  topology? : Topology = Linear,
) -> Dna raise RestrictError {
  if raw.length() == 0 {
    raise InvalidInput("DNA_EMPTY")
  }
  if raw.length() > 100000 {
    raise LimitExceeded("DNA_MAX_100000")
  }
  let s = raw.to_upper()
  for c in s.iter() {
    match c {
      'A'
      | 'C'
      | 'G'
      | 'T'
      | 'R'
      | 'Y'
      | 'S'
      | 'W'
      | 'K'
      | 'M'
      | 'B'
      | 'D'
      | 'H'
      | 'V'
      | 'N' => ()
      _ => raise InvalidInput("DNA_ALPHABET")
    }
  }
  { bases: s, topology }
}

///|
pub fn Dna::sequence(self : Dna) -> String {
  self.bases
}

///|
pub fn Dna::topology(self : Dna) -> Topology {
  self.topology
}

///|
pub fn Dna::length(self : Dna) -> Int {
  self.bases.length()
}

///|
/// Base-set expansion is owned by genetic_code, not reimplemented here.
pub fn Dna::base_options(
  self : Dna,
  index : Int,
) -> Array[Char] raise RestrictError {
  if index < 0 || index >= self.length() {
    raise InvalidInput("BASE_INDEX")
  }
  let c = self.bases[index].to_int().unsafe_to_char()
  @base.IupacBase::from_char(c).expand().map(n => n.to_char())
}