// crypto.mbt — local encryption (mirrors Python `crypto.py`)
//
// MoonBit has no Fernet binding, so we implement the same dependency-free
// XOR obfuscation that Python falls back to when the `cryptography` package is
// absent. The transform operates on Unicode code points (so CJK content stays
// intact and round-trips exactly) and is its own inverse. When disabled, data
// passes through unchanged — identical to the Python default behaviour.

///|
/// Optional local encryption provider.
pub struct CryptoProvider {
  enabled : Bool
  passphrase : String
} derive(Debug)

///|
/// Disabled provider: data is stored as-is.
pub fn CryptoProvider::disabled() -> CryptoProvider {
  { enabled: false, passphrase: "" }
}

///|
/// Enabled provider keyed by `passphrase`.
pub fn CryptoProvider::new(passphrase : String) -> CryptoProvider {
  { enabled: true, passphrase }
}

///|
/// Encrypt (XOR obfuscate) a string.
pub fn CryptoProvider::encrypt(self : CryptoProvider, data : String) -> String {
  if !self.enabled {
    return data
  }
  self.xor_obfuscate(data)
}

///|
/// Decrypt (XOR obfuscate) a string.
pub fn CryptoProvider::decrypt(self : CryptoProvider, data : String) -> String {
  if !self.enabled {
    return data
  }
  self.xor_obfuscate(data)
}

///|
fn xor_char(c : Char, kc : Char) -> Char {
  // `Int::to_char` validates the scalar; fall back to U+FFFD only for the
  // (extremely rare) case where XOR yields a non-scalar value, keeping the
  // transform its own inverse for all normal CJK/ASCII text.
  match (c.to_int() ^ kc.to_int()).to_char() {
    Some(x) => x
    None => Int::unsafe_to_char(0xFFFD)
  }
}

///|
fn CryptoProvider::xor_obfuscate(
  self : CryptoProvider,
  data : String,
) -> String {
  if self.passphrase == "" {
    return data
  }
  let key_chars = self.passphrase.iter().to_array()
  let kl = key_chars.length()
  let out : Array[Char] = []
  let mut i = 0
  for c in data.iter() {
    let kc = key_chars[i % kl]
    out.push(xor_char(c, kc))
    i = i + 1
  }
  String::from_array(out)
}