// 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"
}

///|
/// A `moon.mod.json` for a scaffolded project with the given dependency map (each
/// entry a `"pkg": "version"` line).
fn mod_json(name : String, deps : Array[(String, String)]) -> String {
  let dep_lines : Array[String] = []
  for d in deps {
    dep_lines.push("    " + quote(d.0) + ": " + quote(d.1))
  }
  let deps_body = if dep_lines.length() == 0 {
    "{}"
  } else {
    "{\n" + join_commas(dep_lines) + "\n  }"
  }
  "{\n  \"name\": " +
  quote(name) +
  ",\n  \"version\": \"0.1.0\",\n  \"deps\": " +
  deps_body +
  "\n}\n"
}

///|
/// A `src/moon.pkg.json` importing the given packages, and marked native+executable
/// when `executable` (a runnable service needs the async server, which is native).
fn pkg_json(imports : Array[String], executable : Bool) -> String {
  let import_lines : Array[String] = []
  for im in imports {
    import_lines.push("    " + quote(im))
  }
  let imports_body = if import_lines.length() == 0 {
    "[]"
  } else {
    "[\n" + join_commas(import_lines) + "\n  ]"
  }
  let mut out = "{\n  \"import\": " + imports_body
  if executable {
    out = out + ",\n  \"is-main\": true,\n  \"supported-targets\": [\"native\"]"
  }
  out + "\n}\n"
}

///|
/// Join lines with `,\n` — the JSON-array/object element separator.
fn join_commas(parts : Array[String]) -> String {
  let mut out = ""
  for i = 0; i < parts.length(); i = i + 1 {
    if i > 0 {
      out = out + ",\n"
    }
    out = out + parts[i]
  }
  out
}

///|
/// Scaffold a runnable moonapi service project under `/`: a `moon.mod.json`
/// (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(api))
  [
    {
      path: name + "/moon.mod.json",
      content: mod_json(name, [
        ("Lfan-ke/moonapi", "0.6.0"),
        ("Lfan-ke/moonasgi", "0.1.0"),
      ]),
    },
    { path: name + "/" + name + ".api", content: api },
    {
      path: name + "/src/moon.pkg.json",
      content: pkg_json(["Lfan-ke/moonapi", "Lfan-ke/moonasgi"], false),
    },
    { 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.json` (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.json",
      content: mod_json(name, [("Lfan-ke/moonrpc", "0.6.0")]),
    },
    { path: name + "/" + name + ".proto", content: proto },
    {
      path: name + "/src/moon.pkg.json",
      content: pkg_json(["Lfan-ke/moonrpc"], false),
    },
    { 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.json`
/// (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.json",
      content: mod_json(name, [
        ("Lfan-ke/moonorm", "0.6.1"),
        ("Lfan-ke/moondb", "0.1.3"),
      ]),
    },
    { path: name + "/schema.sql", content: sql },
    {
      path: name + "/src/moon.pkg.json",
      content: pkg_json(["Lfan-ke/moonorm", "Lfan-ke/moondb"], false),
    },
    { 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? : Int = 8080) -> 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 },
  ]
}

///|
/// 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`.
pub fn scaffold_kube(
  name : String,
  port? : Int = 8080,
  replicas? : Int = 2,
) -> Array[GenFile] {
  let p = port.to_string()
  let r = replicas.to_string()
  let deployment = "apiVersion: apps/v1\n" +
    "kind: Deployment\n" +
    "metadata:\n  name: " +
    name +
    "\n  labels:\n    app: " +
    name +
    "\nspec:\n  replicas: " +
    r +
    "\n  selector:\n    matchLabels:\n      app: " +
    name +
    "\n  template:\n    metadata:\n      labels:\n        app: " +
    name +
    "\n    spec:\n      containers:\n        - name: " +
    name +
    "\n          image: " +
    name +
    ":latest\n          ports:\n            - containerPort: " +
    p +
    "\n          livenessProbe:\n            httpGet:\n              path: /ping\n              port: " +
    p +
    "\n            initialDelaySeconds: 5\n            periodSeconds: 10\n"
  let service = "apiVersion: v1\n" +
    "kind: Service\n" +
    "metadata:\n  name: " +
    name +
    "\nspec:\n  selector:\n    app: " +
    name +
    "\n  ports:\n    - protocol: TCP\n      port: 80\n      targetPort: " +
    p +
    "\n  type: ClusterIP\n"
  [
    { path: "deploy/deployment.yaml", content: deployment },
    { path: "deploy/service.yaml", content: service },
  ]
}