///|
/// Decoded JWT token parts.
pub(all) struct DecodedToken {
  header : Json
  claims : Json
  signature : Bytes
  raw_header : String
  raw_claims : String
  raw_signature : String
}

///|
/// Parse the header JSON from a decoded token.
fn parse_header_json(header_b64 : String) -> Result[Json, JwtError] {
  let header_bytes = match base64url_decode(header_b64) {
    None => return Err(InvalidHeader("cannot decode header"))
    Some(b) => b
  }
  let header_str = @utf8.decode_lossy(header_bytes)
  Ok(@json.parse(header_str)) catch {
    _ => Err(InvalidHeader("header is not valid JSON"))
  }
}

///|
/// Fully decode a JWT token into its parts (without verifying signature).
///
/// Returns the parsed header JSON, claims JSON, and raw signature bytes.
pub fn decode(token : String) -> Result[DecodedToken, JwtError] {
  let parts = token.split(".").to_array()
  if parts.length() != 3 {
    return Err(InvalidToken("token must have 3 parts"))
  }
  let header_b64 = parts[0].to_owned()
  let claims_b64 = parts[1].to_owned()
  let sig_b64 = parts[2].to_owned()
  // Parse header
  let header_json = match parse_header_json(header_b64) {
    Ok(h) => h
    Err(e) => return Err(e)
  }
  // Parse claims
  let claims_json : Json = match base64url_decode(claims_b64) {
    None => return Err(InvalidClaims("cannot decode claims"))
    Some(claims_bytes) => {
      let claims_str = @utf8.decode_lossy(claims_bytes)
      @json.parse(claims_str) catch {
        _ => return Err(InvalidClaims("claims is not valid JSON"))
      }
    }
  }
  // Decode signature
  let sig_bytes = match base64url_decode(sig_b64) {
    None => return Err(InvalidToken("cannot decode signature"))
    Some(b) => b
  }
  Ok({
    header: header_json,
    claims: claims_json,
    signature: sig_bytes,
    raw_header: header_b64,
    raw_claims: claims_b64,
    raw_signature: sig_b64,
  })
}

///|
/// Extract the algorithm from a decoded token's header.
pub fn DecodedToken::algorithm(
  self : DecodedToken,
) -> Result[Algorithm, JwtError] {
  match self.header {
    Object(o) =>
      match o["alg"] {
        String(s) =>
          match s {
            "HS256" => Ok(HS256)
            "HS384" => Ok(HS384)
            "HS512" => Ok(HS512)
            _ => Err(InvalidAlgorithm(s))
          }
        _ => Err(InvalidHeader("alg field is not a string"))
      }
    _ => Err(InvalidHeader("header is not an object"))
  }
}

///|
/// Extract the key ID (kid) from a decoded token's header.
pub fn DecodedToken::key_id(self : DecodedToken) -> String? {
  match self.header {
    Object(o) =>
      match o["kid"] {
        String(s) => Some(s)
        _ => None
      }
    _ => None
  }
}

///|
/// Extract the token type (typ) from a decoded token's header.
pub fn DecodedToken::token_type(self : DecodedToken) -> String? {
  match self.header {
    Object(o) =>
      match o["typ"] {
        String(s) => Some(s)
        _ => None
      }
    _ => None
  }
}

///|
/// Get a string claim from the decoded token.
pub fn DecodedToken::get_string_claim(
  self : DecodedToken,
  key : String,
) -> String? {
  match self.claims {
    Object(o) =>
      match o[key] {
        String(s) => Some(s)
        _ => None
      }
    _ => None
  }
}

///|
/// Get a numeric claim (as Int) from the decoded token.
pub fn DecodedToken::get_numeric_claim(
  self : DecodedToken,
  key : String,
) -> Int? {
  match self.claims {
    Object(o) =>
      match o[key] {
        Number(n, ..) => Some(n.to_int())
        _ => None
      }
    _ => None
  }
}