// component.mbt — Covered component identifiers (RFC 9421 §4.1).
//
// A "covered component" names a piece of the HTTP message that is signed.
// Components are either derived (computed from the message, prefixed with
// `@`) or field components (named after an HTTP header/trailer). The set of
// covered components, in order, plus the signature parameters, form the
// signature base.

///|
/// A derived component computed from the message. `QueryParam` carries the
/// parameter name from its `name` parameter.
pub(all) enum DerivedComponent {
  /// `@signature-params` — the parameters of this signature itself.
  SignatureParams
  /// `@method` — the HTTP request method.
  Method
  /// `@target-uri` — scheme://authority/path?query (verbatim).
  TargetUri
  /// `@authority` — the authority portion verbatim.
  Authority
  /// `@scheme` — the URI scheme.
  Scheme
  /// `@request-target` — method SP request-target.
  RequestTarget
  /// `@path` — the path component verbatim.
  Path
  /// `@query` — the query component verbatim.
  Query
  /// `@query-param` — one query parameter by name.
  QueryParam(String)
  /// `@status` — the response status code (responses only).
  Status
}

///|
/// A field component: an HTTP header/trailer name plus its parameters.
pub(all) struct FieldComponent {
  name : String
  parameters : ComponentParameters
}

///|
/// Component parameters attached to a covered component.
pub(all) struct ComponentParameters {
  /// `sf` — the field value is a structured field value (RFC 9651).
  sf : Bool
  /// `key` — dictionary member key (only with `sf` on a dictionary field).
  key : String?
  /// `bs` — the field value is a base64-encoded byte sequence.
  bs : Bool
  /// `tr` — the component is taken from the trailer section.
  tr : Bool
  /// `req` — the component refers to the related request (responses only).
  req : Bool
  /// `name` — parameter name for `@query-param`.
  name : String?
}

///|
/// A covered component: derived or field.
///
/// Derived components carry their parameters so that `req` (and `name` for
/// `@query-param`) survives parsing and is emitted in the signature base
/// (RFC 9421 §2.4).
pub(all) enum CoveredComponent {
  Derived(DerivedComponent, ComponentParameters)
  Field(FieldComponent)
}

///|
/// The default component parameters (all unset).
pub fn ComponentParameters::default() -> ComponentParameters {
  { sf: false, key: None, bs: false, tr: false, req: false, name: None }
}

///|
/// Constructs a derived covered component with default parameters.
pub fn covered_derived(d : DerivedComponent) -> CoveredComponent {
  Derived(d, ComponentParameters::default())
}

///|
/// Constructs a derived covered component with explicit parameters.
pub fn covered_derived_with_params(
  d : DerivedComponent,
  parameters : ComponentParameters,
) -> CoveredComponent {
  Derived(d, parameters)
}

///|
/// Constructs a field covered component with default parameters.
pub fn covered_field(name : String) -> CoveredComponent {
  Field({ name, parameters: ComponentParameters::default() })
}

///|
/// Constructs a field covered component with explicit parameters.
pub fn covered_field_with_params(
  name : String,
  parameters : ComponentParameters,
) -> CoveredComponent {
  Field({ name, parameters })
}

///|
/// Returns the canonical component identifier string used in the signature
/// base (RFC 9421 §2.5). Every component name is serialized as an sf-string,
/// so the identifier is double-quoted; parameters follow the quotes.
/// Examples: `"@method"`, `"content-type";sf`, `"@query-param";name="foo"`.
pub fn CoveredComponent::to_component_string(
  self : CoveredComponent,
) -> Result[String, HsError] {
  Ok(covered_component_string_raise(self)) catch {
    e => Err(e)
  }
}

///|
/// Returns the bare (unquoted) identifier used for internal matching, e.g.
/// `@method`, `content-type`, `@query-param`.
pub fn CoveredComponent::identifier(self : CoveredComponent) -> String {
  match self {
    Derived(d, _) => derived_component_name(d)
    Field(f) => f.name
  }
}

///|
/// Returns the canonical quoted string for a derived component including its
/// parameters (`;req` and `;name` for `@query-param`).
fn derived_component_string(
  d : DerivedComponent,
  parameters : ComponentParameters,
) -> String raise HsError {
  let base = match d {
    SignatureParams => "\"@signature-params\""
    Method => "\"@method\""
    TargetUri => "\"@target-uri\""
    Authority => "\"@authority\""
    Scheme => "\"@scheme\""
    RequestTarget => "\"@request-target\""
    Path => "\"@path\""
    Query => "\"@query\""
    QueryParam(name) =>
      "\"@query-param\";name=" + serialize_sf_string_raise(name)
    Status => "\"@status\""
  }
  let mut out = base
  if parameters.req {
    out = out + ";req"
  }
  out
}

///|
/// Returns the bare derived component name (unquoted).
fn derived_component_name(d : DerivedComponent) -> String {
  match d {
    SignatureParams => "@signature-params"
    Method => "@method"
    TargetUri => "@target-uri"
    Authority => "@authority"
    Scheme => "@scheme"
    RequestTarget => "@request-target"
    Path => "@path"
    Query => "@query"
    QueryParam(_) => "@query-param"
    Status => "@status"
  }
}

///|
/// Internal: canonical component string, raising on failure.
fn covered_component_string_raise(
  component : CoveredComponent,
) -> String raise HsError {
  match component {
    Derived(d, params) => derived_component_string(d, params)
    Field(f) =>
      "\"" + f.name + "\"" + serialize_component_parameters_raise(f.parameters)
  }
}

///|
/// Returns the parameters of the component if it is a field component.
pub fn CoveredComponent::field_parameters(
  self : CoveredComponent,
) -> ComponentParameters? {
  match self {
    Field(f) => Some(f.parameters)
    Derived(_, _) => None
  }
}

///|
/// Returns the query-param name if this is `@query-param`, else `None`.
pub fn CoveredComponent::query_param_name(self : CoveredComponent) -> String? {
  match self {
    Derived(QueryParam(name), _) => Some(name)
    _ => None
  }
}

///|
/// Returns the derived component if this is a derived component.
pub fn CoveredComponent::as_derived(
  self : CoveredComponent,
) -> DerivedComponent? {
  match self {
    Derived(d, _) => Some(d)
    Field(_) => None
  }
}

///|
/// Returns the field component if this is a field component.
pub fn CoveredComponent::as_field(self : CoveredComponent) -> FieldComponent? {
  match self {
    Field(f) => Some(f)
    Derived(_, _) => None
  }
}