///|
fn read_input(path : String) -> String raise {
  let text = @fs.read_file_to_string(path)
  if text.length() > 16777216 {
    raise @sdmx.Limit(resource="CLI text", maximum=16777216)
  }
  text
}

///|
fn usage() -> Unit {
  println(
    "Append --output NEW_PATH for exact UTF-8 file output (existing paths refused).",
  )
  println("moonsdmx json2csv [--drop-annotations] MESSAGE.json")
  println("moonsdmx extract-structure MESSAGE.json")
  println("moonsdmx validate-csv STRUCTURE.json DATA.csv")
  println(
    "moonsdmx csv2json STRUCTURE.json DATA.csv MESSAGE_ID SENDER PREPARED_UTC",
  )
}

///|
fn run(original : Array[String]) -> Unit raise {
  let size = original.length()
  let destination = if size >= 4 && original[size - 2] == "--output" {
    Some(original[size - 1])
  } else {
    None
  }
  let args = if destination is Some(_) {
    original[:size - 2].to_owned()
  } else {
    original
  }
  if destination is Some(path) && @fs.path_exists(path) {
    raise @sdmx.Conflict(key=path, reason="output already exists")
  }
  if args.length() < 2 {
    usage()
    @sys.exit(2)
    return
  }
  let output = match args[1] {
    "json2csv" if args.length() == 3 =>
      @sdmx.decode_json2(read_input(args[2])).encode_csv2()
    "json2csv" if args.length() == 4 && args[2] == "--drop-annotations" =>
      @sdmx.decode_json2(read_input(args[3]))
      .without_annotations()
      .encode_csv2()
    "reencode-json2" if args.length() == 6 =>
      @sdmx.decode_json2(read_input(args[2])).encode_json2(
        id=args[3],
        sender=args[4],
        prepared=args[5],
      )
    "extract-structure" if args.length() == 3 =>
      @sdmx.decode_json2(read_input(args[2]))
      .dataset()
      .structure()
      .to_profile_json()
    "validate-csv" if args.length() == 4 => {
      let structure = @sdmx.Structure::from_profile_json(read_input(args[2]))
      let message = @sdmx.decode_csv2(structure, read_input(args[3]))
      "valid Information message: \{message.dataset().length()} observations; source=\{message.reference().format()}"
    }
    "csv2json" if args.length() == 7 => {
      let structure = @sdmx.Structure::from_profile_json(read_input(args[2]))
      @sdmx.decode_csv2(structure, read_input(args[3])).encode_json2(
        id=args[4],
        sender=args[5],
        prepared=args[6],
      )
    }
    _ => {
      usage()
      @sys.exit(2)
      return
    }
  }
  // Exact file output avoids Windows console text-mode newline translation.
  match destination {
    Some(path) => @fs.write_string_to_file(path, output)
    None => {
      let display = output.replace_all(old="\r\n", new="\n")
      println(
        if display.has_suffix("\n") {
          display[:display.length() - 1].to_owned()
        } else {
          display
        },
      )
    }
  }
}

///|
fn main {
  run(@env.args()) catch {
    @sdmx.Invalid(path~, reason~) => {
      println("ERROR: \{path}: \{reason}")
      @sys.exit(1)
    }
    @sdmx.Limit(resource~, maximum~) => {
      println("ERROR: \{resource}: limit \{maximum} exceeded")
      @sys.exit(1)
    }
    @sdmx.Unsupported(feature~) => {
      println("ERROR: unsupported profile: \{feature}")
      @sys.exit(1)
    }
    @sdmx.Conflict(key~, reason~) => {
      println("ERROR: \{key}: \{reason}")
      @sys.exit(1)
    }
    error => {
      println("ERROR: \{error.to_string()}")
      @sys.exit(1)
    }
  }
}