///|
fn[T] written(result : Result[T, @ar.WriteError]) -> T raise {
  match result {
    Ok(value) => value
    Err(error) => fail("\{error.code}: \{error.message}")
  }
}

///|
fn[T] parsed(result : Result[T, @ar.ParseError]) -> T raise {
  match result {
    Ok(value) => value
    Err(error) => fail("\{error.code} at \{error.offset}: \{error.message}")
  }
}

///|
// The explicit allowlist supplies bytes; archive paths never open files here.
fn resolve_thin(args : Array[String], start : Int32) -> @ar.ThinArchive raise {
  let archive = parsed(@ar.parse_thin(@fs.read_file_to_bytes(args[2])))
  let files : Map[Bytes, Bytes] = Map([])
  let mut i = start
  while i < args.length() {
    files[@utf8.encode(args[i])] = @fs.read_file_to_bytes(args[i + 1])
    i += 2
  }
  let entries : Array[Json] = []
  let mut builder = @ar.ArchiveBuilder::new()
  for i = 0; i < archive.member_count(); i = i + 1 {
    let entry = archive.get_member(i).unwrap()
    if entry.kind != @ar.Regular {
      continue
    }
    let payload = parsed(
      archive.load_member(i, fn(path, _limit) {
        match files.get(path) {
          Some(bytes) => Ok(bytes)
          None => Err("member is absent from the supplied file map")
        }
      }),
    )
    entries.push(
      Json::object({
        "name_hex": Json(entry.name_hex()),
        "size": Json(payload.length()),
      }),
    )
    builder = written(
      builder.append(
        @ar.WriteMember::new(entry.name, payload, metadata=entry.metadata),
      ),
    )
  }
  if args[1] == "materialize" {
    // This data-container example deliberately writes without a symbol index.
    // Static libraries can pass a fresh WriteSymbol mapping to write().
    @fs.write_bytes_to_file(args[3], written(builder.write()))
  }
  println(
    Json::object({
      "members": Json::array(entries),
      "symbol_count": Json(
        archive
        .symbol_indexes()
        .fold(init=0, fn(total, index) { total + index.symbols.length() }),
      ),
    }).stringify(),
  )
  archive
}

///|
fn workflow(args : Array[String]) -> Unit raise {
  if args.length() == 4 && args[1] == "create" {
    let format = match args[3] {
      "gnu" => @ar.Gnu
      "bsd" => @ar.Bsd
      _ => fail("expected gnu or bsd")
    }
    let mut builder = @ar.ArchiveBuilder::new()
    for
      (name, data) in [
        (b"short.txt", b"hello\n"),
        (b"a_long_member_name_for_testing.bin", b"\x00\xff\x0a"),
        (b"same.txt", b"first"),
        (b"same.txt", b"second"),
        (b"empty", b""),
      ] {
      builder = written(builder.append(@ar.WriteMember::new(name, data)))
    }
    @fs.write_bytes_to_file(args[2], written(builder.write(format~)))
  } else if args.length() == 4 && args[1] == "edit" {
    let archive = parsed(@ar.parse(@fs.read_file_to_bytes(args[2])))
    let mut builder = written(archive.edit())
    builder = written(
      builder.append(@ar.WriteMember::new(b"added.txt", b"appended")),
    )
    builder = written(
      builder.replace(0, @ar.WriteMember::new(b"short.txt", b"updated")),
    )
    builder = written(builder.remove(1))
    builder = written(builder.move_member(2, 0))
    @fs.write_bytes_to_file(args[3], written(builder.write()))
  } else if args.length() == 5 && args[1] == "indexed" {
    let mut builder = @ar.ArchiveBuilder::new()
    builder = written(
      builder.append(
        @ar.WriteMember::new(
          b"a_long_alpha_object_name.o",
          @fs.read_file_to_bytes(args[3]),
        ),
      ),
    )
    builder = written(
      builder.append(
        @ar.WriteMember::new(b"beta.o", @fs.read_file_to_bytes(args[4])),
      ),
    )
    @fs.write_bytes_to_file(
      args[2],
      written(
        builder.write(
          symbols=Some([
            { name: b"moon_alpha", member_index: 0, },
            { name: b"moon_beta", member_index: 1, },
          ]),
        ),
      ),
    )
  } else if args.length() == 5 && args[1] == "reindex" {
    let archive = parsed(@ar.parse(@fs.read_file_to_bytes(args[2])))
    let mut builder = written(archive.edit(drop_symbol_indexes=true))
    builder = written(
      builder.replace(
        0,
        @ar.WriteMember::new(
          b"replacement_alpha.o",
          @fs.read_file_to_bytes(args[4]),
        ),
      ),
    )
    builder = written(builder.move_member(0, 1))
    @fs.write_bytes_to_file(
      args[3],
      written(
        builder.write(
          symbols=Some([
            { name: b"moon_alpha", member_index: 1, },
            { name: b"moon_beta", member_index: 0, },
          ]),
        ),
      ),
    )
  } else if args.length() >= 3 &&
    args[1] == "thin" &&
    (args.length() - 3) % 2 == 0 {
    ignore(resolve_thin(args, 3))
  } else if args.length() >= 4 &&
    args[1] == "materialize" &&
    (args.length() - 4) % 2 == 0 {
    ignore(resolve_thin(args, 4))
  } else {
    fail(
      "Usage: archive_workflow create OUT gnu|bsd; edit IN OUT; indexed OUT ALPHA_OBJECT BETA_OBJECT; reindex IN OUT REPLACEMENT_ALPHA; thin IN [MEMBER FILE]...; materialize IN OUT [MEMBER FILE]...",
    )
  }
}

///|
fn main {
  workflow(@env.args()) catch {
    error => {
      eprintln(error)
      @sys.exit(1)
    }
  }
}