///|
fn option_type_to_string(t : OptionType) -> String {
  match t {
    StringOpt => "string"
    BoolOpt => "bool"
    IntOpt => "int"
  }
}

///|
fn option_def_to_json(opt : OptionDef) -> Json {
  let m : Map[String, Json] = Map([])
  m["type"] = option_type_to_string(opt.type_).to_json()
  m["description"] = opt.description.to_json()
  m["required"] = opt.required.to_json()
  if opt.type_ == BoolOpt {
    m["style"] = "flag".to_json()
  }
  if opt.short != no_short {
    m["short"] = opt.short.to_string().to_json()
  }
  match opt.default_value {
    Some(v) => m["default"] = v.to_json()
    None => ()
  }
  Json::object(m)
}

///|
fn positional_def_to_json(pos : PositionalDef) -> Json {
  let m : Map[String, Json] = Map([])
  m["description"] = pos.description.to_json()
  m["required"] = pos.required.to_json()
  Json::object(m)
}

///|
fn generate_example(cmd_path : String, options : Array[OptionDef]) -> String {
  let parts : Array[String] = [cmd_path]
  for opt in options {
    if opt.required {
      parts.push("--" + opt.name + " <" + opt.name + ">")
    }
  }
  parts.join(" ")
}

///|
fn command_def_to_json(cmd : CommandDef, cmd_path : String) -> Json {
  let m : Map[String, Json] = Map([])
  m["description"] = cmd.description.to_json()
  if cmd.options.length() > 0 {
    let opts : Map[String, Json] = Map([])
    for opt in cmd.options {
      opts[opt.name] = option_def_to_json(opt)
    }
    m["options"] = Json::object(opts)
  }
  if cmd.positionals.length() > 0 {
    let poss : Map[String, Json] = Map([])
    for pos in cmd.positionals {
      poss[pos.name] = positional_def_to_json(pos)
    }
    m["positionals"] = Json::object(poss)
  }
  if cmd.examples.length() > 0 {
    m["examples"] = Json::array(cmd.examples.map(fn(e) { e.to_json() }))
  } else if cmd.options.length() > 0 || cmd.positionals.length() > 0 {
    let ex = generate_example(cmd_path, cmd.options)
    m["examples"] = Json::array([ex.to_json()])
  }
  if cmd.subcommands.length() > 0 {
    let subs : Map[String, Json] = Map([])
    for sub in cmd.subcommands {
      let sub_path = cmd_path + " " + sub.name
      subs[sub.name] = command_def_to_json(sub, sub_path)
    }
    m["commands"] = Json::object(subs)
  }
  Json::object(m)
}

///|
pub fn CliApp::render_schema_json(self : CliApp) -> Json {
  let m : Map[String, Json] = Map([])
  m["name"] = self.name.to_json()
  m["version"] = self.version.to_json()
  m["description"] = self.description.to_json()
  if self.root_options.length() > 0 {
    let opts : Map[String, Json] = Map([])
    for opt in self.root_options {
      opts[opt.name] = option_def_to_json(opt)
    }
    m["options"] = Json::object(opts)
  }
  if self.commands.length() > 0 {
    let cmds : Map[String, Json] = Map([])
    for cmd in self.commands {
      let cmd_path = self.name + " " + cmd.name
      cmds[cmd.name] = command_def_to_json(cmd, cmd_path)
    }
    m["commands"] = Json::object(cmds)
  }
  Json::object(m)
}

///|
pub fn CliApp::render_schema(self : CliApp) -> String {
  self.render_schema_json().stringify()
}