// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// A request through a traced application: the middleware reads the context the
/// request arrived with, opens a server span, and puts the context back on the
/// response.
///
///     moon run src/examples/02-asgi
///
/// The application is driven in process, the way a caller would drive it; a real
/// server binds the same `@spec.AsgiApp` to a socket instead.
#coverage.skip
async fn main {
  @async.pause()
  let recorder = @trace.Recorder::new()
  let tracer = @trace.Tracer::new(
    rand=random_bytes,
    sink=recorder,
    sampler=@trace.parent_based(root=@trace.always),
  )
  let layer = @asgi.Tracing::new(tracer~, now=clock())
  let app : @spec.AsgiApp = async fn(_, receive, send) {
    ignore(receive())
    send(
      @spec.HttpResponseStart(
        status=200,
        headers=[("content-type", "application/json")],
        trailers=false,
      ),
    )
    send(@spec.HttpResponseBody(body=b"{\"ok\":true}", more_body=false))
  }
  let traced = layer.wrap(app)

  let scope = @spec.Http(
    @spec.HttpScope::new(
      http_method="POST",
      path="/orders",
      headers=[
        (
          "traceparent", "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
        ),
        ("baggage", "userId=alice"),
      ],
      server=("shop.example.com", Some(8443)),
    ),
  )

  // A minimal server: one request in, whatever the application sends out.
  let handled = Ref(false)
  let receive : @spec.Receive = async fn() {
    @async.pause()
    if handled.val {
      @spec.HttpDisconnect
    } else {
      handled.val = true
      @spec.HttpRequest(body=b"", more_body=false)
    }
  }
  let send : @spec.Send = async fn(event) {
    @async.pause()
    match event {
      @spec.HttpResponseStart(status~, headers~, ..) => {
        println("response \{status}")
        for header in headers {
          println("  \{header.0}: \{header.1}")
        }
      }
      @spec.HttpResponseBody(body~, ..) =>
        println("body \{body.length()} octets")
      _ => ()
    }
  }
  traced(scope, receive, send)

  println("")
  for span in recorder.spans() {
    println("\{span.name}  \{span.duration()} ns  \{span.status}")
    for pair in span.attributes {
      println("  \{pair.0} = \{pair.1}")
    }
  }
}

///|
/// A clock that moves ten milliseconds per reading. A real service hands in its own
/// clock here; this one keeps the example's output the same on every run.
fn clock() -> () -> Int64 {
  let now = Ref(1_700_000_000_000_000_000L)
  () => {
    now.val = now.val + 10_000_000L
    now.val
  }
}

///|
fn random_bytes(width : Int32) -> Bytes {
  @moonseed.bytes(width, @system.source) catch {
    _ => abort("the platform has no entropy source")
  }
}