///|
/// SplitMix64 (Steele et al.): one round mixes a 64-bit counter into a
/// well-distributed 64-bit value. Trace and span ids are derived from a request
/// counter through this so they look random and collide rarely, while staying
/// deterministic for tests. A production deployment swaps the counter for a
/// CSPRNG at the edge; the id shape is unchanged.
fn splitmix64(x : UInt64) -> UInt64 {
  let mut z = x + 0x9E3779B97F4A7C15UL
  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL
  z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL
  z ^ (z >> 31)
}

///|
/// The 16 lowercase hex digits of a 64-bit value, most-significant nibble first.
fn hex16(v : UInt64) -> String {
  let digits = "0123456789abcdef"
  let sb = StringBuilder::new()
  for i = 15; i >= 0; i = i - 1 {
    let nibble = ((v >> (i * 4)) & 0xFUL).to_int()
    sb.write_char(digits[nibble].to_int().unsafe_to_char())
  }
  sb.to_string()
}

///|
/// A 32-hex-char (128-bit) trace id from `seed`, mixing two independent words.
pub fn generate_trace_id(seed : Int64) -> String {
  let s = seed.reinterpret_as_uint64()
  hex16(splitmix64(s * 2UL)) + hex16(splitmix64(s * 2UL + 1UL))
}

///|
/// A 16-hex-char (64-bit) span id from `seed`.
pub fn generate_span_id(seed : Int64) -> String {
  hex16(splitmix64(seed.reinterpret_as_uint64() * 2UL + 2UL))
}

///|
/// A W3C Trace Context (← go-zero's OpenTelemetry propagation): the 128-bit trace
/// id shared across a request's whole call tree, the 64-bit span id of the current
/// hop, and the 8-bit sampling flags.
pub(all) struct TraceContext {
  trace_id : String
  span_id : String
  flags : Int
}

///|
/// Format as a W3C `traceparent` header value:
/// `00-<32 hex trace-id>-<16 hex span-id>-<2 hex flags>`.
pub fn TraceContext::to_traceparent(self : TraceContext) -> String {
  "00-" + self.trace_id + "-" + self.span_id + "-" + two_hex(self.flags)
}

///|
/// The trace id (the value propagated unchanged down the call tree).
pub fn TraceContext::trace_id(self : TraceContext) -> String {
  self.trace_id
}

///|
/// The span id of this hop.
pub fn TraceContext::span_id(self : TraceContext) -> String {
  self.span_id
}

///|
/// A two-hex-digit rendering of a byte (the `traceparent` version/flags fields).
fn two_hex(n : Int) -> String {
  let digits = "0123456789abcdef"
  let hi = digits[(n >> 4) & 0xF].to_int().unsafe_to_char()
  let lo = digits[n & 0xF].to_int().unsafe_to_char()
  let sb = StringBuilder::new()
  sb.write_char(hi)
  sb.write_char(lo)
  sb.to_string()
}

///|
/// Whether `s` is exactly `n` lowercase- or uppercase-hex digits.
fn is_hex(s : String, n : Int) -> Bool {
  if s.length() != n {
    return false
  }
  for i = 0; i < s.length(); i = i + 1 {
    let c = s[i].to_int()
    let is_digit = c >= 0x30 && c <= 0x39
    let is_lower = c >= 0x61 && c <= 0x66
    let is_upper = c >= 0x41 && c <= 0x46
    if !(is_digit || is_lower || is_upper) {
      return false
    }
  }
  true
}

///|
/// Parse a W3C `traceparent` value, or `None` if it is malformed. Only the four
/// canonical fields with correct lengths are accepted; the flags default to `0`
/// if unparseable.
pub fn parse_traceparent(value : String) -> TraceContext? {
  let parts = split_char(value, '-')
  if parts.length() != 4 {
    return None
  }
  if !(is_hex(parts[0], 2) && is_hex(parts[1], 32) && is_hex(parts[2], 16)) {
    return None
  }
  if !is_hex(parts[3], 2) {
    return None
  }
  Some({ trace_id: parts[1], span_id: parts[2], flags: hex_byte(parts[3]) })
}

///|
/// Parse a two-hex-digit string as a byte (`0` on a bad digit).
fn hex_byte(s : String) -> Int {
  if s.length() != 2 {
    return 0
  }
  (hex_digit(s[0].to_int()) << 4) | hex_digit(s[1].to_int())
}

///|
/// A single hex digit's value (`0` on a non-hex code unit).
fn hex_digit(c : Int) -> Int {
  if c >= 0x30 && c <= 0x39 {
    c - 0x30
  } else if c >= 0x61 && c <= 0x66 {
    c - 0x61 + 10
  } else if c >= 0x41 && c <= 0x46 {
    c - 0x41 + 10
  } else {
    0
  }
}

///|
/// Derive the outgoing trace context for a request: reuse the inbound
/// `traceparent`'s trace id if the client sent a valid one (continuing the
/// distributed trace), else start a new trace, and always mint a fresh child span
/// id from `seed`. This is the propagation decision, pulled out as a pure function
/// so it is testable without the transport.
pub fn next_trace_context(inbound : String?, seed : Int64) -> TraceContext {
  let span = generate_span_id(seed)
  match inbound {
    Some(v) =>
      match parse_traceparent(v) {
        Some(parent) => { trace_id: parent.trace_id, span_id: span, flags: 1 }
        None => { trace_id: generate_trace_id(seed), span_id: span, flags: 1 }
      }
    None => { trace_id: generate_trace_id(seed), span_id: span, flags: 1 }
  }
}

///|
/// Trace-id propagation middleware (← go-zero's `trace` handler): continue the
/// inbound `traceparent` trace or start a new one, mint a child span, and stamp
/// both `traceparent` and a convenience `x-trace-id` onto the response so the id
/// flows to the client and downstream calls. The per-assembly seed counter keeps
/// span ids distinct across the requests this layer serves. Non-HTTP scopes pass
/// through untraced.
pub fn tracing(header? : String = "x-trace-id") -> Middleware {
  let counter : Ref[Int64] = { val: 0L }
  inner => {
    (scope, receive, send) => {
      match scope {
        Http(_) => {
          counter.val = counter.val + 1L
          let inbound = scope_header(scope, "traceparent")
          let ctx = next_trace_context(inbound, counter.val)
          let wrapped : @moonasgi.Send = event => {
            match event {
              HttpResponseStart(status~, headers~, trailers~) =>
                send(
                  @moonasgi.Event::HttpResponseStart(
                    status~,
                    headers=[
                      (header, ctx.trace_id),
                      ("traceparent", ctx.to_traceparent()),
                      ..headers,
                    ],
                    trailers~,
                  ),
                )
              other => send(other)
            }
          }
          inner(scope, receive, wrapped)
        }
        _ => inner(scope, receive, send)
      }
    }
  }
}