///|
/// Register a zRPC service with all four cardinalities and drive real gRPC calls
/// through the in-process h2c channel — unary, server-streaming, client-streaming,
/// and bidirectional — plus the trailers-only UNIMPLEMENTED an unknown method
/// answers with.
///
///     moon run src/examples/07-zrpc
#coverage.skip
fn main {
  try {
    // Config loads from the same YAML go-zero's zrpc.RpcServerConf reads.
    let conf = @moonzero.RpcServerConf::from_yaml("name: greeter\nport: 9090\n")
    let rpc = @moonzero.RpcServer::new(conf)
    let g = rpc.group("hello.Greeter")
    g.register("SayHello", req => enc("hello " + dec(req)))
    g.register_server_streaming("Tail", req => {
      [enc("chunk-1: " + dec(req)), enc("chunk-2: " + dec(req))]
    })
    g.register_client_streaming("Upload", msgs => {
      enc("received " + msgs.length().to_string() + " parts")
    })
    g.register_bidi_streaming("Chat", () => @moonzero.BidiStreamHandler::{
      on_message: m => [enc("re: " + dec(m))],
      on_end: () => [enc("bye")],
    })
    println("service " + rpc.conf().name + " methods: " + methods_of(rpc))
    println(
      "has SayHello: " + rpc.has_method("/hello.Greeter/SayHello").to_string(),
    )

    // dispatch is the transport-free view of the unary registry.
    println(
      "dispatch SayHello -> " +
      unary(rpc.dispatch("/hello.Greeter/SayHello", enc("qwe"))),
    )
    println(
      "dispatch unknown -> " +
      unary(rpc.dispatch("/hello.Greeter/Nope", enc("x"))),
    )

    // Real h2c round-trips over the channel.
    let ch = @moonzero.RpcChannel::connect(rpc)
    println(
      "unary  -> " + unary(ch.call("/hello.Greeter/SayHello", enc("lfanke"))),
    )
    println(
      "server -> " +
      stream(ch.call_server_streaming("/hello.Greeter/Tail", enc("emm"))),
    )
    println(
      "client -> " +
      unary(
        ch.call_client_streaming("/hello.Greeter/Upload", [
          enc("a"),
          enc("b"),
          enc("c"),
        ]),
      ),
    )

    // Interactive bidi: each send returns the replies produced right then; close_send
    // half-closes and runs on_end.
    let call = ch.open_bidi("/hello.Greeter/Chat")
    println("bidi send hhh -> " + join(call.send(enc("hhh")).map(dec)))
    println("bidi send qwq -> " + join(call.send(enc("qwq")).map(dec)))
    println("bidi close -> " + stream(call.close_send()))

    // One-shot bidi driving the whole exchange.
    println(
      "bidi one-shot -> " +
      stream(
        ch.call_bidi_streaming("/hello.Greeter/Chat", [enc("1"), enc("2")]),
      ),
    )

    // An unregistered path answers UNIMPLEMENTED — the trailers-only response a gRPC
    // server sends for an unknown method.
    println(
      "unknown path -> " + unary(ch.call("/hello.Greeter/Missing", enc("x"))),
    )
    println("status_of_code(12) = " + @moonzero.status_of_code(12).name())
  } catch {
    _ => println("unexpected transport error")
  }
}

///|
fn methods_of(rpc : @moonzero.RpcServer) -> String {
  let ms = rpc.methods()
  ms.sort()
  join(ms)
}

///|
fn unary(r : Result[Bytes, @moonrpc.Status]) -> String {
  match r {
    Ok(b) => "ok(" + dec(b) + ")"
    Err(s) => "err(" + s.name() + ")"
  }
}

///|
fn stream(r : Result[Array[Bytes], @moonrpc.Status]) -> String {
  match r {
    Ok(msgs) => "ok" + join(msgs.map(dec))
    Err(s) => "err(" + s.name() + ")"
  }
}

///|
fn enc(s : String) -> Bytes {
  @utf8.encode(s)
}

///|
fn dec(b : Bytes) -> String {
  @utf8.decode_lossy(b[:])
}

///|
fn join(items : Array[String]) -> String {
  let sb = StringBuilder()
  sb.write_char('[')
  for i = 0; i < items.length(); i = i + 1 {
    if i > 0 {
      sb.write_string(", ")
    }
    sb.write_string(items[i])
  }
  sb.write_char(']')
  sb.to_string()
}