///|
fn pdf_flag(matches : @argparse.Matches, name : String) -> Bool {
  if matches.flags.get_or_default(name, false) {
    return true
  }
  matches.subcommand.map_or(false, child => pdf_flag(child.1, name))
}

///|
fn pdf_operation(matches : @argparse.Matches) -> String {
  guard matches.subcommand is Some((name, child)) else { return "" }
  let action = pdf_operation(child)
  if action == "" {
    name
  } else {
    name + " " + action
  }
}

///|
async fn run_pdf(matches : @argparse.Matches) -> Unit {
  let mode = if pdf_flag(matches, "jsonl") {
    JsonLines
  } else if pdf_flag(matches, "json") {
    JsonDocument
  } else {
    Human
  }
  let dry_run = pdf_flag(matches, "dry-run")
  let overwrite = pdf_flag(matches, "overwrite")
  let result = @pdf_cli.execute(matches) catch {
    error if @async.is_being_cancelled() => raise error
    @pdf_cli.PdfCliError::Usage(message) =>
      raise CliFailure(
        @lib.protocol_error(
          "office.invalid_arguments",
          bounded_text(message, 240),
        ),
      )
    @pdf_cli.PdfCliError::Runtime(message) =>
      raise CliFailure(
        @lib.protocol_error("office.pdf.failed", bounded_text(message, 240)),
      )
    error =>
      raise unexpected_cli_failure("office.pdf.failed", "PDF operation", error)
  }
  let fields : Map[String, Json] = {
    "schema": Json::string("office.pdf/1"),
    "operation": Json::string(pdf_operation(matches)),
    "result": result.data,
    "dry_run": Json::boolean(dry_run),
    "committed": Json::boolean(false),
  }
  if result.artifact is Some(artifact) {
    fields["output"] = Json::string(artifact.path)
    fields["result_size"] = Json::number(artifact.bytes.length().to_double())
    // Use the same create-new publication primitive as Office render/preview.
    // Explicit replacement removes the old destination before staging; it is
    // not an atomic swap and a subsequent failure can leave the path absent.
    if !overwrite && @afs.exists(artifact.path) {
      raise CliFailure(
        @lib.protocol_error(
          "office.transaction.output_exists",
          "destination already exists; use --overwrite to replace it",
          details=Json::object({ "output": Json::string(artifact.path) }),
        ),
      )
    }
    if !dry_run {
      if overwrite {
        @afs.remove(artifact.path) catch {
          _ => ()
        }
      }
      @transaction.atomic_write_new(artifact.path, artifact.bytes) catch {
        @transaction.TransactionError(..) as error =>
          raise transaction_failure(error)
        error => raise error
      }
      fields["committed"] = Json::boolean(true)
    }
  }
  let report = @lib.output_success(Json::object(fields))
  match mode {
    JsonDocument => println(report.stringify(indent=2))
    JsonLines => println(report.stringify())
    Human =>
      if result.artifact is Some(artifact) {
        if dry_run {
          println("validated dry run: \{human_text(artifact.path, 512)}")
        } else {
          println("wrote \{human_text(artifact.path, 512)}")
        }
      } else {
        @stdio.stdout.write(result.text)
      }
  }
}