// What every service tree shares, whatever it serves: the module, its entry
// point, the configuration the entry point reads and the context handed to the
// author's code.

///|
/// The shared part of a service tree for module `module_`. `deps` are the
/// libraries the module imports besides `moonbitlang/async`, and `imports` and
/// `main` make up the entry point.
fn base_tree(
  module_ : String,
  st : @style.Style,
  dir : String,
  deps~ : Array[String],
  imports~ : Array[(String, String)],
  main~ : String,
) -> Array[TreeFile] {
  let file = st.format(module_)
  let config_pkg = module_ + "/internal/config"
  let svc_pkg = module_ + "/internal/svc"
  let mod_deps = deps.copy()
  mod_deps.push("moonbitlang/async")
  let main_imports = imports.copy()
  main_imports.push(("moonbitlang/async", ""))
  main_imports.push((config_pkg, ""))
  main_imports.push((svc_pkg, ""))
  let config =
    #|///|
    #|/// What the %svc service reads out of `etc/%file.yaml`.
    #|pub(all) struct Config {
    #|  host : String
    #|  port : Int32
    #|}
    #|
    #|///|
    #|/// The configuration `etc/%file.yaml` ships with.
    #|pub fn Config::new(host~ : String = "0.0.0.0", port~ : Int32 = %port) -> Config {
    #|  { host, port }
    #|}
  let context =
    #|///|
    #|/// What the author's code is given besides its input: the loaded configuration,
    #|/// and whatever else the service depends on. Add those here.
    #|pub(all) struct ServiceContext {
    #|  config : @config.Config
    #|}
    #|
    #|///|
    #|pub fn ServiceContext::new(config : @config.Config) -> ServiceContext {
    #|  { config, }
    #|}
  let fill = (s : String) => {
    s
    .replace_all(old="%svc", new=module_)
    .replace_all(old="%file", new=file)
    .replace_all(old="%port", new=port.to_string()) +
    "\n"
  }
  [
    {
      path: at(dir, "moon.mod"),
      content: mod_file(module_, mod_deps),
      regen: Once,
    },
    {
      path: at(dir, "moon.pkg"),
      content: pkg_file(main_imports, main_=true),
      regen: Once,
    },
    { path: at(dir, file + ".mbt"), content: main, regen: Once, },
    {
      path: at(dir, "etc/" + file + ".yaml"),
      content: "name: " +
      module_ +
      "\nhost: 0.0.0.0\nport: " +
      port.to_string() +
      "\n",
      regen: Once,
    },
    {
      path: at(dir, "internal/config/moon.pkg"),
      content: pkg_file([]),
      regen: Once,
    },
    {
      path: at(dir, "internal/config/" + st.format("config") + ".mbt"),
      content: fill(config),
      regen: Once,
    },
    {
      path: at(dir, "internal/svc/moon.pkg"),
      content: pkg_file([(config_pkg, "")]),
      regen: Once,
    },
    {
      path: at(dir, "internal/svc/" + st.format("service_context") + ".mbt"),
      content: fill(context),
      regen: Once,
    },
  ]
}

///|
/// The entry point of a service that serves an ASGI application `app` built from
/// `cfg` and `svc`, announcing itself as serving `what`.
fn asgi_main(module_ : String, what : String, app : String) -> String {
  let src =
    #|///|
    #|/// The %svc service: read the configuration, build the context its code shares
    #|/// and serve %what on mooncat.
    #|async fn main {
    #|  let cfg = @config.Config::new()
    #|  let svc = @svc.ServiceContext::new(cfg)
    #|  let app = %app
    #|  println("%svc serving %what on " + cfg.host + ":" + cfg.port.to_string())
    #|  @mooncat.serve(app, host=cfg.host, port=cfg.port)
    #|}
  src
  .replace_all(old="%svc", new=module_)
  .replace_all(old="%what", new=what)
  .replace_all(old="%app", new=app) +
  "\n"
}