///|
/// Maps a dump library error to a bounded protocol failure.
fn dump_failure(error : Error, file : String) -> CliFailure {
  match error {
    @dump.DumpCancelled =>
      CliFailure(@lib.protocol_error("office.cancelled", "dump was cancelled"))
    @dump.DumpUnsupported(reason) =>
      CliFailure(
        @lib.protocol_error(
          "office.dump.unsupported",
          "cannot dump this package: " + bounded_text(reason, 200),
          details=Json::object({ "file": Json::string(bounded_text(file, 160)) }),
        ),
      )
    @dump.DumpSelfCheckFailed(reason) =>
      CliFailure(
        @lib.protocol_error(
          "office.dump.self_check_failed",
          "dump produced an op the batch engine rejects: " +
          bounded_text(reason, 200),
          details=Json::object({ "file": Json::string(bounded_text(file, 160)) }),
        ),
      )
    _ =>
      CliFailure(
        @lib.protocol_error(
          "office.dump.failed",
          "dump failed: " + bounded_text(error.to_string(), 200),
          details=Json::object({ "file": Json::string(bounded_text(file, 160)) }),
        ),
      )
  }
}

///|
/// Opens a DOCX package through the same tolerant, budgeted annotated
/// reader the validate/issues commands use.
fn open_docx_annotated_package(
  source : OfficeReadPackage,
) -> @docx.DocxAnnotatedResult raise CliFailure {
  let main_document_part = validate_docx_read_archive(
    source.file,
    source.archive,
    cancelled=office_async_cancelled,
  )
  let xml_budget = @xml.xml_read_budget(
    max_source_units=docx_cli_max_xml_source_units,
    max_tokens=docx_cli_max_xml_tokens,
    max_materialized_chars=docx_cli_max_xml_materialized_chars,
    max_token_chars=docx_cli_max_xml_token_chars,
    cancelled=office_async_cancelled,
  )
  @docx.read_docx_annotated_archive_tolerant_limited(
    source.archive,
    xml_budget,
    max_diagnostics=docx_cli_max_warnings,
    max_diagnostic_chars=office_finding_message_chars,
    expected_main_document_path=main_document_part,
  ) catch {
    error => {
      check_office_read_cancelled(office_async_cancelled)
      raise docx_reader_failure(error, source.file)
    }
  }
}

///|
async fn run_dump(matches : @argparse.Matches) -> Unit {
  let file = required_value(matches, "file")
  let mode = office_validate_output_mode(matches)
  let source = read_office_package(file, cancelled=office_async_cancelled)
  let envelope = match source.format {
    Xlsx => {
      let workbook = open_xlsx_read_package(
        source,
        cancelled=office_async_cancelled,
      )
      @dump.dump_xlsx_workbook(
        workbook,
        source_file=file,
        source_bytes=source.bytes.length(),
        source_sha256=Some(@lib.sha256_hex(source.bytes)),
        cancelled=office_async_cancelled,
      ) catch {
        error if office_async_cancelled() => raise error
        error => raise dump_failure(error, file)
      }
    }
    Docx => {
      let annotated = open_docx_annotated_package(source)
      @dump.dump_docx_document(
        annotated,
        source_file=file,
        source_bytes=source.bytes.length(),
        source_sha256=Some(@lib.sha256_hex(source.bytes)),
        cancelled=office_async_cancelled,
      ) catch {
        error if office_async_cancelled() => raise error
        error => raise dump_failure(error, file)
      }
    }
  }
  match mode {
    JsonDocument => println(envelope.to_json_document().stringify(indent=2))
    JsonLines =>
      for line in envelope.to_jsonl_lines() {
        println(line)
      }
    Human =>
      println(
        "\{source.format.name()} dump: \{envelope.ops.length()} op(s), " +
        "\{envelope.residual.length()} residual, " +
        "\{envelope.warnings.length()} warning(s)",
      )
  }
}

///|
fn dump_command() -> @argparse.Command {
  let summary = match @lib.find_capability_command("dump") {
    Some(command) => command.summary
    None => "Dump a package as a replayable semantic op stream"
  }
  Command(
    "dump",
    about=summary,
    positionals=[
      PositionArg(
        "file",
        about="path to an .xlsx or .docx file",
        num_args=@argparse.ValueRange::single(),
      ),
    ],
    flags=[
      FlagArg(
        "json",
        long="json",
        about="print the office.dump/1 JSON document",
      ),
      FlagArg(
        "jsonl",
        long="jsonl",
        about="print the streaming office.dump/1 JSONL form",
      ),
    ],
  )
}