///|
fn usage() -> Unit {
  println("MoonZoneGuard — offline DNS zone review")
  println("Usage:")
  println("  moonzoneguard check   [text|json|markdown]")
  println(
    "  moonzoneguard rollout       [text|json]",
  )
  println("  moonzoneguard plan  [text|json]")
  println(
    "  moonzoneguard diff    [text|json]",
  )
  println(
    "  moonzoneguard impact    [text|json]",
  )
  println("  moonzoneguard stats   [text|json]")
  println("  moonzoneguard trace   ")
  println("  moonzoneguard normalize  ")
  println(
    "  moonzoneguard lookup     [text|json]",
  )
  println(
    "  moonzoneguard policy    [text|json|markdown]",
  )
}

///|
fn load(path : String) -> String? {
  Some(@fs.read_file_to_string(path)) catch {
    _ => {
      println("Cannot read \{path}")
      @sys.exit(2)
      None
    }
  }
}

///|
/// Manifest rows contain: origin before-file after-file. Paths are relative
/// to the process working directory, as with the other CLI commands.
fn load_plan_manifest(input : String) -> Array[@lib.ZoneRevision]? {
  let revisions : Array[@lib.ZoneRevision] = []
  let lines = input.split("\n").collect()
  for index in 0.. {
        let origin = @lib.absolute_name("@", parts[0])
        revisions.push({
          origin,
          before: @lib.parse_zone(before, origin),
          after: @lib.parse_zone(after, origin),
        })
      }
      _ => return None
    }
  }
  Some(revisions)
}

///|
fn main {
  let args = @env.args()
  if args.length() < 3 {
    usage()
    @sys.exit(2)
    return
  }
  match args[2] {
    "plan" => {
      if args.length() < 4 {
        usage()
        @sys.exit(2)
        return
      }
      let format = if args.length() > 4 { args[4] } else { "text" }
      if format != "text" && format != "json" {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        Some(input) =>
          match load_plan_manifest(input) {
            Some(revisions) => {
              let plan = @lib.plan_delegation_rollout(revisions)
              if format == "json" {
                println(@lib.render_deployment_plan_json(plan))
              } else {
                println(@lib.render_deployment_plan_text(plan))
              }
              if plan.status != "safe-order" {
                @sys.exit(1)
              }
            }
            None => @sys.exit(2)
          }
        None => ()
      }
    }
    "rollout" => {
      if args.length() < 9 {
        usage()
        @sys.exit(2)
        return
      }
      let format = if args.length() > 9 { args[9] } else { "text" }
      if format != "text" && format != "json" {
        usage()
        @sys.exit(2)
        return
      }
      match (load(args[3]), load(args[4]), load(args[5]), load(args[6])) {
        (
          Some(parent_before),
          Some(parent_after),
          Some(child_before),
          Some(child_after),
        ) => {
          let result = @lib.analyze_delegation_rollout(
            @lib.parse_zone(parent_before, args[7]),
            @lib.parse_zone(parent_after, args[7]),
            @lib.parse_zone(child_before, args[8]),
            @lib.parse_zone(child_after, args[8]),
          )
          if format == "json" {
            println(@lib.render_delegation_rollout_json(result))
          } else {
            println(@lib.render_delegation_rollout_text(result))
          }
          if result.recommendation == "no-safe-order" {
            @sys.exit(1)
          }
        }
        _ => ()
      }
    }
    "check" => {
      if args.length() < 5 {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        None => return
        Some(source) => {
          let findings = @lib.inspect_zone(source, args[4])
          let format = if args.length() > 5 { args[5] } else { "text" }
          match format {
            "json" => println(@lib.render_diagnostics_json(findings))
            "markdown" => println(@lib.render_diagnostics_markdown(findings))
            "text" => println(@lib.render_diagnostics_text(findings))
            _ => {
              usage()
              @sys.exit(2)
            }
          }
          if @lib.error_count(findings) > 0 {
            @sys.exit(1)
          }
        }
      }
    }
    "diff" | "impact" => {
      if args.length() < 6 {
        usage()
        @sys.exit(2)
        return
      }
      match (load(args[3]), load(args[4])) {
        (Some(before), Some(after)) => {
          let format = if args.length() > 6 { args[6] } else { "text" }
          if format != "text" && format != "json" {
            usage()
            @sys.exit(2)
            return
          }
          let before_zone = @lib.parse_zone(before, args[5])
          let after_zone = @lib.parse_zone(after, args[5])
          let before_findings = @lib.validate_zone(before_zone)
          let after_findings = @lib.validate_zone(after_zone)
          if @lib.error_count(before_findings) > 0 ||
            @lib.error_count(after_findings) > 0 {
            if format == "json" {
              println(
                "{\"before\":\{@lib.render_diagnostics_json(before_findings)},\"after\":\{@lib.render_diagnostics_json(after_findings)}}",
              )
            } else {
              println(
                "Before zone:\n\{@lib.render_diagnostics_text(before_findings)}",
              )
              println(
                "After zone:\n\{@lib.render_diagnostics_text(after_findings)}",
              )
            }
            @sys.exit(1)
            return
          }
          let diff = @lib.compare_zones(before_zone, after_zone)
          if args[2] == "impact" {
            let impact = @lib.assess_impact(diff, args[5])
            if format == "json" {
              println(@lib.render_impact_json(impact))
            } else if format == "text" {
              println(@lib.render_impact_text(impact))
            } else {
              usage()
              @sys.exit(2)
            }
          } else if format == "json" {
            println(@lib.render_diff_json(diff))
          } else if format == "text" {
            println(@lib.render_diff_text(diff))
          } else {
            usage()
            @sys.exit(2)
          }
          if @lib.error_count(diff.diagnostics) > 0 {
            @sys.exit(1)
          }
        }
        _ => ()
      }
    }
    "stats" => {
      if args.length() < 5 {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        None => return
        Some(source) => {
          let zone = @lib.parse_zone(source, args[4])
          let findings = @lib.validate_zone(zone)
          let inventory = @lib.inventory_zone(zone)
          let format = if args.length() > 5 { args[5] } else { "text" }
          if format == "json" {
            println(@lib.render_inventory_json(inventory))
          } else if format == "text" {
            println(@lib.render_inventory_text(inventory))
            println("errors: \{@lib.error_count(findings)}")
            println("warnings: \{@lib.warning_count(findings)}")
          } else {
            usage()
            @sys.exit(2)
          }
          if @lib.error_count(findings) > 0 {
            @sys.exit(1)
          }
        }
      }
    }
    "trace" => {
      if args.length() < 6 {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        Some(source) => {
          let trace = @lib.trace_alias(
            @lib.parse_zone(source, args[4]),
            args[5],
          )
          for name in trace.path {
            println(name)
          }
          if trace.cycle {
            println("cycle returns to \{trace.terminal}")
            @sys.exit(1)
          }
        }
        None => ()
      }
    }
    "normalize" => {
      if args.length() < 5 {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        Some(source) => {
          let zone = @lib.parse_zone(source, args[4])
          if @lib.error_count(zone.diagnostics) > 0 {
            println(@lib.render_diagnostics_text(zone.diagnostics))
            @sys.exit(1)
          }
          println(@lib.render_canonical_zone(zone))
        }
        None => ()
      }
    }
    "lookup" => {
      if args.length() < 7 {
        usage()
        @sys.exit(2)
        return
      }
      match load(args[3]) {
        Some(source) => {
          let result = @lib.lookup_zone(
            @lib.parse_zone(source, args[4]),
            args[5],
            args[6],
          )
          let format = if args.length() > 7 { args[7] } else { "text" }
          if format == "json" {
            println(@lib.render_lookup_json(result))
          } else if format == "text" {
            println(@lib.render_lookup_text(result))
          } else {
            usage()
            @sys.exit(2)
          }
          if result.status == "alias-cycle" || result.status == "invalid-name" {
            @sys.exit(1)
          }
        }
        None => ()
      }
    }
    "policy" => {
      if args.length() < 6 {
        usage()
        @sys.exit(2)
        return
      }
      match (load(args[3]), load(args[5])) {
        (Some(source), Some(config)) => {
          let parsed = @lib.parse_policy(config)
          let findings = @lib.validate_with_policy(
            @lib.parse_zone(source, args[4]),
            parsed.policy,
          )
          for item in parsed.diagnostics {
            findings.push(item)
          }
          let format = if args.length() > 6 { args[6] } else { "text" }
          match format {
            "json" => println(@lib.render_diagnostics_json(findings))
            "markdown" => println(@lib.render_diagnostics_markdown(findings))
            "text" => println(@lib.render_diagnostics_text(findings))
            _ => {
              usage()
              @sys.exit(2)
            }
          }
          if @lib.error_count(findings) > 0 {
            @sys.exit(1)
          }
        }
        _ => ()
      }
    }
    _ => {
      usage()
      @sys.exit(2)
    }
  }
}