///|
suberror CliError {
  CliError(String)
}

///|
fn ensure_dir(path : String) -> Unit raise {
  if path == "" || path == "." || @fs.path_exists(path) {
    return
  }
  let normalized = path.replace(old="\\", new="/")
  let parts = normalized.split("/").to_array()
  let mut current = if normalized.has_prefix("/") { "/" } else { "" }
  for part in parts {
    if part == "" {
      continue
    }
    current = if current == "" || current == "/" {
      current + part.to_owned()
    } else {
      current + "/" + part.to_owned()
    }
    if !@fs.path_exists(current) {
      @fs.create_dir(current)
    }
  }
}

///|
fn safe_output_path(path : String) -> Bool {
  if path == "" ||
    path.has_prefix("/") ||
    path.contains("\\") ||
    path.contains(":") ||
    !path.has_suffix(".mbt") {
    return false
  }
  for part in path.split("/") {
    if part == "" || part == "." || part == ".." {
      return false
    }
  }
  true
}

///|
fn run() -> Unit raise {
  let args = @stdio.args()
  let mut input : String? = None
  let mut output = "."
  let mut i = 1
  while i < args.length() {
    if args[i] == "--help" {
      println("capnpc-moonbit 0.3.1 — Cap'n Proto compiler plugin")
      println(
        "Usage: capnp compile -o/path/to/capnpc-moonbit[:OUTPUT] schema.capnp",
      )
      println("Or: capnpc-moonbit --request REQUEST.bin --out OUTPUT")
      return
    }
    if args[i] == "--version" {
      println("capnpc-moonbit 0.3.1")
      return
    }
    guard i + 1 < args.length() else {
      raise CliError("missing value for \{args[i]}")
    }
    match args[i] {
      "--request" => input = Some(args[i + 1])
      "--out" => output = args[i + 1]
      _ => raise CliError("unknown argument: \{args[i]}")
    }
    i += 2
  }
  let request = match input {
    Some(path) => @fs.read_file_to_bytes(path)
    None => @stdio.read_stdin()
  }
  // Generate and validate every output before creating or replacing any file.
  let files = @codegen.generate(request)
  for file in files {
    guard safe_output_path(file.path) else {
      raise CliError("unsafe generated path: \{file.path}")
    }
  }
  ensure_dir(output)
  for file in files {
    let parts = file.path.split("/").to_array()
    let parents = parts[:parts.length() - 1].to_owned().join("/")
    if parents != "" {
      ensure_dir(output + "/" + parents)
    }
    @fs.write_string_to_file(output + "/" + file.path, file.content)
  }
  let package_path = output + "/moon.pkg"
  if !@fs.path_exists(package_path) {
    @fs.write_string_to_file(
      package_path, "import {\n  \"Ridge-Lab/capnp\",\n}\n",
    )
  }
}

///|
fn main {
  run() catch {
    error => @stdio.fail("capnpc-moonbit: \{error}")
  }
}