///|
let version : String = "0.1.0"

///|
let about : String =
  #|Render PlantUML diagrams as SVG.
  #|
  #|Run it from mooncakes.io without installing (the binary is fetched and
  #|cached on first use; pin a version with moonbit-community/uml_cli@):
  #|
  #|  moonx moonbit-community/uml_cli diagram.puml > diagram.svg
  #|  moonx moonbit-community/uml_cli render diagram.puml -o diagram.svg
  #|  moonx moonbit-community/uml_cli check diagram.puml
  #|
  #|Exit codes:
  #|  0  success
  #|  1  the file could not be read or rendered; a human-readable error is
  #|     printed to stdout
  #|  2  usage error (unknown subcommand or missing file argument)
  #|
  #|`render` prints the SVG to stdout unless `--output` names a file; it never
  #|rewrites the input file.

///|
fn output_option() -> @argparse.OptionArg {
  @argparse.OptionArg::OptionArg(
    "output",
    short='o',
    long="output",
    about="Write the SVG to this path instead of stdout.",
  )
}

///|
fn uml_command() -> @argparse.Command {
  Command(
    "uml_cli",
    about~,
    version~,
    arg_required_else_help=true,
    positionals=[PositionArg("file", about="Render PlantUML and print SVG.")],
    options=[output_option()],
    subcommands=[
      Command(
        "render",
        about="Render PlantUML and print SVG.",
        positionals=[
          PositionArg(
            "file",
            about="PlantUML file to render.",
            num_args=@argparse.ValueRange::single(),
          ),
        ],
        options=[output_option()],
      ),
      Command(
        "check",
        about="Parse PlantUML and report the detected diagram kind.",
        positionals=[
          PositionArg(
            "file",
            about="PlantUML file to validate.",
            num_args=@argparse.ValueRange::single(),
          ),
        ],
      ),
    ],
  )
}

///|
fn main {
  let code = run(@env.args()[1:])
  if code != 0 {
    @sys.exit(code)
  }
}

///|
fn run(args : ArrayView[String]) -> Int {
  let matches = @argparse.parse(uml_command(), argv=args, env=Map([])) catch {
    err => {
      println(err)
      return 2
    }
  }
  match matches.subcommand {
    Some(("render", child)) => dispatch_file(child, render_file)
    Some(("check", child)) => dispatch_file(child, check_file)
    Some((name, _)) => {
      println("error: unsupported command `\{name}`")
      2
    }
    None => dispatch_file(matches, render_file)
  }
}

///|
fn dispatch_file(
  matches : @argparse.Matches,
  action : (String, @argparse.Matches) -> Int,
) -> Int {
  match matches.values.get("file") {
    Some([path]) => action(path, matches)
    _ => 2
  }
}

///|
fn render_file(path : String, matches : @argparse.Matches) -> Int {
  let source = @fs.read_file_to_string(path) catch {
    IOError(message) => {
      println("error: failed to read \{path}: \{message}")
      return 1
    }
  }
  let svg = @api.render_svg(source) catch {
    err => {
      println(render_error(path, "render", err))
      return 1
    }
  }
  match matches.values.get("output") {
    Some([target]) =>
      try @fs.write_string_to_file(target, svg) catch {
        IOError(message) => {
          println("error: failed to write \{target}: \{message}")
          1
        }
      } noraise {
        _ => 0
      }
    _ => {
      println(svg)
      0
    }
  }
}

///|
fn check_file(path : String, _matches : @argparse.Matches) -> Int {
  let source = @fs.read_file_to_string(path) catch {
    IOError(message) => {
      println("error: failed to read \{path}: \{message}")
      return 1
    }
  }
  let document = @api.parse(source) catch {
    err => {
      println(render_error(path, "parse", err))
      return 1
    }
  }
  println("\{path}: OK (\{kind_name(document.kind())} diagram)")
  0
}

///|
fn render_error(path : String, action : String, err : Error) -> String {
  match err {
    @parse.SyntaxError(line, message) => "error: \{path}:\{line}: \{message}"
    _ => "error: failed to \{action} \{path}: \{err}"
  }
}

///|
fn kind_name(kind : @api.DiagramKind) -> String {
  match kind {
    Sequence => "sequence"
    Class => "class"
    Activity => "activity"
    State => "state"
    Component => "component"
    Object => "object"
    UseCase => "use case"
    Mindmap => "mindmap"
    Json => "json"
    Yaml => "yaml"
    Toml => "toml"
  }
}