///|
pub(all) struct Span {
  name : String
  fields : Array[Field]
  trace_id : String
  span_id : String
  parent_span_id : String
  mut kind : SpanKind
  mut start_time : UInt64
  mut end_time : UInt64
  mut active : Bool
  mut status : SpanStatus
  mut status_message : String
}

///|
pub fn span(
  name : String,
  fields? : Array[Field] = [],
  kind? : SpanKind = Internal,
) -> Span {
  {
    name,
    fields,
    trace_id: next_trace_id(),
    span_id: next_span_id(),
    parent_span_id: "",
    kind,
    start_time: @env.now(),
    end_time: (0 : UInt64),
    active: false,
    status: Unset,
    status_message: "",
  }
}

///|
pub fn span_with_trace(
  name : String,
  trace_id : String,
  fields? : Array[Field] = [],
  kind? : SpanKind = Internal,
) -> Span {
  {
    name,
    fields,
    trace_id,
    span_id: next_span_id(),
    parent_span_id: "",
    kind,
    start_time: @env.now(),
    end_time: (0 : UInt64),
    active: false,
    status: Unset,
    status_message: "",
  }
}

///|
fn span_with_ids(
  name : String,
  fields : Array[Field],
  trace_id : String,
  parent_span_id : String,
  kind : SpanKind,
) -> Span {
  {
    name,
    fields,
    trace_id,
    span_id: next_span_id(),
    parent_span_id,
    kind,
    start_time: @env.now(),
    end_time: (0 : UInt64),
    active: false,
    status: Unset,
    status_message: "",
  }
}

///|
pub fn Span::enter(self : Span) -> Unit {
  if self.active {
    return
  }
  self.start_time = @env.now()
  self.end_time = (0 : UInt64)
  self.active = true
  let enter_fields = self.fields.copy()
  emit(Trace, "span.enter \{self.name}", enter_fields)
}

///|
pub fn Span::exit(self : Span) -> Unit {
  if !self.active {
    return
  }
  self.end_time = @env.now()
  self.active = false
  let duration = self.end_time - self.start_time
  let exit_fields = self.fields.copy()
  exit_fields.push({ key: "duration_ns", value: duration.to_json() })
  if self.status != Unset {
    exit_fields.push({ key: "status", value: self.status.to_json() })
    if !self.status_message.is_empty() {
      exit_fields.push({
        key: "status_message",
        value: self.status_message.to_json(),
      })
    }
  }
  emit(Trace, "span.exit \{self.name}", exit_fields)
}

///|
pub fn Span::set_status(
  self : Span,
  status : SpanStatus,
  message? : String? = None,
) -> Unit {
  self.status = status
  match message {
    Some(m) => self.status_message = m
    None => if status != SpanError { self.status_message = "" }
  }
}

///|
pub fn Span::record_error(self : Span, msg : String) -> Unit {
  self.status = SpanError
  self.status_message = msg
  self.fields.push({ key: "error", value: msg.to_json() })
}

///|
pub fn Span::is_active(self : Span) -> Bool {
  self.active
}

///|
pub fn Span::duration(self : Span) -> UInt64 {
  if self.end_time > self.start_time {
    self.end_time - self.start_time
  } else {
    (0 : UInt64)
  }
}