// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// Lays what a caller gave over what a function starts from, one setting at a
/// time, and keeps the names of those that disagreed for `settle`.
///
///     let merge = @moonknob.Merge::new(wins)
///     let merged = {
///       ..policy,
///       leeway: merge.pick("leeway", policy.leeway, leeway),
///       expiry: merge.pick("expiry", policy.expiry, expiry),
///     }
///     merge.settle(clash, policy, merged, "jwt.verify: policy and arguments")
struct Merge {
  wins : Wins
  names : Array[String]
}

///|
pub fn Merge::new(wins : Wins) -> Merge {
  { wins, names: [], }
}

///|
/// One setting. `given` is `None` when the caller did not give it, and then
/// `base` stands; a value equal to `base` stands without being a clash.
pub fn[V : Eq] Merge::pick(
  self : Merge,
  name : String,
  base : V,
  given : V?,
) -> V {
  match given {
    None => base
    Some(value) if value == base => base
    Some(value) => {
      self.names.push(name)
      match self.wins {
        Extra => value
        Base => base
      }
    }
  }
}

///|
/// Two maps as one: every key of both, in the order `base` then `extra` has
/// them, with `wins` deciding a key they both hold with different values.
pub fn[V : Eq] Merge::union(
  self : Merge,
  base : Map[String, V],
  extra : Map[String, V],
) -> Map[String, V] {
  let out = base.copy()
  for key, value in extra {
    match base.get(key) {
      None => out[key] = value
      Some(held) => out[key] = self.pick(key, held, Some(value))
    }
  }
  out
}

///|
/// The names of the settings that disagreed so far.
pub fn Merge::names(self : Merge) -> ArrayView[String] {
  self.names[:]
}

///|
/// `clash` applied to what was gathered: see [`OnClash::settle`].
pub fn[T] Merge::settle(
  self : Merge,
  clash : OnClash[T],
  base : T,
  merged : T,
  site : String,
) -> T {
  clash.settle(base, merged, self.names, site)
}