// derived_components.mbt — Resolution of derived components (RFC 9421 §2.2).
//
// Derived components are computed from the message (request or response)
// rather than read from header fields. All values are emitted verbatim:
// percent-encoding, query order, repeated parameters, and empty parameters
// are preserved exactly as they appear.

///|
/// The message being signed.
pub(all) enum SignTarget {
  /// A request message.
  TargetRequest(RequestContext)
  /// A response message (may carry a related request for `req`).
  TargetResponse(ResponseContext)
}

///|
/// Resolves any covered component (derived or field) to its canonical value.
pub fn resolve_component(
  component : CoveredComponent,
  target : SignTarget,
) -> Result[String, HsError] {
  match component {
    Derived(d, params) => resolve_derived_component(d, target, params.req)
    Field(f) => resolve_field_component(f, target)
  }
}

///|
/// Resolves a derived component to its canonical string value.
///
/// `use_req` indicates the `req` parameter: when the target is a response,
/// the component is resolved against the related request instead.
pub fn resolve_derived_component(
  component : DerivedComponent,
  target : SignTarget,
  use_req : Bool,
) -> Result[String, HsError] {
  Ok(resolve_derived_component_raise(component, target, use_req)) catch {
    e => Err(e)
  }
}

///|
/// Internal: resolves a derived component, raising on failure.
fn resolve_derived_component_raise(
  component : DerivedComponent,
  target : SignTarget,
  use_req : Bool,
) -> String raise HsError {
  match component {
    SignatureParams =>
      raise hs_error(
        ComponentResolution,
        InvalidComponentCombination,
        "@signature-params cannot be resolved as a covered component value",
      )
    Method => effective_request(target, use_req).method
    TargetUri => effective_request(target, use_req).target_uri()
    Authority => effective_request(target, use_req).authority_component()
    Scheme => effective_request(target, use_req).scheme
    RequestTarget => effective_request(target, use_req).request_target()
    Path => effective_request(target, use_req).path_component()
    Query => effective_request(target, use_req).query_component()
    QueryParam(name) =>
      resolve_query_param(effective_request(target, use_req).raw_query(), name)
    Status => resolve_status(target)
  }
}

///|
/// Returns the effective request for resolution: the target when it is a
/// request, or the related request when `use_req` is set on a response.
fn effective_request(
  target : SignTarget,
  use_req : Bool,
) -> RequestContext raise HsError {
  match target {
    TargetRequest(req) => req
    TargetResponse(resp) =>
      if use_req {
        match resp.related_request {
          Some(req) => req
          None =>
            raise hs_error(
              ComponentResolution,
              MissingComponent,
              "req component requested but no related request present",
            )
        }
      } else {
        raise hs_error(
          ComponentResolution,
          InvalidComponentCombination,
          "request-derived component used on a response without req",
        )
      }
  }
}

///|
/// Returns the response status code as a decimal string.
fn resolve_status(target : SignTarget) -> String raise HsError {
  match target {
    TargetResponse(resp) => resp.status.to_string()
    TargetRequest(_) =>
      raise hs_error(
        ComponentResolution,
        InvalidComponentCombination,
        "@status is only valid on a response",
      )
  }
}

///|
/// Resolves `@query-param;name="name"` to the percent-encoded value of the
/// named query parameter.
///
/// The query is split on `&`; each part is split on the first `=`. Both the
/// parameter name in the component identifier and the query parameter names
/// are compared in their raw (percent-encoded) form, so no decoding is
/// applied and order/repetition semantics are preserved.
fn resolve_query_param(query : String, name : String) -> String raise HsError {
  let parts = query.split("&")
  for part in parts {
    let s = part.to_owned()
    let (pname, pvalue) = match s.split_once("=") {
      Some((n, v)) => (n.to_owned(), v.to_owned())
      None => (s, "")
    }
    if pname == name {
      return pvalue
    }
  }
  raise hs_error(
    ComponentResolution,
    MissingComponent,
    "query parameter not found: " + name,
  )
}