/// TODO: 適切ファイルへ切り出し移動

// OTLP JSON serialization functions for converting MoonBit span structures
// to OpenTelemetry Protocol JSON format

// Helper structure for OTLP resourceSpans

///|
pub struct OtlpResourceSpans {
  resource_spans : Array[OtlpResourceSpan]
} derive(ToJson(rename_fields="camelCase"))

///|
fn OtlpResourceSpans::to_protobuf(
  self : OtlpResourceSpans,
) -> @trace.TracesData {
  let otlp_td = @trace.TracesData::default()
  otlp_td.resource_spans = []
  for rs in self.resource_spans {
    otlp_td.resource_spans.push(rs.to_protobuf())
  }
  otlp_td
}

///|
pub fn OtlpResourceSpans::from_spans(spans : Array[Span]) -> OtlpResourceSpans {
  if spans.length() == 0 {
    return { resource_spans: [] }
  }
  let spans_grouped_by_resource : Map[Resource, Array[Span]] = Map::new()
  for span in spans {
    let resource = span.resource
    if !spans_grouped_by_resource.contains(resource) {
      spans_grouped_by_resource[resource] = []
    }
    spans_grouped_by_resource[resource].push(span)
  }
  let spans_grouped_by_tracer_identifier : Map[
    Resource,
    Map[TracerIdentifier, Array[Span]],
  ] = Map::new()
  for rs in spans_grouped_by_resource {
    let (resource, spans) = rs
    let tracer_map : Map[TracerIdentifier, Array[Span]] = Map::new()
    for span in spans {
      let tracer_id = span.tracer_identifier
      if !tracer_map.contains(tracer_id) {
        tracer_map[tracer_id] = []
      }
      tracer_map[tracer_id].push(span)
    }
    spans_grouped_by_tracer_identifier[resource] = tracer_map
  }
  let scope_spans_from_map = OtlpResourceSpans::from_map(
    spans_grouped_by_tracer_identifier,
  )
  scope_spans_from_map
}

///|
pub struct OtlpResourceSpan {
  resource : Resource
  scope_spans : Array[OtlpScopeSpan]
  schemaUrl : String?
} derive(ToJson(rename_fields="camelCase"))

///|
pub fn OtlpResourceSpans::from_map(
  map : Map[Resource, Map[TracerIdentifier, Array[Span]]],
) -> OtlpResourceSpans {
  let resource_spans : Array[OtlpResourceSpan] = []
  for rm in map {
    let (resource, tracer_map) = rm
    let scope_spans = OtlpResourceSpan::from_map(tracer_map)
    let resource_span : OtlpResourceSpan = {
      resource,
      scope_spans,
      schemaUrl: None,
    }
    resource_spans.push(resource_span)
  }
  OtlpResourceSpans::{ resource_spans, }
}

///|
pub struct OtlpScopeSpan {
  scope : OtlpScope
  spans : Array[Span]
  schema_url : String?
} derive(ToJson(rename_fields="camelCase"))

///|
fn OtlpResourceSpan::from_map(
  map : Map[TracerIdentifier, Array[Span]],
) -> Array[OtlpScopeSpan] {
  let scope_spans : Array[OtlpScopeSpan] = []
  for ti in map {
    let (tracer_id, spans) = ti
    let scope : OtlpScope = { name: tracer_id.name, version: tracer_id.version }
    let scope_span : OtlpScopeSpan = { scope, spans, schema_url: None }
    scope_spans.push(scope_span)
  }
  scope_spans
}

///|
fn OtlpResourceSpan::to_protobuf(
  self : OtlpResourceSpan,
) -> @trace.ResourceSpans {
  let otlp_rs = @trace.ResourceSpans::default()
  otlp_rs.schema_url = self.schemaUrl.unwrap_or("")
  otlp_rs.resource = self.resource.to_protobuf()
  otlp_rs.scope_spans = []
  for ss in self.scope_spans {
    let otlp_ss = @trace.ScopeSpans::default()
    otlp_ss.schema_url = ss.schema_url.unwrap_or("")
    otlp_ss.scope = @otel_common.InstrumentationScope::default()
    otlp_ss.scope.name = ss.scope.name
    otlp_ss.scope.version = ss.scope.version
    otlp_ss.spans = []
    for span in ss.spans {
      otlp_ss.spans.push(span.to_protobuf())
    }
    otlp_rs.scope_spans.push(otlp_ss)
  }
  otlp_rs
}

///|
pub struct OtlpScope {
  name : String
  version : String
} derive(ToJson)

// Serialize a single Span to OTLP JSON format using ToJson
// Serialize an array of Spans to OTLP resourceSpans JSON format

///|
pub fn spans_to_resource_spans_json(spans : Array[Span]) -> String {
  if spans.length() == 0 {
    return "{\"resourceSpans\":[]}"
  }
  let otlp = OtlpResourceSpans::from_spans(spans)
  otlp.to_json().stringify()
}

// Serialize an array of Spans to OTLP resourceSpans JSON format

///|
pub fn spans_to_resource_spans_protobuf_bytes(
  spans : Array[Span],
) -> Bytes raise {
  if spans.length() == 0 {
    return Bytes::new(0)
  }
  let otlp = OtlpResourceSpans::from_spans(spans)

  // Convert to protobuf TracesData
  let traces_data = otlp.to_protobuf()
  let buffer = @buffer.new()
  traces_data.write(buffer) catch {
    e =>
      raise Failure::Failure("Protobuf serialization failed: " + e.to_string())
  }
  buffer.to_bytes()
}