// Project skeletons: `mctl api/rpc/model new `, `mctl docker`, `mctl kube`.
// goctl scaffolds a whole nested project tree (a runnable service, its config, a
// Dockerfile, a Kubernetes deployment) rather than a single file; these generators
// are the MoonBit equivalent. Each returns the list of files to write — reusing the
// `GenFile { path, content }` shape the plugin protocol already uses — so the `mctl`
// binary writes them with the same nested-directory `mkdir -p` walk. The `api`, `rpc`
// and `model` skeletons emit their source through the very same built-in generators
// (`generate` / `generate_grpc` / `generate_crud_from_ddl`), so a scaffolded project
// compiles for the same reason a generated file does.

///|
/// A sample `.api` spec seeded into an `api` skeleton.
fn sample_api(name : String) -> String {
  "// " +
  name +
  " service — edit this spec, then regenerate with `mctl gen api " +
  name +
  ".api`.\nservice " +
  name +
  " {\n  get  /ping        ping        \"health check\"\n  get  /" +
  name +
  "/:id   get_one\n  post /" +
  name +
  "        create_one  \"create one\"\n}\n\ntype CreateReq {\n  name: string\n  age:  int\n}\n"
}

///|
/// A sample `.proto` seeded into an `rpc` skeleton.
fn sample_proto(name : String) -> String {
  "syntax = \"proto3\";\n\npackage " +
  name +
  ";\n\nmessage PingReq {}\n\nmessage PingResp {\n  string message = 1;\n}\n\nservice " +
  to_pascal(name) +
  " {\n  rpc Ping(PingReq) returns (PingResp);\n}\n"
}

///|
/// A sample SQL schema seeded into a `model` skeleton.
fn sample_sql(name : String) -> String {
  "-- " +
  name +
  " schema — edit, then regenerate with `mctl gen crud schema.sql`.\nCREATE TABLE " +
  name +
  " (\n  id    INTEGER PRIMARY KEY,\n  name  TEXT NOT NULL,\n  age   INTEGER\n);\n"
}

///|
/// Scaffold a runnable moonapi service project under `/`: a `moon.mod`
/// (depending on the published `moonapi` + `moonasgi`), a sample `.api` spec,
/// its generated routes+handlers in `src/app.mbt` (through the same `generate` the
/// `gen api` command uses, so it compiles), and a README. This is goctl's
/// `goctl api new`.
pub fn scaffold_api(name : String) -> Array[GenFile] {
  let api = sample_api(name)
  let app = generate_builtin(parse_lenient(api))
  [
    {
      path: name + "/moon.mod",
      content: mod_file(name, ["moonbitstack/moonapi", "moonbitstack/moonasgi"]),
    },
    { path: name + "/" + name + ".api", content: api, },
    {
      path: name + "/src/moon.pkg",
      content: pkg_file([
        ("moonbitstack/moonapi", ""),
        ("moonbitstack/moonasgi", ""),
      ]),
    },
    { path: name + "/src/app.mbt", content: app, },
    {
      path: name + "/README.md",
      content: "# " +
      name +
      "\n\nA moonapi service scaffolded by `mctl api new`. Edit `" +
      name +
      ".api`, regenerate with `mctl gen api " +
      name +
      ".api`, and fill in the handler stubs in `src/app.mbt`.\n",
    },
  ]
}

///|
/// Scaffold a moonrpc service project under `/`: a `moon.mod` (depending
/// on the published `moonrpc`), a sample `.proto`, its generated service stub
/// in `src/service.mbt` (through the same `generate_grpc` as `gen proto`), and a
/// README. goctl's `goctl rpc new`.
pub fn scaffold_rpc(name : String) -> Array[GenFile] {
  let proto = sample_proto(name)
  let stub = generate_grpc(parse_proto(proto))
  [
    {
      path: name + "/moon.mod",
      content: mod_file(name, ["moonbitstack/moonrpc"]),
    },
    { path: name + "/" + name + ".proto", content: proto, },
    {
      path: name + "/src/moon.pkg",
      content: pkg_file([("moonbitstack/moonrpc", "")]),
    },
    { path: name + "/src/service.mbt", content: stub, },
    {
      path: name + "/README.md",
      content: "# " +
      name +
      "\n\nA moonrpc service scaffolded by `mctl rpc new`. Edit `" +
      name +
      ".proto`, regenerate with `mctl gen proto " +
      name +
      ".proto`, and implement the RPC handlers in `src/service.mbt`.\n",
    },
  ]
}

///|
/// Scaffold a moonorm data-layer project under `/`: a `moon.mod`
/// (depending on the published `moonorm` + `moondb`), a sample `schema.sql`, its
/// generated models + CRUD in `src/model.mbt` (through the same
/// `generate_crud_from_ddl` as `gen crud`), and a README. goctl's `goctl model … new`.
pub fn scaffold_model(name : String) -> Array[GenFile] {
  let sql = sample_sql(name)
  let model = generate_crud_from_ddl(sql)
  [
    {
      path: name + "/moon.mod",
      content: mod_file(name, ["moonbitstack/moonorm", "moonbitstack/moondb"]),
    },
    { path: name + "/schema.sql", content: sql, },
    {
      path: name + "/src/moon.pkg",
      content: pkg_file([
        ("moonbitstack/moonorm", ""),
        ("moonbitstack/moondb", ""),
      ]),
    },
    { path: name + "/src/model.mbt", content: model, },
    {
      path: name + "/README.md",
      content: "# " +
      name +
      "\n\nA moonorm data layer scaffolded by `mctl model new`. Edit `schema.sql`, regenerate with `mctl gen crud schema.sql`.\n",
    },
  ]
}

///|
/// Scaffold container files for a native `mctl`-generated service: a `Dockerfile`
/// (a MoonBit build stage that produces the native binary, then a slim runtime
/// stage that runs it) and a `.dockerignore`. `name` is the binary/image name,
/// `port` the port the service listens on. goctl's `goctl docker`.
pub fn scaffold_docker(name : String, port~ : Int32 = port) -> Array[GenFile] {
  let p = port.to_string()
  let dockerfile = "# syntax=docker/dockerfile:1\n" +
    "# Build stage: compile the native mctl-generated service.\n" +
    "FROM debian:stable-slim AS build\n" +
    "RUN apt-get update && apt-get install -y curl build-essential && rm -rf /var/lib/apt/lists/*\n" +
    "RUN curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bash\n" +
    "ENV PATH=/root/.moon/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n" +
    "WORKDIR /src\n" +
    "COPY . .\n" +
    "RUN moon build --target native --release\n" +
    "\n# Runtime stage: carry only the built binary.\n" +
    "FROM debian:stable-slim\n" +
    "WORKDIR /app\n" +
    "COPY --from=build /src/target/native/release/build/" +
    name +
    "/" +
    name +
    ".exe /app/" +
    name +
    "\n" +
    "EXPOSE " +
    p +
    "\n" +
    "ENTRYPOINT [\"/app/" +
    name +
    "\"]\n"
  let dockerignore = "_build/\ntarget/\n.mooncakes/\n.git/\n*.md\n"
  [
    { path: "Dockerfile", content: dockerfile, },
    { path: ".dockerignore", content: dockerignore, },
  ]
}

///|
/// The port a scaffolded service listens on.
///
/// 12000, the one every server in this family defaults to: HTTP, WebSocket and
/// h2c share it because one listener tells them apart, so a generated manifest
/// and a generated configuration name the same number without anyone saying
/// which they meant.
pub let port : Int32 = 12000

///|
/// Scaffold a Kubernetes deployment for the service under `deploy/`: a
/// `Deployment` (`replicas` pods of the `` image, a container port, and a
/// liveness probe on `/ping`) and a `Service` exposing it. goctl's
/// `goctl kube deploy`.
///
/// The manifests are built as values and written by `moonyaml`, not joined
/// out of string fragments. A generator that joins fragments has the indentation
/// in its string literals, where a missed space is invisible, and quotes nothing
/// — so the first service name with a colon in it emits a file Kubernetes reads
/// as something else.
pub fn scaffold_kube(
  name : String,
  port~ : Int32 = port,
  replicas~ : Int32 = 2,
) -> Array[GenFile] {
  let labels = Json::object({ "app": Json::string(name) })
  let deployment = Json::object({
    "apiVersion": Json::string("apps/v1"),
    "kind": Json::string("Deployment"),
    "metadata": Json::object({ "name": Json::string(name), "labels": labels }),
    "spec": Json::object({
      "replicas": Json::number(replicas.to_double()),
      "selector": Json::object({ "matchLabels": labels }),
      "template": Json::object({
        "metadata": Json::object({ "labels": labels }),
        "spec": Json::object({
          "containers": Json::array([
            Json::object({
              "name": Json::string(name),
              "image": Json::string(name + ":latest"),
              "ports": Json::array([
                Json::object({ "containerPort": Json::number(port.to_double()) }),
              ]),
              "livenessProbe": Json::object({
                "httpGet": Json::object({
                  "path": Json::string("/ping"),
                  "port": Json::number(port.to_double()),
                }),
                "initialDelaySeconds": Json::number(5),
                "periodSeconds": Json::number(10),
              }),
            }),
          ]),
        }),
      }),
    }),
  })
  let service = Json::object({
    "apiVersion": Json::string("v1"),
    "kind": Json::string("Service"),
    "metadata": Json::object({ "name": Json::string(name) }),
    "spec": Json::object({
      "selector": labels,
      "ports": Json::array([
        Json::object({
          "protocol": Json::string("TCP"),
          "port": Json::number(80),
          "targetPort": Json::number(port.to_double()),
        }),
      ]),
      "type": Json::string("ClusterIP"),
    }),
  })
  [
    { path: "deploy/deployment.yaml", content: @moonyaml.dumps(deployment), },
    { path: "deploy/service.yaml", content: @moonyaml.dumps(service), },
  ]
}