///|
pub(all) enum TraceContextError {
  InvalidTraceId(String)
  InvalidSpanId(String)
  InvalidTraceFlags(String)
  InvalidTraceParent(String)
  UnsupportedTraceParentVersion(String)
  InvalidTraceState(String)
} derive(Eq, Compare, Debug)

///|
pub impl Show for TraceContextError with fn output(self, logger) {
  logger.write_string(self.to_string())
}

///|
pub fn TraceContextError::to_string(self : TraceContextError) -> String {
  match self {
    InvalidTraceId(reason) =>
      "invalid trace id: " + escape_control_chars(reason)
    InvalidSpanId(reason) => "invalid span id: " + escape_control_chars(reason)
    InvalidTraceFlags(reason) =>
      "invalid trace flags: " + escape_control_chars(reason)
    InvalidTraceParent(reason) =>
      "invalid traceparent: " + escape_control_chars(reason)
    UnsupportedTraceParentVersion(version) =>
      "unsupported traceparent version: " + escape_control_chars(version)
    InvalidTraceState(reason) =>
      "invalid tracestate: " + escape_control_chars(reason)
  }
}

///|
pub struct TraceId {
  value : String
} derive(Eq, Compare, Debug)

///|
pub struct SpanId {
  value : String
} derive(Eq, Compare, Debug)

///|
pub struct TraceFlags {
  flags : Int
} derive(Eq, Compare, Debug)

///|
pub struct TraceStateEntry {
  key : String
  value : String
} derive(Eq, Compare, Debug)

///|
pub struct TraceState {
  priv entries : Array[TraceStateEntry]
} derive(Debug)

///|
pub struct SpanContext {
  trace_id : String
  span_id : String
  flags : TraceFlags
  trace_state : TraceState
  is_remote : Bool
} derive(Debug)

///|
const TRACEPARENT_MAX_LENGTH : Int = 512

///|
const TRACESTATE_MAX_LENGTH : Int = 512

///|
fn is_lower_hex_unit(unit : Int) -> Bool {
  (unit >= 48 && unit <= 57) || (unit >= 97 && unit <= 102)
}

///|
fn hex_value_unit(unit : Int) -> Int? {
  if unit >= 48 && unit <= 57 {
    Some(unit - 48)
  } else if unit >= 97 && unit <= 102 {
    Some(unit - 87)
  } else {
    None
  }
}

///|
fn is_lower_hex_string(value : String, expected_len : Int) -> Bool {
  if value.length() != expected_len {
    return false
  }
  let mut ok = true
  for i in 0.. Bool {
  let mut all_zero = true
  for i in 0.. String {
  match value {
    0 => "0"
    1 => "1"
    2 => "2"
    3 => "3"
    4 => "4"
    5 => "5"
    6 => "6"
    7 => "7"
    8 => "8"
    9 => "9"
    10 => "a"
    11 => "b"
    12 => "c"
    13 => "d"
    14 => "e"
    _ => "f"
  }
}

///|
fn parse_hex_byte(value : String) -> Int? {
  guard value.length() == 2 else { return None }
  match
    (
      hex_value_unit(value.code_unit_at(0).to_int()),
      hex_value_unit(value.code_unit_at(1).to_int()),
    ) {
    (Some(hi_val), Some(lo_val)) => Some(hi_val * 16 + lo_val)
    _ => None
  }
}

///|
pub fn TraceId::parse(value : String) -> Result[TraceId, TraceContextError] {
  if !is_lower_hex_string(value, 32) {
    Result::Err(InvalidTraceId("expected 32 lowercase hex characters"))
  } else if is_all_zero(value) {
    Result::Err(InvalidTraceId("all-zero trace id is invalid"))
  } else {
    Result::Ok({ value, })
  }
}

///|
pub fn trace_id(value : String) -> Result[TraceId, TraceContextError] {
  TraceId::parse(value)
}

///|
pub fn TraceId::to_string(self : TraceId) -> String {
  self.value
}

///|
pub impl Show for TraceId with fn output(self, logger) {
  logger.write_string(self.value)
}

///|
pub fn SpanId::parse(value : String) -> Result[SpanId, TraceContextError] {
  if !is_lower_hex_string(value, 16) {
    Result::Err(InvalidSpanId("expected 16 lowercase hex characters"))
  } else if is_all_zero(value) {
    Result::Err(InvalidSpanId("all-zero span id is invalid"))
  } else {
    Result::Ok({ value, })
  }
}

///|
pub fn span_id(value : String) -> Result[SpanId, TraceContextError] {
  SpanId::parse(value)
}

///|
pub fn SpanId::to_string(self : SpanId) -> String {
  self.value
}

///|
pub impl Show for SpanId with fn output(self, logger) {
  logger.write_string(self.value)
}

///|
pub fn TraceFlags::from_int(
  flags : Int,
) -> Result[TraceFlags, TraceContextError] {
  if flags < 0 || flags > 255 {
    Result::Err(InvalidTraceFlags("expected an unsigned 8-bit value"))
  } else {
    Result::Ok({ flags, })
  }
}

///|
pub fn TraceFlags::from_hex(
  value : String,
) -> Result[TraceFlags, TraceContextError] {
  match parse_hex_byte(value) {
    Some(flags) => TraceFlags::from_int(flags)
    None =>
      Result::Err(InvalidTraceFlags("expected 2 lowercase hex characters"))
  }
}

///|
pub fn TraceFlags::sampled() -> TraceFlags {
  { flags: 1 }
}

///|
pub fn TraceFlags::unsampled() -> TraceFlags {
  { flags: 0 }
}

///|
pub fn trace_flags_sampled() -> TraceFlags {
  TraceFlags::sampled()
}

///|
pub fn trace_flags_unsampled() -> TraceFlags {
  TraceFlags::unsampled()
}

///|
pub fn TraceFlags::is_sampled(self : TraceFlags) -> Bool {
  self.flags % 2 == 1
}

///|
pub fn TraceFlags::with_sampled(
  self : TraceFlags,
  sampled : Bool,
) -> TraceFlags {
  match (self.is_sampled(), sampled) {
    (true, false) => { flags: self.flags - 1 }
    (false, true) => { flags: self.flags + 1 }
    _ => self
  }
}

///|
pub fn TraceFlags::to_int(self : TraceFlags) -> Int {
  self.flags
}

///|
pub fn TraceFlags::to_hex(self : TraceFlags) -> String {
  hex_digit(self.flags / 16) + hex_digit(self.flags % 16)
}

///|
fn traceparent_flags_hex(flags : TraceFlags) -> String {
  if flags.is_sampled() {
    "01"
  } else {
    "00"
  }
}

///|
fn traceparent_flags(flags : TraceFlags) -> TraceFlags {
  if flags.is_sampled() {
    TraceFlags::sampled()
  } else {
    TraceFlags::unsampled()
  }
}

///|
pub impl Show for TraceFlags with fn output(self, logger) {
  logger.write_string(self.to_hex())
}

///|
fn is_trace_state_key_start(unit : Int, allow_digit : Bool) -> Bool {
  (unit >= 97 && unit <= 122) || (allow_digit && unit >= 48 && unit <= 57)
}

///|
fn is_trace_state_key_rest(unit : Int) -> Bool {
  (unit >= 97 && unit <= 122) ||
  (unit >= 48 && unit <= 57) ||
  unit == 95 ||
  unit == 45 ||
  unit == 42 ||
  unit == 47
}

///|
fn validate_key_part(
  value : String,
  max_len : Int,
  allow_digit_start : Bool,
) -> Bool {
  guard !value.is_empty() && value.length() <= max_len else { return false }
  for i in 0.. Bool {
  guard !key.is_empty() && key.length() <= 256 else { return false }
  match key.split_once("@") {
    Some((tenant_view, system_view)) => {
      let tenant = tenant_view.to_owned()
      let system = system_view.to_owned()
      if system.contains("@") {
        false
      } else {
        validate_key_part(tenant, 241, true) &&
        validate_key_part(system, 14, false)
      }
    }
    None => validate_key_part(key, 256, true)
  }
}

///|
fn validate_trace_state_value(value : String) -> Bool {
  guard !value.is_empty() && value.length() <= 256 else { return false }
  for i in 0.. 126 || unit == 44 || unit == 61 {
      return false
    }
  }
  value.code_unit_at(value.length() - 1).to_int() != 32
}

///|
fn validate_trace_state_entry(
  key : String,
  value : String,
) -> Result[TraceStateEntry, TraceContextError] {
  if !validate_trace_state_key(key) {
    Result::Err(InvalidTraceState("invalid key: " + key))
  } else if !validate_trace_state_value(value) {
    Result::Err(InvalidTraceState("invalid value for key: " + key))
  } else {
    Result::Ok({ key, value })
  }
}

///|
pub fn trace_state_entry(
  key : String,
  value : String,
) -> Result[TraceStateEntry, TraceContextError] {
  validate_trace_state_entry(key, value)
}

///|
fn append_trace_state_entry(
  entries : Array[TraceStateEntry],
  entry : TraceStateEntry,
) -> Array[TraceStateEntry] {
  let next : Array[TraceStateEntry] = []
  entries.each(fn(existing) {
    if existing.key != entry.key {
      next.push(existing)
    }
  })
  next.push(entry)
  next
}

///|
fn prepend_trace_state_entry(
  entries : Array[TraceStateEntry],
  entry : TraceStateEntry,
) -> Array[TraceStateEntry] {
  let next : Array[TraceStateEntry] = [entry]
  entries.each(fn(existing) {
    if existing.key != entry.key {
      next.push(existing)
    }
  })
  next
}

///|
pub fn TraceState::empty() -> TraceState {
  { entries: [] }
}

///|
pub fn TraceState::from_entries(
  entries : Array[TraceStateEntry],
) -> Result[TraceState, TraceContextError] {
  if entries.length() > 32 {
    return Result::Err(InvalidTraceState("expected at most 32 entries"))
  }
  let mut normalized : Array[TraceStateEntry] = []
  for i in 0..
        normalized = append_trace_state_entry(normalized, entry)
      Result::Err(err) => return Result::Err(err)
    }
  }
  Result::Ok({ entries: normalized })
}

///|
pub fn trace_state(
  entries : Array[TraceStateEntry],
) -> Result[TraceState, TraceContextError] {
  TraceState::from_entries(entries)
}

///|
pub fn TraceState::len(self : TraceState) -> Int {
  self.entries.length()
}

///|
pub fn TraceState::is_empty(self : TraceState) -> Bool {
  self.entries.is_empty()
}

///|
pub fn TraceState::get(self : TraceState, key : String) -> String? {
  for i in 0.. Result[TraceState, TraceContextError] {
  match validate_trace_state_entry(key, value) {
    Result::Ok(entry) => {
      let entries = prepend_trace_state_entry(self.entries, entry)
      if entries.length() > 32 {
        Result::Err(InvalidTraceState("expected at most 32 entries"))
      } else {
        Result::Ok({ entries, })
      }
    }
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn TraceState::to_header(self : TraceState) -> String {
  let buf = StringBuilder()
  for i in 0.. 0 {
      buf.write_string(",")
    }
    buf.write_string(self.entries[i].key)
    buf.write_string("=")
    buf.write_string(self.entries[i].value)
  }
  buf.to_string()
}

///|
pub fn format_tracestate(state : TraceState) -> String {
  state.to_header()
}

///|
fn copy_trace_state(state : TraceState) -> TraceState {
  { entries: state.entries.copy() }
}

///|
pub fn parse_tracestate(
  header : String,
) -> Result[TraceState, TraceContextError] {
  if header.length() > TRACESTATE_MAX_LENGTH {
    return Result::Err(InvalidTraceState("header exceeds 512 characters"))
  }
  let parsed_entries : Array[TraceStateEntry] = []
  let seen : Map[String, Bool] = Map([])
  let mut non_empty_count = 0
  for part in header.split(",") {
    let list_member = part.trim(chars=" \t")
    if !list_member.is_empty() {
      non_empty_count = non_empty_count + 1
      if non_empty_count > 32 {
        return Result::Err(InvalidTraceState("expected at most 32 entries"))
      }
      match list_member.split_once("=") {
        Some((key_view, value_view)) => {
          let key = key_view.to_owned()
          let value = value_view.to_owned()
          match validate_trace_state_entry(key, value) {
            Result::Ok(entry) => {
              if seen.contains(key) {
                return Result::Err(InvalidTraceState("duplicate key: " + key))
              }
              seen.set(key, true)
              parsed_entries.push(entry)
            }
            Result::Err(err) => return Result::Err(err)
          }
        }
        None =>
          return Result::Err(
            InvalidTraceState("expected key=value list member"),
          )
      }
    }
  }
  Result::Ok({ entries: parsed_entries })
}

///|
pub fn span_context(
  trace_id : TraceId,
  span_id : SpanId,
  flags? : TraceFlags = { flags: 0 },
  trace_state? : TraceState = { entries: [] },
  is_remote? : Bool = false,
) -> SpanContext {
  {
    trace_id: trace_id.value,
    span_id: span_id.value,
    flags,
    trace_state: copy_trace_state(trace_state),
    is_remote,
  }
}

///|
pub fn span_context_from_ids(
  trace_id : String,
  span_id : String,
  flags? : TraceFlags = { flags: 0 },
  trace_state? : TraceState = { entries: [] },
  is_remote? : Bool = false,
) -> Result[SpanContext, TraceContextError] {
  match TraceId::parse(trace_id) {
    Result::Ok(tid) =>
      match SpanId::parse(span_id) {
        Result::Ok(sid) =>
          Result::Ok(span_context(tid, sid, flags~, trace_state~, is_remote~))
        Result::Err(err) => Result::Err(err)
      }
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn SpanContext::is_sampled(self : SpanContext) -> Bool {
  self.flags.is_sampled()
}

///|
pub fn SpanContext::is_remote(self : SpanContext) -> Bool {
  self.is_remote
}

///|
pub fn SpanContext::trace_flags_int(self : SpanContext) -> Int {
  self.flags.to_int()
}

///|
pub fn SpanContext::traceparent(self : SpanContext) -> String {
  format_traceparent(self)
}

///|
pub fn format_traceparent(ctx : SpanContext) -> String {
  "00-" +
  ctx.trace_id +
  "-" +
  ctx.span_id +
  "-" +
  traceparent_flags_hex(ctx.flags)
}

///|
pub fn parse_traceparent(
  header : String,
) -> Result[SpanContext, TraceContextError] {
  let length = header.length()
  if length < 55 {
    return Result::Err(InvalidTraceParent("expected at least 55 characters"))
  }
  if length > TRACEPARENT_MAX_LENGTH {
    return Result::Err(InvalidTraceParent("header exceeds 512 characters"))
  }
  if header.code_unit_at(2).to_int() != 45 ||
    header.code_unit_at(35).to_int() != 45 ||
    header.code_unit_at(52).to_int() != 45 {
    return Result::Err(InvalidTraceParent("invalid field separators"))
  }
  let version = header[0:2].to_owned()
  if !is_lower_hex_string(version, 2) {
    return Result::Err(InvalidTraceParent("invalid version"))
  }
  if version == "ff" {
    return Result::Err(InvalidTraceParent("version ff is reserved"))
  }
  if version == "00" && length != 55 {
    return Result::Err(
      InvalidTraceParent("version 00 cannot contain extra fields"),
    )
  }
  if version != "00" && length > 55 && header.code_unit_at(55).to_int() != 45 {
    return Result::Err(InvalidTraceParent("invalid extension separator"))
  }
  let trace_id = header[3:35].to_owned()
  let span_id = header[36:52].to_owned()
  let trace_flags = header[53:55].to_owned()
  match TraceId::parse(trace_id) {
    Result::Ok(tid) =>
      match SpanId::parse(span_id) {
        Result::Ok(sid) =>
          match TraceFlags::from_hex(trace_flags) {
            Result::Ok(flags) =>
              Result::Ok(
                span_context(
                  tid,
                  sid,
                  flags=traceparent_flags(flags),
                  trace_state=TraceState::empty(),
                  is_remote=true,
                ),
              )
            Result::Err(err) => Result::Err(err)
          }
        Result::Err(err) => Result::Err(err)
      }
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn parse_span_context(
  traceparent : String,
  tracestate? : String = "",
) -> Result[SpanContext, TraceContextError] {
  match parse_traceparent(traceparent) {
    Result::Ok(ctx) => {
      let state = match parse_tracestate(tracestate) {
        Result::Ok(state) => state
        Result::Err(_) => TraceState::empty()
      }
      Result::Ok({
        trace_id: ctx.trace_id,
        span_id: ctx.span_id,
        flags: ctx.flags,
        trace_state: state,
        is_remote: true,
      })
    }
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn span_trace_flags(span : Span) -> TraceFlags {
  match TraceFlags::from_int(span.trace_flags) {
    Result::Ok(flags) => flags
    Result::Err(_) => TraceFlags::unsampled()
  }
}

///|
pub fn span_trace_state(span : Span) -> TraceState {
  copy_trace_state(span.trace_state)
}

///|
pub fn span_context_from_span(
  span : Span,
) -> Result[SpanContext, TraceContextError] {
  match
    span_context_from_ids(
      span.trace_id,
      span.span_id,
      flags=span_trace_flags(span),
      trace_state=span_trace_state(span),
      is_remote=false,
    ) {
    Result::Ok(ctx) => Result::Ok(ctx)
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn traceparent_from_span(span : Span) -> Result[String, TraceContextError] {
  match span_context_from_span(span) {
    Result::Ok(ctx) => Result::Ok(format_traceparent(ctx))
    Result::Err(err) => Result::Err(err)
  }
}

///|
pub fn span_from_remote_context(
  ctx : SpanContext,
  name : String,
  fields? : Array[Field] = [],
  kind? : SpanKind = Server,
) -> Span {
  let s = span_with_ids_unnotified(
    name,
    fields,
    ctx.trace_id,
    ctx.span_id,
    kind,
    trace_flags=ctx.flags.to_int(),
    trace_state=ctx.trace_state,
  )
  notify_span_created(s)
  notify_span_linked(s, {
    trace_id: ctx.trace_id,
    span_id: ctx.span_id,
    kind: SpanChildOf,
    fields: [],
    trace_state: ctx.trace_state.to_header(),
  })
  s
}