// parameter.mbt — RFC 8288 link-parameter parsing.
//
// RFC 8288 Section 3:
//
//     link-param = token BWS [ "=" BWS ( token / quoted-string ) ]
//
// Design decisions:
//
//   - Parameter *names* are strict RFC 7230 tokens (`tchar`), matching the
//     final RFC 8288 grammar exactly.
//
//   - Unquoted parameter *values* accept the broader `ptokenchar` set so
//     that common real-world values parse. The RFC's own `type` ABNF
//     (`type-name "/" subtype-name`) contains a `/` that the `token` rule
//     cannot express, so a strict `token`-only value parser would reject
//     `type=text/html`, which the RFC itself implies must work. The
//     accepted set is still bounded: it excludes SP, HTAB, DQUOTE, `;`,
//     `,`, and all control and non-ASCII bytes.
//
//   - A parameter whose name ends in `*` (for example `title*`) carries an
//     RFC 8187 extended value, parsed by `parse_extended_value`.
//
//   - Everything is a single pass over the byte cursor; no lookahead beyond
//     the current parameter.

///|
/// The raw form of a parsed parameter value, before it is dispatched to a
/// field of the link model.
pub enum RawParamValue {
  /// The parameter has no `=` and no value (`; foo`).
  Empty
  /// The value was the unquoted token form.
  Token(String)
  /// The value was the quoted-string form; the string is already unquoted.
  Quoted(String)
  /// The value of a `name*` parameter, decoded per RFC 8187.
  Extended(ExtendedValue)
}

///|
/// One parsed link-parameter: its name, the value form, and the byte offset
/// of the first byte of the value (used for error reporting).
pub struct ParsedParameter {
  name : String
  value_start : Int
  value : RawParamValue
}

///|
/// The parameter name.
pub fn ParsedParameter::name(self : ParsedParameter) -> String {
  self.name
}

///|
/// The byte offset of the first byte of the value.
pub fn ParsedParameter::value_start(self : ParsedParameter) -> Int {
  self.value_start
}

///|
/// The raw value form.
pub fn ParsedParameter::value(self : ParsedParameter) -> RawParamValue {
  self.value
}

///|
/// The value as a plain string: the unquoted content for the token and
/// quoted forms, or `None` for flag parameters and `name*` parameters
/// (which are not plain strings).
pub fn RawParamValue::as_string(self : RawParamValue) -> String? {
  match self {
    Empty => None
    Token(v) => Some(v)
    Quoted(v) => Some(v)
    Extended(_) => None
  }
}

///|
/// Whether the parameter has a value at all.
pub fn RawParamValue::has_value(self : RawParamValue) -> Bool {
  match self {
    Empty => false
    _ => true
  }
}

///|
/// Parses one link-parameter starting at the current scanner position.
/// On success the cursor is positioned immediately after the value (or
/// after the name, for flag parameters), before any following `;` or OWS.
///
/// Errors: `Parameter::MissingParameterName` (no token where a name is
/// required), `Parameter::InvalidParameter` (no value after `=`),
/// `Limit::LimitExceeded`, plus the quoted-string and extended-value
/// errors from `parse_quoted_string` and `parse_extended_value`.
pub fn parse_link_param(
  cursor : Scanner,
  limits : Limits,
) -> Result[ParsedParameter, LinkError] {
  let name_start = cursor.position()
  let (ns, ne) = cursor.consume_token()
  if ne == ns {
    return Err(
      link_error_at(
        Parameter,
        MissingParameterName,
        name_start,
        cursor.context_string(),
      ),
    )
  }
  if ne - ns > limits.max_parameter_name_bytes() {
    return Err(
      link_error_at(
        Limit,
        LimitExceeded,
        ns,
        "parameter name exceeds max_parameter_name_bytes",
      ),
    )
  }
  let name = cursor.take_string(ns, ne)

  // BWS around the "="
  cursor.skip_ows()
  if !cursor.consume_char(61) {
    return Ok({ name, value_start: cursor.position(), value: Empty })
  }
  cursor.skip_ows()
  let value_start = cursor.position()

  if ends_with_star(name) && name.length() > 1 {
    // RFC 8187 extended value
    let ev = parse_extended_value(cursor, limits) catch {
      e => return Err(unwrap_link_error(e))
    }
    if cursor.position() - value_start > limits.max_parameter_value_bytes() {
      return Err(
        link_error_at(
          Limit,
          LimitExceeded,
          value_start,
          "extended value exceeds max_parameter_value_bytes",
        ),
      )
    }
    return Ok({ name, value_start, value: Extended(ev) })
  }

  match cursor.peek_byte() {
    Some(34) => {
      // quoted-string
      let q = parse_quoted_string(cursor, limits) catch {
        e => return Err(unwrap_link_error(e))
      }
      Ok({ name, value_start, value: Quoted(q) })
    }
    _ => {
      // unquoted value: ptokenchar (documented lenient superset of token)
      let (vs, ve) = cursor.consume_while(fn(b) { ptokenchar(b) })
      if ve == vs {
        return Err(
          link_error_at(
            Parameter,
            InvalidParameter,
            value_start,
            cursor.context_string(),
          ),
        )
      }
      if ve - vs > limits.max_parameter_value_bytes() {
        return Err(
          link_error_at(
            Limit,
            LimitExceeded,
            vs,
            "parameter value exceeds max_parameter_value_bytes",
          ),
        )
      }
      Ok({ name, value_start, value: Token(cursor.take_string(vs, ve)) })
    }
  }
}

///|
/// Whether the given parameter name is an RFC 8187 `name*` form.
pub fn is_ext_value_name(name : String) -> Bool {
  name.length() > 1 && ends_with_star(name)
}

///|
fn ends_with_star(name : String) -> Bool {
  let bytes = @utf8.encode(name)
  if bytes.length() == 0 {
    return false
  }
  bytes[bytes.length() - 1] == 42
}