// component_params.mbt — Covered-component parameter parsing/serialization.
//
// RFC 9421 §3.1.2 defines the component parameters `sf`, `key`, `name`,
// `bs`, `req`, and `tr`. They appear both in the covered-components inner
// list (as item parameters) and in the signature base component identifier.
// This module parses a parameter list into a `ComponentParameters` and
// serializes it back canonically, enforcing RFC 9421 combination rules.
//
// Internal functions raise `HsError`; public wrappers convert to `Result`.

///|
/// Parses item parameters into `ComponentParameters`, enforcing RFC 9421
/// §3.1.2 rules:
/// - unknown parameters are rejected;
/// - `sf`/`bs`/`req`/`tr` must be booleans;
/// - `key`/`name` must be strings;
/// - `name` is only valid on `@query-param` (checked by the caller).
pub fn parse_component_parameters(
  params : Array[SfParameter],
) -> Result[ComponentParameters, HsError] {
  Ok(parse_component_parameters_raise(params)) catch {
    e => Err(e)
  }
}

///|
/// Internal: parses item parameters, raising on violation.
fn parse_component_parameters_raise(
  params : Array[SfParameter],
) -> ComponentParameters raise HsError {
  let mut sf = false
  let mut key : String? = None
  let mut bs = false
  let mut tr = false
  let mut req = false
  let mut name : String? = None
  for p in params {
    match p.name {
      "sf" =>
        match p.value {
          SfBoolean(v) => sf = v
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "sf parameter must be a boolean",
            )
        }
      "key" =>
        match p.value {
          SfString(v) => key = Some(v)
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "key parameter must be a string",
            )
        }
      "bs" =>
        match p.value {
          SfBoolean(v) => bs = v
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "bs parameter must be a boolean",
            )
        }
      "tr" =>
        match p.value {
          SfBoolean(v) => tr = v
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "tr parameter must be a boolean",
            )
        }
      "req" =>
        match p.value {
          SfBoolean(v) => req = v
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "req parameter must be a boolean",
            )
        }
      "name" =>
        match p.value {
          SfString(v) => name = Some(v)
          _ =>
            raise hs_error(
              SignatureInputParsing,
              InvalidCoveredComponent,
              "name parameter must be a string",
            )
        }
      _ =>
        raise hs_error(
          SignatureInputParsing,
          UnsupportedComponentParameter,
          "unsupported component parameter: " + p.name,
        )
    }
  }
  { sf, key, bs, tr, req, name }
}

///|
/// Serializes component parameters canonically: boolean-true parameters as
/// bare `;name`, other values as `;name=value`.
pub fn serialize_component_parameters(
  params : ComponentParameters,
) -> Result[String, HsError] {
  Ok(serialize_component_parameters_raise(params)) catch {
    e => Err(e)
  }
}

///|
/// Internal: serializes component parameters, raising on failure.
fn serialize_component_parameters_raise(
  params : ComponentParameters,
) -> String raise HsError {
  let out = Buffer::Buffer()
  if params.sf {
    out.write_string_utf8(";sf")
  }
  if params.key is Some(_) {
    out.write_string_utf8(
      ";key=" + serialize_sf_string_raise(params.key.unwrap()),
    )
  }
  if params.bs {
    out.write_string_utf8(";bs")
  }
  if params.tr {
    out.write_string_utf8(";tr")
  }
  if params.req {
    out.write_string_utf8(";req")
  }
  if params.name is Some(_) {
    out.write_string_utf8(
      ";name=" + serialize_sf_string_raise(params.name.unwrap()),
    )
  }
  buffer_to_string(out)
}

///|
/// Validates a parsed covered component against the RFC 9421 combination
/// rules that depend only on the component itself:
///
/// 1. `name` is only allowed on `@query-param`;
/// 2. `key` requires `sf` on a field component (dictionary semantics);
/// 3. `sf` and `bs` are mutually exclusive.
fn validate_component_combination(
  component : CoveredComponent,
) -> Result[Unit, HsError] {
  match component {
    Field(f) => {
      let p = f.parameters
      if p.name is Some(_) {
        return Err(
          hs_error(
            SignatureInputParsing,
            InvalidComponentCombination,
            "name parameter is only valid on @query-param",
          ),
        )
      }
      if p.key is Some(_) && !p.sf {
        return Err(
          hs_error(
            SignatureInputParsing,
            InvalidComponentCombination,
            "key parameter requires sf",
          ),
        )
      }
      if p.sf && p.bs {
        return Err(
          hs_error(
            SignatureInputParsing,
            InvalidComponentCombination,
            "sf and bs are mutually exclusive",
          ),
        )
      }
      Ok(())
    }
    Derived(QueryParam(name), _) =>
      if name.is_empty() {
        Err(
          hs_error(
            SignatureInputParsing,
            InvalidComponentCombination,
            "@query-param requires a non-empty name",
          ),
        )
      } else {
        Ok(())
      }
    Derived(_, _) => Ok(())
  }
}

///|
/// Parses a single covered component from an `SfItem`.
///
/// Per RFC 9421 §2.5 the component identifier is an sf-string; we also accept
/// an unquoted token for lenience. Identifiers starting with `@` denote
/// derived components, everything else is a field component.
pub fn parse_covered_component(
  item : SfItem,
) -> Result[CoveredComponent, HsError] {
  let token = match item.value {
    SfString(s) => s
    SfToken(t) => t
    _ =>
      return Err(
        hs_error(
          SignatureInputParsing,
          InvalidCoveredComponent,
          "covered component must be a string",
        ),
      )
  }
  let params = match parse_component_parameters(item.parameters) {
    Ok(p) => p
    Err(e) => return Err(e)
  }
  if token.has_prefix("@") {
    // Derived components: only @query-param carries parameters; all others
    // must have none.
    match token {
      "@method" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Method, params),
        )
      "@target-uri" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(TargetUri, params),
        )
      "@authority" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Authority, params),
        )
      "@scheme" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Scheme, params),
        )
      "@request-target" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(RequestTarget, params),
        )
      "@path" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Path, params),
        )
      "@query" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Query, params),
        )
      "@status" =>
        ok_derived_params(
          item,
          token,
          covered_derived_with_params(Status, params),
        )
      "@signature-params" =>
        ok_derived_params(item, token, covered_derived(SignatureParams))
      "@query-param" =>
        match params.name {
          Some(qpname) => {
            let cc = covered_derived_with_params(QueryParam(qpname), params)
            match validate_component_combination(cc) {
              Ok(_) => Ok(cc)
              Err(e) => Err(e)
            }
          }
          None =>
            Err(
              hs_error(
                SignatureInputParsing,
                InvalidComponentCombination,
                "@query-param requires a name parameter",
              ),
            )
        }
      _ =>
        Err(
          hs_error(
            SignatureInputParsing,
            UnsupportedDerivedComponent,
            "unsupported derived component: " + token,
          ),
        )
    }
  } else {
    // Field component.
    let field = { name: token, parameters: params }
    let cc = Field(field)
    match validate_component_combination(cc) {
      Ok(_) => Ok(cc)
      Err(e) => Err(e)
    }
  }
}

///|
/// Returns the given derived component if its parameters are limited to the
/// allowed `req` flag; otherwise raises `InvalidCoveredComponent`.
fn ok_derived_params(
  item : SfItem,
  token : String,
  component : CoveredComponent,
) -> Result[CoveredComponent, HsError] {
  for p in item.parameters {
    if p.name != "req" {
      return Err(
        hs_error(
          SignatureInputParsing,
          InvalidCoveredComponent,
          token + " does not support parameter: " + p.name,
        ),
      )
    }
  }
  Ok(component)
}

///|
/// Reconstructs an `SfItem` for a covered component (used when serializing a
/// Signature-Input or signature base). The item value is the sf-string of the
/// component name; component parameters are mapped back to SF parameters.
pub fn covered_component_to_sf_item(component : CoveredComponent) -> SfItem {
  match component {
    Derived(QueryParam(name), params) => {
      let parameters : Array[SfParameter] = Array::new()
      parameters.push({ name: "name", value: SfString(name) })
      if params.req {
        parameters.push({ name: "req", value: SfBoolean(true) })
      }
      { value: SfString("@query-param"), parameters }
    }
    Derived(d, params) => {
      let parameters : Array[SfParameter] = Array::new()
      if params.req {
        parameters.push({ name: "req", value: SfBoolean(true) })
      }
      { value: SfString(derived_component_name(d)), parameters }
    }
    Field(f) => {
      let p = f.parameters
      let parameters : Array[SfParameter] = Array::new()
      if p.sf {
        parameters.push({ name: "sf", value: SfBoolean(true) })
      }
      if p.key is Some(k) {
        parameters.push({ name: "key", value: SfString(k) })
      }
      if p.bs {
        parameters.push({ name: "bs", value: SfBoolean(true) })
      }
      if p.tr {
        parameters.push({ name: "tr", value: SfBoolean(true) })
      }
      if p.req {
        parameters.push({ name: "req", value: SfBoolean(true) })
      }
      if p.name is Some(n) {
        parameters.push({ name: "name", value: SfString(n) })
      }
      { value: SfString(f.name), parameters }
    }
  }
}

///|
/// Parses a covered-components inner list into an ordered array.
pub fn parse_covered_components(
  items : Array[SfItem],
  max_components : Int,
) -> Result[Array[CoveredComponent], HsError] {
  if items.length() > max_components {
    return Err(
      hs_error(
        SignatureInputParsing,
        TooManyComponents,
        "too many covered components",
      ),
    )
  }
  let out : Array[CoveredComponent] = Array::new()
  for item in items {
    match parse_covered_component(item) {
      Ok(cc) => out.push(cc)
      Err(e) => return Err(e)
    }
  }
  Ok(out)
}