///|
/// Convert an identifier to `snake_case` (← protobuf's PascalCase RPC/service
/// names, which must become MoonBit value names). An uppercase letter starts a
/// new word when it follows a lowercase letter or digit, or when it is followed
/// by a lowercase letter (so `HTTPServer` → `http_server`, `SayHello` →
/// `say_hello`). Existing underscores are preserved and the result is lowercased.
fn to_snake(s : String) -> String {
  let n = s.length()
  let mut r = ""
  for i = 0; i < n; i = i + 1 {
    let c = s[i].to_int()
    let is_upper = c >= 0x41 && c <= 0x5A
    if is_upper && i > 0 {
      let prev = s[i - 1].to_int()
      let prev_lower_or_digit = (prev >= 0x61 && prev <= 0x7A) ||
        (prev >= 0x30 && prev <= 0x39)
      let next_lower = i + 1 < n &&
        ({
          let nx = s[i + 1].to_int()
          nx >= 0x61 && nx <= 0x7A
        })
      if prev_lower_or_digit || next_lower {
        r = r + "_"
      }
    }
    r = r + s[i:i + 1].to_owned()
  }
  r.to_lower()
}

///|
/// Join `parts` with `", "` — the codegen workhorse for column/argument lists.
fn commas(parts : Array[String]) -> String {
  let mut out = ""
  for i = 0; i < parts.length(); i = i + 1 {
    if i > 0 {
      out = out + ", "
    }
    out = out + parts[i]
  }
  out
}

///|
/// Map a protobuf scalar type onto its MoonBit spelling (proto3 scalar set). An
/// unrecognised name is a message/enum reference and is passed through verbatim.
fn map_proto_type(raw : String) -> String {
  match raw {
    "double" => "Double"
    "float" => "Float"
    "int32" | "sint32" | "sfixed32" => "Int"
    "int64" | "sint64" | "sfixed64" => "Int64"
    "uint32" | "fixed32" => "UInt"
    "uint64" | "fixed64" => "UInt64"
    "bool" => "Bool"
    "string" => "String"
    "bytes" => "Bytes"
    _ => raw
  }
}

///|
/// One field of a protobuf `message`: its name and the (already MoonBit-mapped)
/// type. `repeated` becomes `Array[T]` and `map` becomes `Map[K, V]`.
pub(all) struct ProtoField {
  name : String
  type_ : String
}

///|
/// A protobuf `message` declaration, generated into a MoonBit struct.
pub(all) struct ProtoMessage {
  name : String
  fields : Array[ProtoField]
}

///|
/// One `rpc` method of a service: its name, request and response message names,
/// and whether either side is a `stream` (client/server/bidi streaming).
pub(all) struct Rpc {
  name : String
  request : String
  response : String
  client_streaming : Bool
  server_streaming : Bool
}

///|
/// A protobuf `service` declaration and its RPC methods.
pub(all) struct ProtoService {
  name : String
  rpcs : Array[Rpc]
}

///|
/// A parsed `.proto` file: its `package` (empty when none) and the services and
/// messages it declares.
pub(all) struct Proto {
  package_ : String
  services : Array[ProtoService]
  messages : Array[ProtoMessage]
}

///|
/// Tokenise a `.proto` source: whitespace-separated words plus the single-char
/// punctuation `{ } ( ) < > ; = ,` as their own tokens, with `//` line comments
/// and `/* … */` block comments stripped and `"…"` string literals kept whole.
fn proto_tokens(src : String) -> Array[String] {
  let out : Array[String] = []
  let n = src.length()
  let mut i = 0
  while i < n {
    let c = src[i]
    if is_ws(c) {
      i = i + 1
      continue
    }
    if c == '/' && i + 1 < n && src[i + 1] == '/' {
      while i < n && src[i] != '\n' {
        i = i + 1
      }
      continue
    }
    if c == '/' && i + 1 < n && src[i + 1] == '*' {
      i = i + 2
      while i + 1 < n && !(src[i] == '*' && src[i + 1] == '/') {
        i = i + 1
      }
      i = i + 2
      continue
    }
    if c == '"' {
      let start = i
      i = i + 1
      while i < n && src[i] != '"' {
        i = i + 1
      }
      i = i + 1
      out.push(src[start:i].to_owned())
      continue
    }
    if c == '{' ||
      c == '}' ||
      c == '(' ||
      c == ')' ||
      c == '<' ||
      c == '>' ||
      c == ';' ||
      c == '=' ||
      c == ',' {
      out.push(src[i:i + 1].to_owned())
      i = i + 1
      continue
    }
    let start = i
    while i < n {
      let d = src[i]
      if is_ws(d) ||
        d == '{' ||
        d == '}' ||
        d == '(' ||
        d == ')' ||
        d == '<' ||
        d == '>' ||
        d == ';' ||
        d == '=' ||
        d == ',' ||
        d == '"' {
        break
      }
      i = i + 1
    }
    out.push(src[start:i].to_owned())
  }
  out
}

///|
/// Index just past the next `;` at or after `i` (used to skip a statement).
fn skip_to_semi(toks : Array[String], i : Int) -> Int {
  let n = toks.length()
  let mut j = i
  while j < n && toks[j] != ";" {
    j = j + 1
  }
  j + 1
}

///|
/// Given `toks[i] == "{"`, index just past its matching `}` (brace-balanced).
fn skip_braced(toks : Array[String], i : Int) -> Int {
  let n = toks.length()
  let mut depth = 0
  let mut j = i
  while j < n {
    if toks[j] == "{" {
      depth = depth + 1
    } else if toks[j] == "}" {
      depth = depth - 1
      if depth == 0 {
        return j + 1
      }
    }
    j = j + 1
  }
  j
}

///|
/// Skip a whole `keyword … { … }` block starting at `i` (e.g. a nested message or
/// an enum); returns the index just past the matching `}`.
fn skip_block(toks : Array[String], i : Int) -> Int {
  let n = toks.length()
  let mut j = i
  while j < n && toks[j] != "{" {
    j = j + 1
  }
  if j >= n {
    return n
  }
  skip_braced(toks, j)
}

///|
/// Parse a `message` block starting at `toks[start] == "message"`. Returns the
/// message and the index just past its closing `}`. Handles scalar/message
/// fields, `repeated`, `map`, `optional`/`required`, and skips `reserved`,
/// nested messages/enums, and `option` lines.
fn parse_message(toks : Array[String], start : Int) -> (ProtoMessage, Int) {
  let n = toks.length()
  let name = if start + 1 < n { toks[start + 1] } else { "Unnamed" }
  let fields : Array[ProtoField] = []
  let mut i = start + 2
  if i < n && toks[i] == "{" {
    i = i + 1
  }
  while i < n && toks[i] != "}" {
    let t = toks[i]
    if t == "reserved" || t == "option" {
      i = skip_to_semi(toks, i)
      continue
    }
    if t == "message" || t == "enum" {
      i = skip_block(toks, i)
      continue
    }
    if t == "oneof" {
      i = i + 2
      if i < n && toks[i] == "{" {
        i = i + 1
      }
      while i < n && toks[i] != "}" {
        let (f, ni) = parse_field(toks, i)
        match f {
          Some(field) => fields.push(field)
          None => ()
        }
        i = ni
      }
      i = i + 1
      continue
    }
    let (f, ni) = parse_field(toks, i)
    match f {
      Some(field) => fields.push(field)
      None => ()
    }
    i = ni
  }
  i = i + 1
  ({ name, fields }, i)
}

///|
/// Parse a single message field statement (`[repeated] Type name = N ;` or
/// `map name = N ;`) starting at `i`; returns the field (or `None` if the
/// line is not a field) and the index just past its `;`.
fn parse_field(toks : Array[String], i : Int) -> (ProtoField?, Int) {
  let n = toks.length()
  if i >= n {
    return (None, n)
  }
  if toks[i] == "map" {
    let mut j = i + 1
    if j < n && toks[j] == "<" {
      j = j + 1
    }
    let k = if j < n { toks[j] } else { "string" }
    j = j + 1
    if j < n && toks[j] == "," {
      j = j + 1
    }
    let v = if j < n { toks[j] } else { "string" }
    j = j + 1
    if j < n && toks[j] == ">" {
      j = j + 1
    }
    let fname = if j < n { toks[j] } else { "field" }
    let end = skip_to_semi(toks, j)
    let type_ = "Map[" + map_proto_type(k) + ", " + map_proto_type(v) + "]"
    return (Some({ name: fname, type_ }), end)
  }
  let mut j = i
  let mut repeated = false
  if toks[j] == "repeated" {
    repeated = true
    j = j + 1
  } else if toks[j] == "optional" || toks[j] == "required" {
    j = j + 1
  }
  if j + 1 >= n {
    return (None, skip_to_semi(toks, i))
  }
  let ptype = toks[j]
  let fname = toks[j + 1]
  let end = skip_to_semi(toks, j + 1)
  let mapped = map_proto_type(ptype)
  let type_ = if repeated { "Array[" + mapped + "]" } else { mapped }
  (Some({ name: fname, type_ }), end)
}

///|
/// Parse a `service` block starting at `toks[start] == "service"`. Returns the
/// service and the index just past its closing `}`.
fn parse_service(toks : Array[String], start : Int) -> (ProtoService, Int) {
  let n = toks.length()
  let name = if start + 1 < n { toks[start + 1] } else { "Unnamed" }
  let rpcs : Array[Rpc] = []
  let mut i = start + 2
  if i < n && toks[i] == "{" {
    i = i + 1
  }
  while i < n && toks[i] != "}" {
    if toks[i] == "rpc" {
      let mname = if i + 1 < n { toks[i + 1] } else { "Unnamed" }
      let mut j = i + 2
      if j < n && toks[j] == "(" {
        j = j + 1
      }
      let mut client_streaming = false
      if j < n && toks[j] == "stream" {
        client_streaming = true
        j = j + 1
      }
      let request = if j < n { toks[j] } else { "Unit" }
      j = j + 1
      if j < n && toks[j] == ")" {
        j = j + 1
      }
      if j < n && toks[j] == "returns" {
        j = j + 1
      }
      if j < n && toks[j] == "(" {
        j = j + 1
      }
      let mut server_streaming = false
      if j < n && toks[j] == "stream" {
        server_streaming = true
        j = j + 1
      }
      let response = if j < n { toks[j] } else { "Unit" }
      j = j + 1
      if j < n && toks[j] == ")" {
        j = j + 1
      }
      rpcs.push({
        name: mname,
        request,
        response,
        client_streaming,
        server_streaming,
      })
      i = if j < n && toks[j] == "{" {
        skip_braced(toks, j)
      } else {
        skip_to_semi(toks, j)
      }
    } else if toks[i] == "option" {
      i = skip_to_semi(toks, i)
    } else {
      i = i + 1
    }
  }
  i = i + 1
  ({ name, rpcs }, i)
}

///|
/// Parse a minimal `.proto` (proto3). Recognises the `package` declaration,
/// `service` blocks with their `rpc` methods (including `stream`), and `message`
/// blocks with scalar/`repeated`/`map` fields. `syntax`, `import`, `option`, and
/// top-level `enum` declarations are skipped.
///
/// ```
/// syntax = "proto3";
/// package greet;
/// service Greeter {
///   rpc SayHello (HelloRequest) returns (HelloReply);
/// }
/// message HelloRequest { string name = 1; }
/// message HelloReply   { string message = 1; }
/// ```
pub fn parse_proto(source : String) -> Proto {
  let toks = proto_tokens(source)
  let n = toks.length()
  let mut package_ = ""
  let services : Array[ProtoService] = []
  let messages : Array[ProtoMessage] = []
  let mut i = 0
  while i < n {
    let t = toks[i]
    if t == "package" {
      let mut name = ""
      i = i + 1
      while i < n && toks[i] != ";" {
        name = name + toks[i]
        i = i + 1
      }
      package_ = name
      i = i + 1
    } else if t == "syntax" || t == "import" || t == "option" {
      i = skip_to_semi(toks, i)
    } else if t == "service" {
      let (svc, ni) = parse_service(toks, i)
      services.push(svc)
      i = ni
    } else if t == "message" {
      let (msg, ni) = parse_message(toks, i)
      messages.push(msg)
      i = ni
    } else if t == "enum" {
      i = skip_block(toks, i)
    } else {
      i = i + 1
    }
  }
  { package_, services, messages }
}

///|
/// The gRPC handler input type for an RPC (its request, or `Array[Request]` when
/// the client streams).
fn rpc_in(r : Rpc) -> String {
  if r.client_streaming {
    "Array[" + r.request + "]"
  } else {
    r.request
  }
}

///|
/// The gRPC handler output type for an RPC (its reply, or `Array[Reply]` when the
/// server streams).
fn rpc_out(r : Rpc) -> String {
  if r.server_streaming {
    "Array[" + r.response + "]"
  } else {
    r.response
  }
}

///|
/// Generate a moonrpc service stub from a parsed `.proto`: a MoonBit struct per
/// `message`, a `@moonrpc.Method` descriptor per RPC (its gRPC `:path` is
/// `/package.Service/Method`), a `Server` handler-registration struct
/// (one synchronous-core handler field per RPC, each returning its reply or a
/// gRPC `@moonrpc.Status`), and a `_methods()` listing. The output
/// compiles against `Lfan-ke/moonrpc`.
pub fn generate_grpc(proto : Proto) -> String {
  let mut out = "// Code generated by moonctl. DO NOT EDIT.\n\n"
  for m in proto.messages {
    out = out +
      "///|\n/// `" +
      m.name +
      "` message (generated from the `.proto` `message` block).\npub(all) struct " +
      m.name +
      " {\n"
    for f in m.fields {
      out = out + "  " + f.name + " : " + f.type_ + "\n"
    }
    out = out + "}\n\n"
  }
  for s in proto.services {
    let full = if proto.package_ == "" {
      s.name
    } else {
      proto.package_ + "." + s.name
    }
    let svc = to_snake(s.name)
    for r in s.rpcs {
      let dname = svc + "_" + to_snake(r.name)
      out = out +
        "///|\n/// gRPC method descriptor for `" +
        full +
        "/" +
        r.name +
        "` (`:path` = " +
        quote("/" + full + "/" + r.name) +
        ").\npub let " +
        dname +
        " : @moonrpc.Method = { service: " +
        quote(full) +
        ", name: " +
        quote(r.name) +
        " }\n\n"
    }
    out = out +
      "///|\n/// The `" +
      full +
      "` service: one handler per RPC (synchronous core). Each handler takes its\n/// request (an `Array` when the client streams) and returns its reply (an\n/// `Array` when the server streams) or a gRPC `@moonrpc.Status` on failure.\npub(all) struct " +
      s.name +
      "Server {\n"
    for r in s.rpcs {
      out = out +
        "  " +
        to_snake(r.name) +
        " : (" +
        rpc_in(r) +
        ") -> Result[" +
        rpc_out(r) +
        ", @moonrpc.Status]\n"
    }
    out = out + "}\n\n"
    let names : Array[String] = []
    for r in s.rpcs {
      names.push(svc + "_" + to_snake(r.name))
    }
    out = out +
      "///|\n/// Every method descriptor in `" +
      full +
      "`, in declaration order.\npub fn " +
      svc +
      "_methods() -> Array[@moonrpc.Method] {\n  [" +
      commas(names) +
      "]\n}\n\n"
  }
  out
}