///|
/// PKCE (Proof Key for Code Exchange) Implementation
/// RFC 7636: https://datatracker.ietf.org/doc/html/rfc7636

///|
/// PKCE code_verifier
/// A cryptographically random string between 43 and 128 characters
pub struct PkceCodeVerifier {
  value : String
} derive(Show, Eq)

///|
/// PKCE code_challenge
/// Derived from code_verifier using S256 (SHA256) or Plain method
pub struct PkceCodeChallenge {
  value : String
  challenge_method : PkceCodeChallengeMethod
} derive(Show, Eq)

///|
/// PKCE code_challenge_method
pub enum PkceCodeChallengeMethod {
  Plain // code_challenge = code_verifier
  S256 // code_challenge = BASE64URL(SHA256(code_verifier))
} derive(Show, Eq)

///|
/// Create a new PkceCodeVerifier from an existing string
/// The string must be 43-128 characters long and contain only unreserved characters
pub fn PkceCodeVerifier::new(value : String) -> PkceCodeVerifier {
  { value, }
}

///|
/// Generate a random PKCE code_verifier
/// Returns a 43-character string (256 bits of entropy)
/// Uses cryptographically secure random number generator (Chacha8 CSPRNG)
/// Characters: A-Z, a-z, 0-9, -, ., _, ~
pub fn PkceCodeVerifier::new_random() -> PkceCodeVerifier {
  // Generate 43 random characters from unreserved character set
  // RFC 7636 recommends 43-128 characters, we use 43 (minimum)
  let r = @random.Rand::new()
  let unreserved_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
  let char_count = unreserved_chars.length()
  let result = StringBuilder::new()

  for i = 0; i < 43; i = i + 1 {
    let idx = r
      .uint(limit=char_count.reinterpret_as_uint())
      .reinterpret_as_int()
    result.write_char(unreserved_chars[idx].unsafe_to_char())
  }

  { value: result.to_string() }
}

///|
/// Get the string value of code_verifier
pub fn PkceCodeVerifier::to_string(self : PkceCodeVerifier) -> String {
  self.value
}

///|
/// Create a PkceCodeChallenge from a code_verifier
/// Uses S256 method (SHA256 hash + Base64URL encoding)
pub fn PkceCodeChallenge::from_verifier_s256(
  verifier : PkceCodeVerifier,
) -> PkceCodeChallenge {
  // Compute SHA256 hash of code_verifier using moonbitlang/x/crypto
  let hasher = @crypto.SHA256::new()

  // Convert String to FixedArray[Byte] for ByteSource
  let verifier_bytes = FixedArray::make(
    verifier.value.length(),
    Byte::default(),
  )
  for i = 0; i < verifier.value.length(); i = i + 1 {
    verifier_bytes[i] = verifier.value[i].to_int().to_byte()
  }

  hasher.update(verifier_bytes)
  let hash_bytes = hasher.finalize()

  // Convert FixedArray[Byte] to String for base64url encoding
  let hash_string = StringBuilder::new()
  for i = 0; i < hash_bytes.length(); i = i + 1 {
    hash_string.write_char(hash_bytes[i].to_int().unsafe_to_char())
  }

  // Base64URL encode the hash
  let challenge_value = base64url_encode(hash_string.to_string())

  { value: challenge_value, challenge_method: S256 }
}

///|
/// Create a PkceCodeChallenge using Plain method
/// code_challenge = code_verifier (not recommended, use S256 instead)
pub fn PkceCodeChallenge::from_verifier_plain(
  verifier : PkceCodeVerifier,
) -> PkceCodeChallenge {
  { value: verifier.value, challenge_method: Plain }
}

///|
/// Get the string value of code_challenge
pub fn PkceCodeChallenge::to_string(self : PkceCodeChallenge) -> String {
  self.value
}

///|
/// Get the code_challenge_method as a string
pub fn PkceCodeChallenge::method_string(self : PkceCodeChallenge) -> String {
  match self.challenge_method {
    Plain => "plain"
    S256 => "S256"
  }
}