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

// A fuller integration: a small site config is read, inspected, changed and
// written back, then a broken config is reported by the line it failed on.
//
// It uses tables, an array of tables, dotted keys, a date, a hexadecimal
// integer and a byte-order mark, which is most of what a real config file holds.
//
// Run it with `moon run examples/integration`.

///|
/// The parts of the config this program actually consumes.
struct Site {
  title : String
  port : Int32
  host : String
  tls : Bool
  routes : Array[String]
}

///|
fn main raise {
  let src =
    #|title = "field notes"
    #|port = 0x1F90
    #|started = 2026-03-01 09:30:00Z
    #|
    #|[server]
    #|host = "127.0.0.1"
    #|tls = false
    #|limits.timeout = 30
    #|
    #|[[route]]
    #|path = "/"
    #|methods = [ "GET" ]
    #|
    #|[[route]]
    #|path = "/notes"
    #|methods = [ "GET", "POST" ]
    #|

  // Read the document once, then take what the program needs out of the tree.
  let doc = @moontoml.loads(src[:])
  guard doc is Object(root) else { abort("a document is a table") }
  let site = read(root)

  println("\{site.title} on \{site.host}:\{site.port}")
  println("tls = \{site.tls}, routes = \{site.routes.length()}")
  for route in site.routes {
    println("  \{route}")
  }

  // Change the tree the way the program would, then write it as sorted TOML so
  // the output has one order no matter how the input was laid out.
  match root.get("server") {
    Some(Object(server)) => server["tls"] = Json::boolean(true)
    _ => ()
  }
  let route : Map[String, Json] = {
    "path": Json::string("/about"),
    "methods": Json::array([Json::string("GET")]),
  }
  match root.get("route") {
    Some(Array(items)) => items.push(Json::object(route))
    _ => root["route"] = Json::array([Json::object(route)])
  }
  println("")
  println(@moontoml.dumps(doc, sort=true))

  // A refused document says where it stopped, so the caller can point at it.
  let broken = "port = 1\nport = 2\n"
  let refusal = try {
    let _ = @moontoml.loads(broken[:])
    None
  } catch {
    e => Some(e)
  }
  match refusal {
    Some(e) =>
      println("refused at line \{e.at().line}, column \{e.at().column}: \{e}")
    None => println("no refusal")
  }
}

///|
/// Pull the fields the program uses out of the root table.
fn read(root : Map[String, Json]) -> Site {
  let title = match root.get("title") {
    Some(String(s)) => s
    _ => "?"
  }
  let port = match root.get("port") {
    Some(Number(n, ..)) => n.to_int()
    _ => 0
  }
  // A sub-table is just a `Json` object under a key.
  let (host, tls) = match root.get("server") {
    Some(Object(server)) =>
      (
        match server.get("host") {
          Some(String(s)) => s
          _ => "localhost"
        },
        match server.get("tls") {
          Some(True) => true
          _ => false
        },
      )
    _ => ("localhost", false)
  }
  // An array of tables is an array whose elements are objects.
  let routes = match root.get("route") {
    Some(Array(items)) =>
      items
      .iter()
      .filter_map(fn(item) {
        match item {
          Object(r) =>
            match r.get("path") {
              Some(String(p)) => Some(p)
              _ => None
            }
          _ => None
        }
      })
      .collect()
    _ => []
  }
  { title, port, host, tls, routes, }
}