// Copyright (c) 2026 moonbit-bimap contributors
// SPDX-License-Identifier: Apache-2.0

// Additional public API: non-overwriting insert, positional (index) access,
// and construction / conversion helpers. All mutations still funnel through
// the invariant chokepoints in bimap.mbt (put_pair / remove_by_*).

// ---------------------------------------------------------------------------
// Non-overwriting insert
// ---------------------------------------------------------------------------

///|
/// Insert `(l, r)` only if NEITHER side is already present. If the left key or
/// the right value already exists, the map is left unchanged and the attempted
/// pair is returned in `Err((l, r))`; otherwise the pair is inserted and
/// `Ok(())` is returned. Aligns with Rust `bimap::insert_no_overwrite`.
pub fn[L : Hash + Eq, R : Hash + Eq] BiMap::insert_no_overwrite(
  self : BiMap[L, R],
  l : L,
  r : R,
) -> Result[Unit, (L, R)] {
  // Both conflict checks happen BEFORE any mutation.
  if self.forward.contains(l) || self.backward.contains(r) {
    Err((l, r))
  } else {
    self.put_pair(l, r) |> ignore
    Ok(())
  }
}

// ---------------------------------------------------------------------------
// Positional (index) access — the ordering dividend
// ---------------------------------------------------------------------------

///|
/// Return the pair at insertion-order index `i`, or `None` if `i` is out of
/// bounds.
pub fn[L : Hash + Eq, R] BiMap::get_index(
  self : BiMap[L, R],
  i : Int,
) -> (L, R)? {
  if i < 0 || i >= self.order.length() {
    return None
  }
  let l = self.order[i]
  match self.forward.get(l) {
    Some(r) => Some((l, r))
    None => None
  }
}

///|
/// Return the insertion-order index of left key `l`, or `None` if absent.
pub fn[L : Hash + Eq, R] BiMap::get_index_of_left(
  self : BiMap[L, R],
  l : L,
) -> Int? {
  self.positions.get(l)
}

///|
/// Return the insertion-order index of the pair whose right value is `r`, or
/// `None` if absent. This is a convenience for
/// `get_index_of_left(get_by_right(r))`: the order array only stores left keys,
/// so a right value's index is the index of the left key it is paired with.
pub fn[L : Hash + Eq, R : Hash + Eq] BiMap::get_index_of_right(
  self : BiMap[L, R],
  r : R,
) -> Int? {
  match self.backward.get(r) {
    Some(l) => self.positions.get(l)
    None => None
  }
}

///|
/// Return the earliest-inserted pair, or `None` if the map is empty.
pub fn[L : Hash + Eq, R] BiMap::first(self : BiMap[L, R]) -> (L, R)? {
  self.get_index(0)
}

///|
/// Return the most-recently-inserted pair, or `None` if the map is empty.
pub fn[L : Hash + Eq, R] BiMap::last(self : BiMap[L, R]) -> (L, R)? {
  if self.order.length() == 0 {
    return None
  }
  self.get_index(self.order.length() - 1)
}

// ---------------------------------------------------------------------------
// Construction / conversion
// ---------------------------------------------------------------------------

///|
/// Build a `BiMap` from an array of pairs. Pairs are inserted in order through
/// `insert`, so on a conflict the LATER pair wins (aligning with Rust
/// `FromIterator`). For example `from_array([("a",1), ("a",2)])` yields
/// `{ "a" <-> 2 }`.
pub fn[L : Hash + Eq, R : Hash + Eq] BiMap::from_array(
  pairs : Array[(L, R)],
) -> BiMap[L, R] {
  let m = BiMap::with_capacity(pairs.length())
  let mut i = 0
  while i < pairs.length() {
    let (l, r) = pairs[i]
    m.insert(l, r) |> ignore
    i = i + 1
  }
  m
}

///|
/// Return an independent deep copy. Mutating the copy does not affect the
/// original (and vice versa). Insertion order is preserved.
pub fn[L : Hash + Eq, R : Hash + Eq] BiMap::copy(
  self : BiMap[L, R],
) -> BiMap[L, R] {
  let result = BiMap::with_capacity(self.order.length())
  let mut i = 0
  while i < self.order.length() {
    let l = self.order[i]
    match self.forward.get(l) {
      Some(r) => result.insert(l, r) |> ignore
      None => ()
    }
    i = i + 1
  }
  result
}

///|
/// Return an independent copy with the two sides swapped: a `BiMap[R, L]`
/// whose left keys are this map's right values (in the same pair order). This
/// is a COPY, not a live view — mutating it does not affect the original.
/// (This differs from Guava's live `inverse()` and matches Rust bimap's
/// method-based reverse access.)
pub fn[L : Hash + Eq, R : Hash + Eq] BiMap::to_inverse(
  self : BiMap[L, R],
) -> BiMap[R, L] {
  let result : BiMap[R, L] = BiMap::with_capacity(self.order.length())
  let mut i = 0
  while i < self.order.length() {
    let l = self.order[i]
    match self.forward.get(l) {
      Some(r) => result.insert(r, l) |> ignore
      None => ()
    }
    i = i + 1
  }
  result
}