// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// The four layers and the binding, in one run.
///
///     moon run src/examples/tour
#coverage.skip
fn main raise {
  // What the program carries.
  let defaults = @moonconf.Node::map(
    [
      (
        "db",
        @moonconf.Node::map(
          [
            ("host", @moonconf.Node::str("localhost", "defaults")),
            ("port", @moonconf.Node::num(5432, "defaults")),
          ],
          "defaults",
        ),
      ),
    ],
    "defaults",
  )

  // What the deployment shipped, as an INI file.
  let file = @ini.parse("[db]\nhost = db.internal\npool = 8\n").tree(
    source="app.ini",
  )

  // What the environment says. A process environment arrives as text, which is
  // why the binding below reads text as the type it spells.
  let environment = @moonconf.Node::map(
    [
      (
        "db",
        @moonconf.Node::map(
          [("port", @moonconf.Node::str("6432", "env"))],
          "env",
        ),
      ),
    ],
    "env",
  )

  let merged = @moonconf.merge([
    @moonconf.Source::new(@moonconf.Env, "env", environment),
    @moonconf.Source::new(@moonconf.Default, "defaults", defaults),
    @moonconf.Source::new(@moonconf.File, "app.ini", file),
  ])
  println("host  -> \{merged.need_text("db.host")}")
  println("port  -> \{merged.need_text("db.port")}")
  println("pool  -> \{merged.need_text("db.pool")}")

  // The model says what a valid configuration is; binding says whether this one
  // is, and fills in what was not written.
  let db = @moonmodel.Model::new("Db", [
    @moonmodel.Field::new("host", @moonmodel.Type::Text, required=true),
    @moonmodel.Field::new("port", @moonmodel.Type::Int, required=true),
    @moonmodel.Field::new(
      "pool",
      @moonmodel.Type::Int,
      default=Json::number(4.0),
    ),
  ])
  println("bound -> \{@moonconf.bind(merged.need("db"), db).stringify()}")

  // A `.env` file is the same idea with fewer brackets, and its references are
  // expanded in file order.
  let env = @dotenv.load("HOST=db.internal\nDSN=postgres://${HOST}/app\n")
  println("dsn   -> \{env.get("DSN").unwrap()}")

  // Text expansion stands on its own, for a value written as a shell would.
  let vars = @expand.Vars::of_map(Map([("PORT", "8080")]))
  let template = "http://localhost:${PORT:-80}/"
  println("url   -> \{@expand.expand(template, vars)}")
}