// SpanBuilder helps construct a Span with various options

///|
pub struct SpanBuilder {
  priv name : String
  priv kind : SpanKind
  priv parent_context : SpanContext?
  priv attributes : Map[String, AttributeValue]
  priv start_time : Int64?
  priv resource : Resource
  priv tracer_identifier : TracerIdentifier
  priv tracer_options : TracerOptions
}

// Create a new SpanBuilder

///|
pub fn SpanBuilder::new(
  name : String,
  resource : Resource,
  tracer_identifier : TracerIdentifier,
  tracer_options : TracerOptions,
) -> SpanBuilder {
  {
    name,
    kind: SpanKind::Internal,
    parent_context: None,
    attributes: Map::new(),
    start_time: None,
    resource,
    tracer_identifier,
    tracer_options,
  }
}

// Set the SpanKind

///|
pub fn SpanBuilder::kind(self : SpanBuilder, kind : SpanKind) -> SpanBuilder {
  { ..self, kind, }
}

// Set the parent SpanContext

///|
pub fn SpanBuilder::parent(
  self : SpanBuilder,
  parent : &SpanContextExtractable,
) -> SpanBuilder {
  { ..self, parent_context: Some(parent.as_span_context()) }
}

///|
pub trait SpanContextExtractable {
  as_span_context(Self) -> SpanContext
}

///|
pub impl SpanContextExtractable for SpanContext with as_span_context(
  self : SpanContext,
) -> SpanContext {
  self
}

///|
pub impl SpanContextExtractable for Span with as_span_context(self : Span) -> SpanContext {
  self.context()
}

// Add an attribute

///|
pub fn SpanBuilder::attribute(
  self : SpanBuilder,
  key : String,
  value : AttributeValue,
) -> SpanBuilder {
  let new_attributes = self.attributes.copy()
  new_attributes.set(key, value)
  { ..self, attributes: new_attributes }
}

///|
pub fn SpanBuilder::attributes(
  self : SpanBuilder,
  attributes : Attributes,
) -> SpanBuilder {
  let new_attributes = self.attributes.merge(attributes.map)
  { ..self, attributes: new_attributes }
}

// Set the start time

///|
pub fn SpanBuilder::start_time_ns(
  self : SpanBuilder,
  start_time : Int64,
) -> SpanBuilder {
  { ..self, start_time: Some(start_time) }
}

// Build the Span using the Tracer

///|
pub fn SpanBuilder::start(self : SpanBuilder) -> Span {
  // Generate new trace and span IDs using tracer's config generators
  let trace_id = match self.parent_context {
    Some(parent) => parent.trace_id
    None => (self.tracer_options.trace_id_generator)()
  }
  let span_id = (self.tracer_options.span_id_generator)()
  let parent_span_id = match self.parent_context {
    Some(parent) => Some(parent.span_id)
    None => None
  }

  // Inherit trace flags from parent or default to sampled
  let trace_flags = match self.parent_context {
    Some(parent) => parent.trace_flags
    None => TraceFlags::sampled() // Sampled by default
  }
  let context = SpanContext::new(trace_id, span_id, trace_flags, false)
  let start_time = match self.start_time {
    Some(t) => t
    None => (self.tracer_options.get_current_time_nanos)()
  }
  let mut span = Span::new(
    context,
    self.name,
    self.kind,
    start_time,
    self.resource,
    self.tracer_identifier,
    parent_span_id,
    on_end=self.tracer_options.span_on_end,
  )

  // Add all attributes from the builder
  self.attributes.each(fn(key, value) {
    span = { ..span, attributes: span.attributes.set(key, value) }
  })
  span
}