///|
fn first_value(args : Array[String], name : String) -> String? {
  let mut index = 0
  while index < args.length() {
    if args[index] == name && index + 1 < args.length() {
      return Some(args[index + 1])
    }
    index = index + 1
  }
  None
}

///|
fn flag_enabled(args : Array[String], name : String) -> Bool {
  args.any(value => value == name)
}

///|
fn decode_base64_text(
  encoded : String,
  label : String,
) -> Result[String, String] {
  let decoded = @base64.decode(encoded) catch {
    _ => return Err("invalid Base64 " + label)
  }
  let text = @utf8.decode(decoded) catch {
    _ => return Err("invalid UTF-8 " + label)
  }
  Ok(text)
}

///|
fn escape_json_string(input : String) -> String {
  let builder = StringBuilder::new()
  for char in input {
    match char {
      '"' => builder.write_string("\\\"")
      '\\' => builder.write_string("\\\\")
      '\n' => builder.write_string("\\n")
      '\r' => builder.write_string("\\r")
      '\t' => builder.write_string("\\t")
      _ => builder.write_char(char)
    }
  }
  builder.to_string()
}

///|
fn string_list_json(items : Array[String]) -> String {
  let builder = StringBuilder::new()
  builder.write_string("[")
  let mut index = 0
  while index < items.length() {
    if index > 0 {
      builder.write_string(",")
    }
    builder.write_string("\"")
    builder.write_string(escape_json_string(items[index]))
    builder.write_string("\"")
    index = index + 1
  }
  builder.write_string("]")
  builder.to_string()
}

///|
fn render_response_json(
  output : String,
  errors : Array[String],
  missing_variables : Array[String],
) -> String {
  let builder = StringBuilder::new()
  builder.write_string("{")
  builder.write_string("\"output\":\"")
  builder.write_string(escape_json_string(output))
  builder.write_string("\",")
  builder.write_string("\"errors\":")
  builder.write_string(string_list_json(errors))
  builder.write_string(",")
  builder.write_string("\"missing_variables\":")
  builder.write_string(string_list_json(missing_variables))
  builder.write_string("}")
  builder.to_string()
}

///|
fn error_response_json(message : String) -> String {
  render_response_json("", [message], [])
}

///|
fn effective_argv() -> Array[String] {
  let args = @env.args()
  if args.length() > 2 && args[1].strip_suffix(".js") is Some(_) {
    args[2:].to_owned()
  } else if args.length() > 1 {
    args[1:].to_owned()
  } else {
    []
  }
}

///|
fn main {
  let args = effective_argv()
  let template_base64 = match first_value(args, "--template-base64") {
    Some(value) => value
    None => {
      println(
        error_response_json("missing required argument: --template-base64"),
      )
      return
    }
  }
  let context_base64 = match first_value(args, "--context-base64") {
    Some(value) => value
    None => {
      println(
        error_response_json("missing required argument: --context-base64"),
      )
      return
    }
  }
  let partials_base64 = match first_value(args, "--partials-base64") {
    Some(value) => value
    None => {
      println(
        error_response_json("missing required argument: --partials-base64"),
      )
      return
    }
  }

  let template = match decode_base64_text(template_base64, "template") {
    Ok(value) => value
    Err(message) => {
      println(error_response_json(message))
      return
    }
  }
  let context_json = match decode_base64_text(context_base64, "context JSON") {
    Ok(value) => value
    Err(message) => {
      println(error_response_json(message))
      return
    }
  }
  let partials_json = match
    decode_base64_text(partials_base64, "partials JSON") {
    Ok(value) => value
    Err(message) => {
      println(error_response_json(message))
      return
    }
  }

  let partials = match @src.parse_json_partials_checked(partials_json) {
    Ok(value) => value
    Err(message) => {
      println(error_response_json(message))
      return
    }
  }

  let render_options = if flag_enabled(args, "--strict-missing") {
    @src.RenderOptions::strict()
  } else {
    @src.RenderOptions::default()
  }

  let result = @src.render_json_checked_with_options(
    template, context_json, partials, render_options,
  )
  println(
    render_response_json(result.output, result.errors, result.missing_variables),
  )
}