///|
let default_config_path : String = "bw-config.json"

///|
let account_id_option : @admiral.OptionDef[String] = @admiral.string(
  "account-id",
  description="Cloudflare account ID",
  env="CLOUDFLARE_ACCOUNT_ID",
  config="account_id",
  required=true,
)

///|
let api_token_option : @admiral.OptionDef[String] = @admiral.string(
  "api-token",
  description="Cloudflare API token",
  env="CLOUDFLARE_API_TOKEN",
  config="api_token",
  required=true,
)

///|
let config_option : @admiral.OptionDef[String] = @admiral.string(
  "config",
  description="Path to JSON config file",
  default=Some(default_config_path),
)

///|
struct ConfigLoaderArgs {
  argv : Array[String]
  explicit_path : Bool
}

///|
fn config_loader_args(process_args : Array[String]) -> ConfigLoaderArgs {
  let argv = ["load"]
  let mut explicit_path = false
  let mut index = 1
  while index < process_args.length() {
    let argument = process_args[index]
    if argument == "--" {
      break
    } else if argument == "--config" {
      explicit_path = true
      argv.push(argument)
      if index + 1 < process_args.length() {
        argv.push(process_args[index + 1])
        index = index + 2
      } else {
        index = index + 1
      }
    } else if argument.has_prefix("--config=") {
      explicit_path = true
      argv.push(argument)
      index = index + 1
    } else {
      index = index + 1
    }
  }
  { argv, explicit_path }
}

///|
async fn load_config(
  process_args? : Array[String] = @env.args(),
) -> Map[String, Json] raise @admiral.ConfigLoadFailure {
  let loader_args = config_loader_args(process_args)
  let config_path = Ref(default_config_path)
  let loader = @admiral.CliApp::CliApp(name="bw-config-loader", commands=[
    @admiral.CommandDef::CommandDef(
      name="load",
      options=[config_option],
      run=Some(context => {
        config_path.val = context.get_string_required(config_option)
      }),
    ),
  ])
  loader.run(argv=Some(loader_args.argv), env=Map([])) catch {
    error =>
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(error.to_string())
  }
  let config_exists = @fs.exists(config_path.val) catch {
    error =>
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(
        "failed to inspect config file '\{config_path.val}': \{error}",
      )
  }
  if !config_exists {
    if loader_args.explicit_path {
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(
        "config file does not exist: \{config_path.val}",
      )
    }
    return Map([])
  }
  let text = @fs.read_file(config_path.val).text() catch {
    error =>
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(
        "failed to read config file '\{config_path.val}': \{error}",
      )
  }
  let parsed = @json.parse(text) catch {
    error =>
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(
        "invalid config JSON in '\{config_path.val}': \{error}",
      )
  }
  match parsed {
    Json::Object(object) => object
    _ =>
      raise @admiral.ConfigLoadFailure::ConfigLoadFailure(
        "config file must contain a JSON object",
      )
  }
}

///|
fn required_body_source(url : String?, html : String?) -> Unit raise {
  guard url is Some(_) || html is Some(_) else {
    fail("Either --url or --html is required")
  }
}

///|
async fn read_optional_file(path : String?) -> String? {
  match path {
    Some(path) => Some(@fs.read_file(path).text())
    None => None
  }
}

///|
struct SchemaResponseFormat {
  schema : Json
}

///|
let schema_response_format_schema_lens : @lens.Lens[Json] = @lens.root().json(
  "schema",
)

///|
let schema_response_format_type_lens : @lens.Lens[String] = @lens.root().string(
  "type",
)

///|
impl ToJson for SchemaResponseFormat with fn to_json(self) -> Json {
  let builder = @lens.JsonBuilder::JsonBuilder()
  schema_response_format_schema_lens.set_or_abort(builder, self.schema)
  schema_response_format_type_lens.set_or_abort(builder, "json_schema")
  builder.to_json()
}

///|
extend SchemaResponseFormat with ToJson::{to_json}

///|
async fn schema_response_format(path : String) -> SchemaResponseFormat {
  let schema = @json.parse(@fs.read_file(path).text()) catch {
    error if @async.is_being_cancelled() => raise error
    error => fail("Invalid JSON schema file: \{error}")
  }
  { schema, }
}