// demo.mbt — a runnable tour of the public API.
//
// This exists because the acceptance criteria ask for a runnable example, not
// just a README snippet: something a reviewer can execute end to end and compare
// against the recorded output in docs/reproducible-demo.md.
//
// Run it with:
//   moon run cmd/demo
//
// Every value printed here comes from the library itself. Nothing is hard-coded
// to make the output look right, so if the library regresses this output changes.

///|
fn head(title : String) -> Unit {
  println("")
  println("== \{title} ==")
}

///|
fn show(label : String, value : String) -> Unit {
  println("\{label}: \{value}")
}

///|
fn join(items : Array[String], sep : String) -> String {
  let buf = StringBuilder()
  for i = 0; i < items.length(); i = i + 1 {
    if i > 0 {
      buf.write_string(sep)
    }
    buf.write_string(items[i])
  }
  buf.to_string()
}

///|
/// Parse one written number and print the useful facts about it.
fn tour(input : String, region : String) -> Unit {
  show(
    "input",
    input + (if region == "" { "" } else { "  [region " + region + "]" }),
  )
  match @dn.parse(input, region) {
    None => show("result", "unreadable")
    Some(pn) => {
      show("e164", @dn.format_e164(pn))
      show("international", @dn.format_international(pn))
      let reg = match @dn.region_of_number(pn) {
        Some(c) => c
        None => "unresolved"
      }
      show("region", reg)
      show("possible", if @dn.is_possible(pn) { "yes" } else { "no" })
      show("valid", if @dn.is_valid(pn) { "yes" } else { "no" })
      match @dn.number_type(pn) {
        Some(t) => show("type", t.to_string())
        None => show("type", "(none)")
      }
    }
  }
}

///|
/// The number types a length admits, as a comma-separated list.
fn types_of(input : String, region : String) -> Unit {
  match @dn.parse(input, region) {
    None => show("types", "unreadable")
    Some(pn) => {
      let names : Array[String] = []
      for t in @dn.possible_types(pn) {
        names.push(t.to_string())
      }
      show(input, join(names, ","))
    }
  }
}

///|
/// One line of region metadata drawn straight from the table.
fn fact(code : String) -> Unit {
  let name = match @dn.region_name(code) {
    Some(n) => n
    None => "?"
  }
  let cc = match @dn.calling_code(code) {
    Some(c) => c.to_string()
    None => "?"
  }
  let lens : Array[String] = []
  match @dn.possible_lengths(code) {
    Some(ls) =>
      for n in ls {
        lens.push(n.to_string())
      }
    None => ()
  }
  show(code, name + " +" + cc + "  lengths {" + join(lens, ",") + "}")
}

///|
fn main {
  head("parse across the ways people write a number")
  tour("+1 201-555-0123", "")
  tour("011 44 20 7946 0958", "US")
  tour("090-1234-5678", "JP")
  tour("+86 (131) 2345-6789", "")
  tour("02 12345678", "IT")
  tour("+999 555 0000", "")

  head("length is what separates possible from impossible")
  tour("+1 201-555-012", "")
  tour("+1 201-555-01234", "")

  head("national rendering needs a region")
  match @dn.parse("090-1234-5678", "JP") {
    None => show("national", "unreadable")
    Some(pn) => {
      let nat = match @dn.format_national(pn, "JP") {
        Some(v) => v
        None => "(none)"
      }
      show("national", nat)
    }
  }

  head("each plan groups the number its own way")
  // The grouping is the plans' own: each publishes the shapes its numbers are
  // written in, and the renderer picks the rule that fits.
  let styled : Array[(String, String)] = [
    ("+1 201-555-0123", "US"),
    ("+44 20 7946 0958", "GB"),
    ("+49 30 123456", "DE"),
    ("+33 1 23 45 67 89", "FR"),
    ("+81 90-1234-5678", "JP"),
    ("+55 11 98765-4321", "BR"),
    ("+61 2 1234 5678", "AU"),
  ]
  for pair in styled {
    match @dn.parse(pair.0, "") {
      None => show(pair.1, "unreadable")
      Some(pn) => {
        let nat = match @dn.format_national(pn, pair.1) {
          Some(v) => v
          None => "(none)"
        }
        show(pair.1, "national: " + nat + "   international: " + @dn.format_international(pn))
      }
    }
  }

  head("+1 is 25 territories, and each allocation decides")
  // Every +1 territory publishes its own area-code allocation; the engine
  // resolves the number against it instead of a hard-coded US/CA split.
  tour("+1 201-555-0123", "")
  tour("+1 204-254-5000", "")
  tour("+1 787-234-5678", "")
  tour("+1 876-523-0123", "")
  tour("+1 242-345-6789", "")
  tour("+1 868-221-1234", "")
  tour("+1 111-555-0123", "")
  tour("+1 201-155-0123", "")

  head("possible is a length question, valid is a rule question")
  // 555 passes the NANP's shape rules but is not an assigned area code, so the
  // number is possible and not valid — the distinction the two levels exist for.
  tour("+1 555-555-0123", "")
  tour("+1 201-555-0123", "")
  tour("+81 90-1234-567", "")

  head("the rules are data the engine interprets")
  match @dn.plan_pattern("DK") {
    Some(p) => show("DK plan pattern", p)
    None => show("DK plan pattern", "(none)")
  }
  show("pattern matches 12345678", if @dn.matches_pattern("\\d{8}", "12345678") { "yes" } else { "no" })
  show("pattern matches 1234567", if @dn.matches_pattern("\\d{8}", "1234567") { "yes" } else { "no" })

  head("a parsed number keeps its extension")
  match @dn.parse_tel_uri("tel:+1-201-555-0123;ext=42") {
    None => show("tel uri", "unreadable")
    Some(pn) => {
      show("e164", @dn.format_e164(pn))
      show("extension", pn.extension)
      show("rfc3966", @dn.format_rfc3966(pn))
    }
  }

  head("a tel: URI keeps its local context")
  // A local number with a numeric context resolves; the context is preserved
  // rather than merged away, so the canonical form can be regenerated.
  match @dn.parse_tel_uri_full("tel:555-0123;phone-context=+1-919;ext=42") {
    None => show("tel uri", "rejected")
    Some(t) => {
      show("local_number", t.local_number)
      show("phone_context", t.phone_context)
      show("canonical", t.to_string())
      match @dn.resolve_tel_uri(t) {
        Some(pn) => show("resolved", @dn.format_e164(pn))
        None => show("resolved", "no")
      }
    }
  }
  // A domain context is valid RFC 3966 but cannot be resolved to a number here.
  match @dn.parse_tel_uri_full("tel:7042;phone-context=example.com") {
    None => show("tel uri", "rejected")
    Some(t) => {
      show("phone_context", t.phone_context)
      show("canonical", t.to_string())
      match @dn.resolve_tel_uri(t) {
        Some(pn) => show("resolved", @dn.format_e164(pn))
        None => show("resolved", "no (domain context)")
      }
    }
  }

  head("the number types a length admits")
  types_of("+86 131 2345 6789", "")
  types_of("+44 7400 123456", "")

  head("dialling from where you are")
  // The same number, as a caller in three countries would dial it.
  match @dn.parse("+442079460958", "") {
    None => show("dialling", "unreadable")
    Some(pn) => {
      for from in ["US", "DE", "GB", "AU"] {
        let written = match @dn.format_dialling(pn, from) {
          Some(v) => v
          None => "(unknown region)"
        }
        show("from " + from, written)
      }
    }
  }

  head("region facts come from the table")
  let facts : Array[String] = ["US", "JP", "DE", "IT"]
  for code in facts {
    fact(code)
  }
}