///|
fn usage() -> String {
  (
    #|moon-uri-template — RFC 6570 Level 4 URI Template tools
    #|
    #|Usage:
    #|  moon-uri-template validate TEMPLATE
    #|  moon-uri-template variables TEMPLATE
    #|  moon-uri-template inspect TEMPLATE
    #|  moon-uri-template expand TEMPLATE --variables FILE.json
    #|
  )
}

///|
enum CliCommand {
  Validate(String)
  Variables(String)
  Inspect(String)
  Expand(String, String)
} derive(Debug, Eq)

///|
suberror CliError {
  InvalidInvocation(message~ : String)
} derive(Debug)

///|
impl Show for CliError with fn output(self, logger) {
  match self {
    InvalidInvocation(message~) => logger.write_string(message)
  }
}

///|
fn parse_command(args : ArrayView[String]) -> CliCommand raise CliError {
  if args.length() < 2 {
    raise InvalidInvocation(message="missing command")
  }
  let command = args[1]
  match command {
    "validate" | "variables" | "inspect" => {
      if args.length() != 3 {
        raise InvalidInvocation(
          message="'\{command}' expects exactly one TEMPLATE argument",
        )
      }
      let source = args[2]
      match command {
        "validate" => Validate(source)
        "variables" => Variables(source)
        _ => Inspect(source)
      }
    }
    "expand" => {
      if args.length() != 5 || args[3] != "--variables" {
        raise InvalidInvocation(
          message="'expand' expects TEMPLATE --variables FILE.json",
        )
      }
      Expand(args[2], args[4])
    }
    _ => raise InvalidInvocation(message="unknown command: \{command}")
  }
}

///|
fn render_template_summary(source : String, inspect~ : Bool) -> String raise {
  let template = @uri.UriTemplate::parse(source)
  if inspect {
    let variables = template.variables()
    let suffix = if variables.length() == 0 {
      ""
    } else {
      "\n" + variables.join("\n")
    }
    "source: \{template.source()}\n" +
    "level: \{template.level()}\n" +
    "variables:\{suffix}"
  } else {
    "valid RFC 6570 Level \{template.level()} template; " +
    "\{template.variables().length()} variable(s)"
  }
}

///|
fn expand_from_json(source : String, text : String) -> String raise {
  let template = @uri.UriTemplate::parse(source)
  let json = @json.parse(text)
  let variables = @uri.variables_from_json(json)
  template.expand(variables)
}

///|
fn execute_command(command : CliCommand) -> String raise {
  match command {
    Validate(source) => render_template_summary(source, inspect=false)
    Variables(source) => {
      let template = @uri.UriTemplate::parse(source)
      template.variables().join("\n")
    }
    Inspect(source) => render_template_summary(source, inspect=true)
    Expand(source, path) => {
      let text = @fs.read_file_to_string(path)
      expand_from_json(source, text)
    }
  }
}

///|
fn run(args : Array[String]) -> Unit raise {
  let output = execute_command(parse_command(args))
  if output != "" {
    println(output)
  }
}

///|
fn main {
  run(@sys.get_cli_args()) catch {
    InvalidInvocation(message~) => {
      println("error: \{message}")
      println(usage())
      @sys.exit(2)
    }
    error => {
      println("error: \{error}")
      @sys.exit(1)
    }
  }
}