// Server Reflection (`grpc.reflection.v1.ServerReflection`): a reflection client opens
// the bidi stream and sends a `ServerReflectionRequest` per query — `ListServices` to
// enumerate, `FileContainingSymbol` to fetch the descriptor for a symbol. This registers
// a real file descriptor, installs the service on the engine, and drives both queries,
// decoding each `ServerReflectionResponse`.
//
//     moon run src/examples/19-reflection

///|
fn drive(
  server : @moonrpc.H2Server,
  client : @moonrpc.H2Client,
  id : Int32,
  initial : Array[@http2.Frame],
) -> Unit raise {
  let to_server : Array[@http2.Frame] = []
  let to_client : Array[@http2.Frame] = []
  for f in client.preface() {
    to_server.push(f)
  }
  for f in server.preface() {
    to_client.push(f)
  }
  for f in initial {
    to_server.push(f)
  }
  let mut si = 0
  let mut ci = 0
  let mut steps = 0
  while !client.is_done(id) && steps < 100000 {
    steps = steps + 1
    if si < to_server.length() {
      let f = to_server[si]
      si = si + 1
      for r in server.feed(f) {
        to_client.push(r)
      }
    } else if ci < to_client.length() {
      let f = to_client[ci]
      ci = ci + 1
      for r in client.feed(f) {
        to_server.push(r)
      }
    } else {
      break
    }
  }
}

///|
/// Drive one reflection request as its own bidi stream and decode the response.
fn reflect(
  server : @moonrpc.H2Server,
  client : @moonrpc.H2Client,
  request : @moonrpc.ReflectionRequest,
) -> @moonrpc.ReflectionResponse raise {
  let id = client.open(@moonrpc.reflection_v1_path)
  let initial : Array[@http2.Frame] = []
  for
    f in client.send(id, @moonrpc.encode_reflection_request(request), end=true) {
    initial.push(f)
  }
  drive(server, client, id, initial)
  @moonrpc.decode_reflection_response(client.reply(id).messages[0])
}

///|
fn run() -> Unit raise {
  // A file descriptor for one service, indexed by the reflection database.
  let greeter : @moonrpc.ServiceDescriptor = {
    name: "Greeter",
    methods: [
      {
        name: "SayHello",
        input_type: ".greet.HelloRequest",
        output_type: ".greet.HelloReply",
        client_streaming: false,
        server_streaming: false,
      },
    ],
  }
  let refl = @moonrpc.ReflectionService::new()
  refl.add_file(
    @moonrpc.FileDescriptor::new("greet.proto", "greet", services=[greeter]),
  )
  let server = @moonrpc.H2Server::new()
  refl.install(server)
  let client = @moonrpc.H2Client::new()

  // ListServices enumerates every registered service.
  match reflect(server, client, ListServices("")) {
    ServiceList(names) => {
      println("ListServices -> \{names.length()} service(s):")
      for n in names {
        println("  \{n}")
      }
    }
    _ => println("unexpected response shape")
  }

  // FileContainingSymbol returns the descriptor bytes; decode them back to a file.
  match reflect(server, client, FileContainingSymbol("greet.Greeter")) {
    FileDescriptors(protos) => {
      let file = @moonrpc.FileDescriptor::decode(protos[0])
      println(
        "FileContainingSymbol(greet.Greeter) -> \{file.name} (package \{file.package_})",
      )
      for s in file.service_names() {
        println("  defines service \{s}")
      }
    }
    _ => println("unexpected response shape")
  }

  // A missing symbol comes back as an ErrorResponse, not a raise.
  match reflect(server, client, FileContainingSymbol("nope.Nope")) {
    ReflectionError(code, message) =>
      println("FileContainingSymbol(nope.Nope) -> error \{code}: \{message}")
    _ => println("unexpected response shape")
  }
}

///|
#coverage.skip
fn main {
  run() catch {
    e => println("error: \{e}")
  }
}