// D3 CLI surface: `office create docx OUT` (blank) and the fresh-authoring
// `office batch --format docx OUT SCRIPT` variant. DOCX authoring is fresh-only
// (no existing-document mutation, #163/#95), so both route through the create
// transaction: fail-closed refuse-if-exists unless --overwrite. The XLSX
// `office batch FILE SCRIPT --out OUT` mutation path is unchanged.

///|
let office_docx_author_max_script_bytes : Int = 4 * 1024 * 1024

///|
let office_docx_author_max_image_bytes : Int = 8 * 1024 * 1024

///|
let office_docx_author_max_total_image_bytes : Int = 32 * 1024 * 1024

///|
fn docx_create_result_json(
  output : String,
  result : @office_docx.DocxCreateResult,
) -> Json {
  Json::object({
    "schema": Json::string(@lib.SCHEMA_DOCX_CREATE_RESULT),
    "format": Json::string("docx"),
    "output": Json::string(bounded_text(output, 160)),
    "transaction": result.transaction.to_json(),
  })
}

///|
fn docx_batch_result_json(
  output : String,
  result : @office_docx.DocxAuthorResult,
) -> Json {
  Json::object({
    "schema": Json::string(@lib.SCHEMA_DOCX_BATCH_RESULT),
    "format": Json::string("docx"),
    "output": Json::string(bounded_text(output, 160)),
    "ops": Json::number(result.report.ops.to_double()),
    "comments": Json::number(result.report.comments.to_double()),
    "footnotes": Json::number(result.report.footnotes.to_double()),
    "endnotes": Json::number(result.report.endnotes.to_double()),
    "headers": Json::number(result.report.headers.to_double()),
    "footers": Json::number(result.report.footers.to_double()),
    "transaction": result.transaction.to_json(),
  })
}

///|
fn checked_docx_create_options(
  matches : @argparse.Matches,
  output : String,
) -> @transaction.CreateTransactionOptions raise CliFailure {
  @transaction.create_transaction_options(
    output,
    dry_run=matches.flags.get_or_default("dry-run", false),
    overwrite=matches.flags.get_or_default("overwrite", false),
  ) catch {
    TransactionError(..) as error => raise transaction_failure(error)
  }
}

///|
async fn run_create_docx(matches : @argparse.Matches) -> Unit {
  let output = required_value(matches, "output")
  guard output.to_lower().has_suffix(".docx") else {
    raise CliFailure(
      @lib.protocol_error(
        "office.invalid_arguments", "office create docx requires a .docx destination",
      ),
    )
  }
  let options = checked_docx_create_options(matches, output)
  let result = @office_docx.create_blank_document(options) catch {
    @transaction.TransactionError(..) as error =>
      raise transaction_failure(error)
    error if @async.is_being_cancelled() => raise error
    error =>
      raise unexpected_cli_failure(
        "office.docx.create_failed", "DOCX creation", error,
      )
  }
  if matches.flags.get_or_default("json", false) {
    println(
      @lib.output_success(
        docx_create_result_json(output, result),
        warnings=transaction_warnings(result.transaction),
      ).stringify(indent=2),
    )
  } else {
    println(
      "\{human_transaction_status(result.transaction)}: blank DOCX -> \{human_text(output, 160)}",
    )
    print_transaction_warnings(result.transaction)
  }
}

///|
/// Loads the image bytes a fresh-authoring script references, bounded per image
/// and in aggregate. A missing or oversized asset is a typed refusal — nothing
/// is published.
async fn load_docx_authoring_images(
  script : @docx_batch.BatchScript,
) -> Map[String, Bytes] {
  let images : Map[String, Bytes] = Map([])
  let mut total = 0
  for path in script.image_paths() {
    let at = match script.image_reference(path) {
      Some(reference) => " (referenced at \{reference})"
      None => ""
    }
    // read_bounded_file checks the file size BEFORE reading, so an oversized
    // asset is refused without materializing it.
    let bytes = read_bounded_file(
      path,
      office_docx_author_max_image_bytes,
      "office.docx.author_asset_read_failed",
      "image '\{bounded_text(path, 160)}'\{at}",
      limit_code="office.docx.resource_limit",
    )
    total += bytes.length()
    if total > office_docx_author_max_total_image_bytes {
      raise CliFailure(
        @lib.protocol_error(
          "office.docx.resource_limit",
          "the authoring script's images exceed the \{office_docx_author_max_total_image_bytes}-byte aggregate ceiling",
        ),
      )
    }
    images[path] = bytes
  }
  images
}

///|
async fn run_docx_batch(matches : @argparse.Matches) -> Unit {
  let output = required_value(matches, "target")
  let script_path = required_value(matches, "script")
  guard output.to_lower().has_suffix(".docx") else {
    raise CliFailure(
      @lib.protocol_error(
        "office.invalid_arguments", "office batch --format docx requires a .docx destination",
      ),
    )
  }
  // Fresh DOCX authoring publishes to the positional destination; --out is an
  // XLSX-only mutation concept and is rejected here to keep the surface honest.
  guard optional_value(matches, "out") is None else {
    raise CliFailure(
      @lib.protocol_error(
        "office.invalid_arguments", "office batch --format docx does not accept --out (the destination is the positional target)",
      ),
    )
  }
  let options = checked_docx_create_options(matches, output)
  let script_bytes = read_bounded_file(
    script_path,
    office_docx_author_max_script_bytes,
    "office.docx.author_script_read_failed",
    "authoring script",
    limit_code="office.docx.resource_limit",
  )
  let script_text = @utf8.decode(script_bytes, ignore_bom=true) catch {
    _ =>
      raise CliFailure(
        @lib.protocol_error(
          "office.docx.batch_parse", "the authoring script is not valid UTF-8",
        ),
      )
  }
  let script = @office_docx.parse_docx_authoring_script(script_text) catch {
    DocxAuthorParseError(reason) =>
      raise CliFailure(
        @lib.protocol_error(
          "office.docx.batch_parse",
          "invalid authoring script: " + bounded_text(reason, 240),
        ),
      )
  }
  let images = load_docx_authoring_images(script)
  let result = @office_docx.author_document(options, script, images) catch {
    @transaction.TransactionError(..) as error =>
      raise transaction_failure(error)
    error if @async.is_being_cancelled() => raise error
    error =>
      raise unexpected_cli_failure(
        "office.docx.author_failed", "DOCX authoring", error,
      )
  }
  if matches.flags.get_or_default("json", false) {
    println(
      @lib.output_success(
        docx_batch_result_json(output, result),
        warnings=transaction_warnings(result.transaction),
      ).stringify(indent=2),
    )
  } else {
    println(
      "\{human_transaction_status(result.transaction)}: authored DOCX (\{result.report.ops} op(s)) -> \{human_text(output, 160)}",
    )
    print_transaction_warnings(result.transaction)
  }
}