///|
/// A typed random-source boundary. The library deliberately does not choose a
/// platform RNG: native, browser, test, and embedded consumers inject one.
pub struct RandomSource {
  next : () -> UInt
}

///|
pub fn RandomSource::new(next : () -> UInt) -> RandomSource {
  { next, }
}

///|
pub fn RandomSource::next_u32(self : RandomSource) -> UInt {
  (self.next)()
}

///|
/// Separators supported by the portable passphrase generator.
pub enum PassphraseSeparator {
  Hyphen
  Period
  Underscore
} derive(Eq, Debug)

///|
pub fn PassphraseSeparator::hyphen() -> PassphraseSeparator {
  Hyphen
}

///|
pub fn PassphraseSeparator::period() -> PassphraseSeparator {
  Period
}

///|
pub fn PassphraseSeparator::underscore() -> PassphraseSeparator {
  Underscore
}

///|
fn PassphraseSeparator::index(self : PassphraseSeparator) -> Int {
  match self {
    Hyphen => 0
    Period => 1
    Underscore => 2
  }
}

///|
/// A portable passphrase policy. `complete_word=false` emits at most the first
/// four characters of each word, which is useful on constrained displays but
/// can reduce the number of unique outputs in a dictionary.
pub struct PassphrasePolicy {
  word_count : Int
  capitalize : Bool
  complete_word : Bool
  separator : PassphraseSeparator
}

///|
pub fn PassphrasePolicy::new(
  word_count : Int,
  capitalize? : Bool = false,
  complete_word? : Bool = true,
  separator? : PassphraseSeparator = Hyphen,
) -> PassphrasePolicy {
  { word_count, capitalize, complete_word, separator, }
}

///|
pub fn PassphrasePolicy::standard() -> PassphrasePolicy {
  PassphrasePolicy::new(4)
}

///|
pub fn PassphrasePolicy::word_count(self : PassphrasePolicy) -> Int {
  self.word_count
}

///|
pub fn PassphrasePolicy::is_valid(self : PassphrasePolicy) -> Bool {
  self.word_count >= 3 && self.word_count <= 12
}

///|
/// Generate a password using a typed random source.
pub fn generate_password_with_source(
  policy : PasswordPolicy,
  source : RandomSource,
) -> Result[String, String] {
  generate_password(policy, fn() { source.next_u32() })
}

///|
/// Generate a PIN using a typed random source.
pub fn generate_pin_with_source(
  length : Int,
  source : RandomSource,
) -> Result[String, String] {
  generate_pin(length, fn() { source.next_u32() })
}

///|
/// Generate a passphrase from an in-memory dictionary. Embedded consumers can
/// use `generate_passphrase_into` directly to read words from Flash.
pub fn generate_passphrase(
  policy : PassphrasePolicy,
  dictionary : Array[String],
  next_u32 : () -> UInt,
) -> Result[String, String] {
  if !policy.is_valid() || dictionary.length() == 0 {
    return Err("invalid passphrase policy or empty dictionary")
  }
  let builder = StringBuilder()
  let result = generate_passphrase_into(
    next_u32,
    fn(code) {
      match code.to_char() {
        Some(ch) => {
          builder.write_char(ch)
          true
        }
        None => false
      }
    },
    dictionary.length(),
    fn(index) { dictionary[index].length() },
    fn(index, offset) {
      match dictionary[index].get_char(offset) {
        Some(ch) => ch.to_int()
        None => -1
      }
    },
    policy.word_count,
    policy.capitalize,
    policy.complete_word,
    policy.separator.index(),
  )
  match result {
    0 => Ok(builder.to_string())
    -1 => Err("dictionary must contain non-empty lowercase ASCII words")
    _ => Err("output rejected generated passphrase character")
  }
}

///|
pub fn generate_passphrase_with_source(
  policy : PassphrasePolicy,
  dictionary : Array[String],
  source : RandomSource,
) -> Result[String, String] {
  generate_passphrase(policy, dictionary, fn() { source.next_u32() })
}

///|
/// Check that a candidate exactly satisfies the selected password policy.
pub fn validate_password(policy : PasswordPolicy, candidate : String) -> Bool {
  if !policy.is_valid() || candidate.length() != policy.length {
    return false
  }
  let mut has_lower = false
  let mut has_upper = false
  let mut has_digit = false
  let mut has_symbol = false
  for ch in candidate {
    let code = ch.to_int()
    if policy.exclude_ambiguous && is_ambiguous(code) {
      return false
    }
    if is_lower(code) {
      if !policy.lowercase {
        return false
      }
      has_lower = true
    } else if is_upper(code) {
      if !policy.uppercase {
        return false
      }
      has_upper = true
    } else if is_digit(code) {
      if !policy.digits {
        return false
      }
      has_digit = true
    } else if is_symbol(code) {
      if !policy.symbols || (policy.safe_symbols_only && !is_safe_symbol(code)) {
        return false
      }
      has_symbol = true
    } else {
      return false
    }
  }
  (!policy.lowercase || has_lower) &&
  (!policy.uppercase || has_upper) &&
  (!policy.digits || has_digit) &&
  (!policy.symbols || has_symbol)
}

///|
/// Check that a candidate is a decimal PIN in the supported 4..32 range.
pub fn validate_pin(candidate : String) -> Bool {
  if candidate.length() < 4 || candidate.length() > 32 {
    return false
  }
  for ch in candidate {
    if !is_digit(ch.to_int()) {
      return false
    }
  }
  true
}

///|
/// Password-strength bands based on an entropy estimate measured in tenths of
/// a bit. The thresholds are presentation aids, not an online-attack promise.
pub enum Strength {
  Unknown
  Weak
  Fair
  Strong
  VeryStrong
} derive(Eq, Debug)

///|
pub fn Strength::unknown() -> Strength {
  Unknown
}

///|
pub fn Strength::weak() -> Strength {
  Weak
}

///|
pub fn Strength::fair() -> Strength {
  Fair
}

///|
pub fn Strength::strong() -> Strength {
  Strong
}

///|
pub fn Strength::very_strong() -> Strength {
  VeryStrong
}

///|
pub fn strength_from_entropy_x10(entropy_x10 : Int) -> Strength {
  if entropy_x10 <= 0 {
    Unknown
  } else if entropy_x10 < 300 {
    Weak
  } else if entropy_x10 < 500 {
    Fair
  } else if entropy_x10 < 800 {
    Strong
  } else {
    VeryStrong
  }
}

///|
/// Estimate `log2(value)` in thousandths without floating-point arithmetic.
fn log2_milli(value : Int) -> Int {
  if value <= 1 {
    return 0
  }
  let mut integer = 0
  let mut base = 1
  while base * 2 <= value {
    base = base * 2
    integer = integer + 1
  }
  let mut normalized = value.to_uint64() * 65536UL / base.to_uint64()
  let mut fraction_1024 = 0
  let mut step = 0
  while step < 10 {
    normalized = normalized * normalized / 65536UL
    if normalized >= 131072UL {
      normalized = normalized / 2UL
      fraction_1024 = fraction_1024 + (1 << (9 - step))
    }
    step = step + 1
  }
  integer * 1000 + fraction_1024 * 1000 / 1024
}

///|
/// Estimate password entropy in tenths of a bit from length and pool size.
/// The value is a configuration estimate, not a claim about RNG quality.
pub fn PasswordPolicy::estimated_entropy_bits_x10(self : PasswordPolicy) -> Int {
  if !self.is_valid() {
    return 0
  }
  self.length *
  log2_milli(
    pool_size(
      self.lowercase,
      self.uppercase,
      self.digits,
      self.symbols,
      self.exclude_ambiguous,
      self.safe_symbols_only,
    ),
  ) /
  100
}

///|
/// Estimate decimal PIN entropy in tenths of a bit.
pub fn estimate_pin_entropy_bits_x10(length : Int) -> Int {
  if length < 4 || length > 32 {
    0
  } else {
    length * 3322 / 100
  }
}

///|
/// Estimate passphrase entropy in tenths of a bit. `unique_output_count` must
/// be the number of distinct emitted words or prefixes after transformations.
pub fn estimate_passphrase_entropy_bits_x10(
  policy : PassphrasePolicy,
  unique_output_count : Int,
) -> Int {
  if !policy.is_valid() || unique_output_count <= 1 {
    0
  } else {
    policy.word_count * log2_milli(unique_output_count) / 100
  }
}