///|
let max_key_value_pairs = 64

///|
let max_len_of_all_pairs = 8192

///|
/// Metadata attached to one baggage entry.
///
/// The stored text is trimmed when constructed.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview
pub struct BaggageMetadata {
  metadata : String
} derive(Eq, Compare, Hash, ToJson, Debug)

///|
/// One baggage entry together with its metadata payload.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview
pub struct KeyValueMetadata {
  key : @common.Key
  value : String
  metadata : BaggageMetadata
} derive(Eq, Compare, Hash, ToJson, Debug)

///|
/// Immutable baggage map used for cross-process propagation.
///
/// The implementation enforces OpenTelemetry-style limits on key count and
/// serialized size. Updates that violate those limits leave the baggage
/// unchanged.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview
pub struct Baggage {
  entries : Map[String, KeyValueMetadata]
  kv_content_len : Int
} derive(Eq, ToJson, Debug)

///|
/// Creates metadata from one text fragment after trimming surrounding
/// whitespace.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#set-value
pub fn BaggageMetadata::new(metadata : StringView) -> BaggageMetadata {
  { metadata: metadata.trim().to_owned() }
}

///|
/// Returns the stored metadata text.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview
pub fn BaggageMetadata::as_string(self : BaggageMetadata) -> String {
  self.metadata
}

///|
pub impl Default for BaggageMetadata with fn default() -> BaggageMetadata {
  BaggageMetadata::new("")
}

///|
/// Creates one baggage entry description.
///
/// This only packages the data. Validation happens when the entry is inserted
/// into a `Baggage`.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#set-value
pub fn KeyValueMetadata::new(
  key : StringView,
  value : StringView,
  metadata? : BaggageMetadata = Default::default(),
) -> KeyValueMetadata {
  { key: @common.Key::new(key), value: value.to_owned(), metadata }
}

///|
/// Returns an empty baggage value.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#overview
pub fn Baggage::new() -> Baggage {
  { entries: {}, kv_content_len: 0 }
}

///|
fn key_value_metadata_bytes_size(
  key : StringView,
  value : StringView,
  metadata : StringView,
) -> Int {
  let base = key.length() + value.length() + 1
  if metadata == "" {
    base
  } else {
    base + metadata.length() + 1
  }
}

///|
fn is_ascii_lower_or_digit(byte : Byte) -> Bool {
  (byte >= b'a' && byte <= b'z') || (byte >= b'0' && byte <= b'9')
}

///|
fn is_key_valid(key : StringView) -> Bool {
  if key == "" || key.length() > 256 {
    return false
  }
  let mut vendor_start : Int? = None
  for index = 0; index < key.length(); index = index + 1 {
    let byte = key.unsafe_get(index).to_byte()
    let is_allowed_special = byte == b'_' ||
      byte == b'-' ||
      byte == b'*' ||
      byte == b'/'
    if !(is_ascii_lower_or_digit(byte) || is_allowed_special || byte == b'@') {
      return false
    }
    if index == 0 && !is_ascii_lower_or_digit(byte) {
      return false
    }
    if byte == b'@' {
      if vendor_start is Some(_) || index + 14 < key.length() {
        return false
      }
      vendor_start = Some(index)
    } else {
      match vendor_start {
        Some(start) if index == start + 1 && !is_ascii_lower_or_digit(byte) =>
          return false
        _ => ()
      }
    }
  }
  true
}

///|
fn is_value_valid(value : StringView) -> Bool {
  value.length() <= 256 && !value.contains(",") && !value.contains("=")
}

///|
fn baggage_with_entries(entries : Map[String, KeyValueMetadata]) -> Baggage {
  let mut kv_content_len = 0
  for _, entry in entries {
    kv_content_len += key_value_metadata_bytes_size(
      entry.key.as_string(),
      entry.value,
      entry.metadata.as_string(),
    )
  }
  { entries, kv_content_len }
}

///|
/// Inserts one baggage item with empty metadata.
///
/// Invalid keys or values, entry-count overflow, and total-size overflow leave
/// the baggage unchanged.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#set-value
pub fn Baggage::insert(
  self : Baggage,
  key : StringView,
  value : StringView,
) -> Baggage {
  self.insert_with_metadata(key, value, Default::default())
}

///|
/// Inserts or replaces one baggage item together with explicit metadata.
///
/// Keys are trimmed before validation. Invalid data or limit violations return
/// the original baggage unchanged.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#set-value
pub fn Baggage::insert_with_metadata(
  self : Baggage,
  key : StringView,
  value : StringView,
  metadata : BaggageMetadata,
) -> Baggage {
  let key = key.trim().to_owned()
  let value = value.to_owned()
  let metadata_text = metadata.as_string()
  if !is_key_valid(key) || !is_value_valid(value) {
    return self
  }
  let entries = self.entries.copy()
  let new_entry = KeyValueMetadata::new(key, value, metadata~)
  let previous_len = match entries.get(key) {
    Some(previous) =>
      key_value_metadata_bytes_size(
        previous.key.as_string(),
        previous.value,
        previous.metadata.as_string(),
      )
    None => 0
  }
  if entries.get(key) is None && entries.length() >= max_key_value_pairs {
    return self
  }
  let new_len = key_value_metadata_bytes_size(key, value, metadata_text)
  let total_len = self.kv_content_len - previous_len + new_len
  if total_len > max_len_of_all_pairs {
    return self
  }
  entries[key] = new_entry
  baggage_with_entries(entries)
}

///|
/// Returns a new baggage value without `key`.
///
/// Removing a missing key still returns a fresh baggage value with the same
/// contents.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#remove-value
pub fn Baggage::remove(self : Baggage, key : StringView) -> Baggage {
  let entries = self.entries.copy()
  ignore(entries.remove(key.to_owned()))
  baggage_with_entries(entries)
}

///|
/// Looks up the string value for `key`.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#get-value
pub fn Baggage::get(self : Baggage, key : StringView) -> String? {
  self.entries.get(key.to_owned()).map(entry => entry.value)
}

///|
/// Looks up the value and metadata for `key`.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#get-all-values
pub fn Baggage::get_with_metadata(
  self : Baggage,
  key : StringView,
) -> (String, BaggageMetadata)? {
  self.entries.get(key.to_owned()).map(entry => (entry.value, entry.metadata))
}

///|
/// Returns all baggage entries as an array.
///
/// The returned array is detached from the internal map.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#get-all-values
pub fn Baggage::entries(self : Baggage) -> Array[KeyValueMetadata] {
  let entries = []
  for _, entry in self.entries {
    entries.push(entry)
  }
  entries
}

///|
/// Returns the current number of baggage entries.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#get-all-values
pub fn Baggage::len(self : Baggage) -> Int {
  self.entries.length()
}

///|
/// Returns whether the baggage contains no entries.
/// Spec: https://opentelemetry.io/docs/specs/otel/baggage/api/#get-all-values
pub fn Baggage::is_empty(self : Baggage) -> Bool {
  self.entries.is_empty()
}

///|
pub impl Default for Baggage with fn default() -> Baggage {
  Baggage::new()
}