// SpanExporter trait: Spanを外部に出力するためのインターフェース

///|
pub(open) trait SpanExporter {
  // Spanのバッチをエクスポート
  async export_spans(Self, Array[Span]) -> Unit

  // Exporterをシャットダウン
  async shutdown(Self) -> Unit
  enable_batch_export(self : Self, options? : BatchExporterOptions) -> BatchExporter = _
}

///|
impl SpanExporter with enable_batch_export(
  self : Self,
  options? : BatchExporterOptions = Default::default(),
) -> BatchExporter {
  BatchExporter::new(self, options~)
}

// ConsoleExporter: Spanを指定された出力関数で出力

///|
pub struct PrettyPrintExporter {
  print_fn : (String) -> Unit // カスタム出力関数
}

///|
pub impl SpanExporter for PrettyPrintExporter with export_spans(
  self : PrettyPrintExporter,
  spans : Array[Span],
) -> Unit {
  self.export_spans(spans)
}

///|
pub impl SpanExporter for PrettyPrintExporter with shutdown(
  self : PrettyPrintExporter,
) -> Unit {
  self.shutdown()
}

// ConsoleExporterの作成

///|
pub fn PrettyPrintExporter::new(
  print : (String) -> Unit,
) -> PrettyPrintExporter {
  { print_fn: print }
}

// ConsoleExporterにSpanExporter traitを実装

///|
pub fn PrettyPrintExporter::export_spans(
  self : PrettyPrintExporter,
  spans : Array[Span],
) -> Unit {
  for span in spans {
    (self.print_fn)(format_span(span))
  }
}

///|
pub fn PrettyPrintExporter::shutdown(self : PrettyPrintExporter) -> Unit {
  (self.print_fn)("ConsoleExporter shutdown")
}

// Spanを人間が読みやすい形式にフォーマット

///|
fn format_span(span : Span) -> String {
  let mut output = "\n"
  output = output + "Span: " + span.name + "\n"
  output = output + "  TraceID: " + span.traceId.to_string() + "\n"
  output = output + "  SpanID: " + span.spanId.to_string() + "\n"

  // 親SpanIDの表示
  match span.parent_span_id {
    Some(parent_id) =>
      output = output + "  ParentSpanID: " + parent_id.to_string() + "\n"
    None => ()
  }
  output = output + "  Kind: " + span.kind.to_string() + "\n"
  output = output + "  Status: " + span.status.to_string() + "\n"
  output = output +
    "  StartTime: " +
    span.start_time_unix_nano.to_string() +
    "ns\n"

  // 期間の表示
  match span.duration() {
    Some(d) => output = output + "  Duration: " + d.to_string() + "ns\n"
    None => output = output + "  Duration: (not ended)\n"
  }

  // 属性の表示
  if not(span.attributes.is_empty()) {
    output = output + "  Attributes:\n"
    span.attributes.each(fn(key, value) {
      output = output +
        "    " +
        key +
        ": " +
        format_attribute_value(value) +
        "\n"
    })
  }
  if not(span.links.is_empty()) {
    output = output + "  Links:\n"
    for link in span.links {
      output = output +
        "    TraceID: " +
        link.trace_id.to_string() +
        ", SpanID: " +
        link.span_id.to_string() +
        "\n"
      if not(link.attributes.is_empty()) {
        output = output + "      Attributes:\n"
        link.attributes.each(fn(key, value) {
          output = output +
            "        " +
            key +
            ": " +
            format_attribute_value(value) +
            "\n"
        })
      }
    }
  }

  // イベントの表示
  if span.events.length() > 0 {
    output = output + "  Events:\n"
    for event in span.events {
      output = output +
        "    " +
        event.name +
        " @ " +
        event.timestamp.to_string() +
        "ns\n"
      if not(event.attributes.is_empty()) {
        event.attributes.each(fn(key, value) {
          output = output +
            "      " +
            key +
            ": " +
            format_attribute_value(value) +
            "\n"
        })
      }
    }
  }
  output
}

// AttributeValueを文字列にフォーマット

///|
fn format_attribute_value(value : AttributeValue) -> String {
  match value {
    AttributeValue::String(s) => "\"" + s + "\""
    AttributeValue::Int(i) => i.to_string()
    AttributeValue::Bool(b) => b.to_string()
    AttributeValue::Double(d) => d.to_string()
  }
}

///|
pub struct BatchExporterOptions {
  max_queue_size : Int
  schedule_delay_millis : Int
  export_timeout_millis : Int
  max_export_batch_size : Int
  set_timeout : async (Int, async () -> Unit noraise) -> Unit
  logger_factory : () -> InternalLogger
}

///|
pub struct BatchExporter {
  priv exporter : &SpanExporter
  priv options : BatchExporterOptions
  priv export_waiting_spans : Array[Span]
  priv sending_queue : @async.Queue[Unit]
  priv schedule_queue : @async.Queue[Unit]
}

///|
pub impl Default for BatchExporterOptions with default() -> BatchExporterOptions {
  {
    max_queue_size: DefaultMaxQueueSize,
    schedule_delay_millis: DefaultScheduleDelay,
    export_timeout_millis: DefaultExportTimeout,
    max_export_batch_size: DefaultMaxExportBatchSize,
    set_timeout: async fn(duration, callback) {
      @async.sleep(duration)
      callback()
    },
    logger_factory: () => InternalLogger::none(),
  }
}

///|
pub fn BatchExporterOptions::enable_debug_log(
  self : BatchExporterOptions,
  print : (&Show) -> Unit,
) -> BatchExporterOptions {
  return BatchExporterOptions::{
    ..self,
    logger_factory: () => InternalLogger::{ print_fn: print },
  }
}

///|
pub fn BatchExporterOptions::with_max_queue_size(
  self : BatchExporterOptions,
  size : Int,
) -> BatchExporterOptions {
  { ..self, max_queue_size: size }
}

///|
pub fn BatchExporterOptions::with_schedule_delay_millis(
  self : BatchExporterOptions,
  millis : Int,
) -> BatchExporterOptions {
  { ..self, schedule_delay_millis: millis }
}

///|
pub fn BatchExporterOptions::with_export_timeout_millis(
  self : BatchExporterOptions,
  millis : Int,
) -> BatchExporterOptions {
  { ..self, export_timeout_millis: millis }
}

///|
pub fn BatchExporterOptions::with_max_export_batch_size(
  self : BatchExporterOptions,
  size : Int,
) -> BatchExporterOptions {
  { ..self, max_export_batch_size: size }
}

///|
pub fn BatchExporterOptions::with_set_timeout(
  self : BatchExporterOptions,
  set_timeout : async (Int, async () -> Unit noraise) -> Unit,
) -> BatchExporterOptions {
  { ..self, set_timeout, }
}

///|
pub fn BatchExporter::new(
  exporter : &SpanExporter,
  options? : BatchExporterOptions = BatchExporterOptions::default(),
) -> BatchExporter {
  {
    exporter,
    options,
    export_waiting_spans: [],
    sending_queue: @async.Queue::new(kind=@aqueue.Kind::Blocking(1)),
    schedule_queue: @async.Queue::new(kind=@aqueue.Kind::Unbounded),
  }
}

///|
pub async fn BatchExporter::start(self : BatchExporter) -> Unit {
  @async.with_task_group(fn(group) {
    group.spawn_loop(async fn() {
      self.schedule_queue.get()
      self.schedule_flush() catch {
        e =>
          (self.options.logger_factory)().log(
            "[BatchExporter] Failed to flush spans: " + e.to_string(),
          )
      }
      IterContinue
    })
  })
}
// DefaultMaxQueueSize is the maximum number of spans that can be queued for export.

///|
pub impl SpanExporter for BatchExporter with export_spans(
  self : BatchExporter,
  spans : Array[Span],
) -> Unit {
  self.export_waiting_spans.push_iter(spans.iter())
  self.schedule_queue.put(())
  // self.schedule_flush()
}

///|
pub impl SpanExporter for BatchExporter with shutdown(self : BatchExporter) -> Unit {
  self.shutdown()
}

///|
async fn BatchExporter::shutdown(self : BatchExporter) -> Unit {
  self.flush()
  self.exporter.shutdown()
}

///|
async fn BatchExporter::flush(self : BatchExporter) -> Unit {
  self.sending_queue.put(())
  let count_of_sending = @cmp.minimum(
    self.export_waiting_spans.length(),
    self.options.max_export_batch_size,
  )
  let sending_spans = self.export_waiting_spans.drain(0, count_of_sending)
  self.exporter.export_spans(sending_spans)
  self.sending_queue.get()
  ()
}

///|
async fn BatchExporter::schedule_flush(self : BatchExporter) -> Unit {
  if self.export_waiting_spans.is_empty() {
    (self.options.logger_factory)().log(
      "[BatchExporter] No spans to export, skipping flush.",
    )
    return
  }
  if self.export_waiting_spans.length() >= self.options.max_export_batch_size {
    self.flush()
  } else {
    (self.options.set_timeout)(self.options.schedule_delay_millis, async fn() -> Unit noraise {
      self.flush() catch {
        e =>
          (self.options.logger_factory)().log(
            "[BatchExporter] Failed to flush spans: " + e.to_string(),
          )
      }
    })
  }
}