///|
fn pptx_unsupported(command : String) -> CliFailure {
  CliFailure(
    @lib.protocol_error(
      "office.unsupported_format",
      "PPTX does not yet support office " + command,
    ),
  )
}

///|
fn pptx_create_command() -> @argparse.Command {
  Command(
    "pptx",
    about="Create a one-slide presentation atomically",
    positionals=[
      PositionArg(
        "output",
        about="new .pptx destination",
        num_args=@argparse.ValueRange::single(),
      ),
    ],
    options=[
      OptionArg(
        "title",
        long="title",
        about="optional title text (maximum 4096 UTF-16 code units)",
      ),
    ],
    flags=[
      FlagArg("dry-run", long="dry-run", about="validate without publishing"),
      FlagArg(
        "overwrite",
        long="overwrite",
        about="replace an existing regular-file destination",
      ),
      FlagArg("json", long="json", about="print office.output/1 JSON"),
    ],
  )
}

///|
async fn run_create_pptx(matches : @argparse.Matches) -> Unit {
  let output = required_value(matches, "output")
  let options = @transaction.create_transaction_options(
    output,
    dry_run=matches.flags.get_or_default("dry-run", false),
    overwrite=matches.flags.get_or_default("overwrite", false),
  ) catch {
    error => raise transaction_failure(error)
  }
  let report = @office_pptx.create_presentation(
    options,
    title=optional_value(matches, "title").unwrap_or(""),
  ) catch {
    @transaction.TransactionError(..) as error =>
      raise transaction_failure(error)
    error if office_async_cancelled() => raise error
    error =>
      raise unexpected_cli_failure(
        "office.pptx.create_failed", "PPTX creation", error,
      )
  }
  if matches.flags.get_or_default("json", false) {
    println(
      @lib.output_success(
        Json::object({
          "schema": Json::string("office.pptx.create/1"),
          "format": Json::string("pptx"),
          "output": Json::string(bounded_text(output, 160)),
          "slide_count": Json::number(1),
          "transaction": report.to_json(),
        }),
        warnings=transaction_warnings(report),
      ).stringify(indent=2),
    )
  } else {
    println(
      "\{human_transaction_status(report)}: one-slide PPTX -> \{human_text(output, 160)}",
    )
    print_transaction_warnings(report)
  }
}

///|
fn pptx_read_payload(
  file : String,
  summary : @office_pptx.PresentationSummary,
  text_mode : Bool,
  offset : Int32,
  limit : Int32,
  maximum : Int32,
) -> BoundedOfficePayload raise CliFailure {
  let records : Array[Json] = []
  let count = summary.slides.length()
  let data = Json::object({
    "schema": Json::string(
      if text_mode {
        "office.pptx.text/1"
      } else {
        "office.pptx.outline/1"
      },
    ),
    "file": Json::string(file),
    "format": Json::string("pptx"),
    "slide_count": Json::number(count.to_double()),
    "width_emu": Json::number(summary.width_emu.to_double()),
    "height_emu": Json::number(summary.height_emu.to_double()),
    "slides": Json::array(records),
  })
  if text_mode {
    let returned = if offset >= count {
      0
    } else if count - offset < limit {
      count - offset
    } else {
      limit
    }
    guard data is Object(fields) else {
      abort("PPTX payload must be an object")
    }
    fields["offset"] = Json::number(offset.to_double())
    fields["limit"] = Json::number(limit.to_double())
    fields["returned"] = Json::number(returned.to_double())
    fields["truncated"] = Json::boolean(offset + returned < count)
  }
  let payload = bounded_office_payload(data, [], maximum, "pptx")
  let start = if text_mode { offset } else { 0 }
  let end = if text_mode && offset + limit < count {
    offset + limit
  } else {
    count
  }
  for i = start; i < end; i = i + 1 {
    let slide = summary.slides[i]
    let record = if text_mode {
      Json::object({
        "index": Json::number(slide.index.to_double()),
        "part": Json::string(slide.part),
        "text": Json::string(slide.text),
        "notes": Json::string(slide.notes),
      })
    } else {
      Json::object({
        "index": Json::number(slide.index.to_double()),
        "part": Json::string(slide.part),
        "shape_count": Json::number(slide.shape_count.to_double()),
      })
    }
    payload.budget.reserve_array_item(record, !records.is_empty())
    records.push(record)
  }
  payload
}

///|
fn run_pptx_read(
  matches : @argparse.Matches,
  source : OfficeReadPackage,
  text_mode : Bool,
  max_elements : Int32,
  max_output : Int32,
  offset : Int32,
  limit : Int32,
) -> Unit raise {
  if optional_value(matches, "under") is Some(_) {
    raise CliFailure(
      @lib.protocol_error(
        "office.invalid_arguments", "PPTX text does not yet support --under; use --offset and --limit to select slides",
      ),
    )
  }
  let summary = @office_pptx.read_presentation(
    source.bytes,
    max_elements~,
    max_text_chars=if text_mode { max_output } else { 16 * 1024 * 1024 },
    cancelled=office_async_cancelled,
  ) catch {
    @lib.OfficeError::ResourceLimit(kind~, limit~, actual~) =>
      raise format_resource_failure("pptx", kind, limit, actual~)
    @docx_core.DocxError::ResourceLimit(limit~, ..) =>
      raise CliFailure(
        @lib.protocol_error(
          "office.pptx.resource_limit",
          "PPTX XML exceeds its read budget: " + limit.message(),
        ),
      )
    error if office_async_cancelled() => raise error
    error =>
      raise unexpected_cli_failure(
        "office.pptx.read_failed", "PPTX reading", error,
      )
  }
  if matches.flags.get_or_default("json", false) {
    emit_docx_success(
      checked_office_json_output(
        pptx_read_payload(
          source.file,
          summary,
          text_mode,
          offset,
          limit,
          max_output,
        ),
      ),
    )
  } else {
    let output = StringBuilder()
    let budget = OfficeOutputBudget::new(max_output, format="pptx")
    let start = if text_mode { offset } else { 0 }
    let end = if text_mode && offset + limit < summary.slides.length() {
      offset + limit
    } else {
      summary.slides.length()
    }
    for i = start; i < end; i = i + 1 {
      let slide = summary.slides[i]
      if text_mode {
        ensure_office_output_limit(slide.text, max_output, "pptx")
        ensure_office_output_limit(slide.notes, max_output, "pptx")
      }
      let line = if text_mode {
        "Slide \{slide.index}\n\{human_text(slide.text, max_output)}\nNotes: \{human_text(slide.notes, max_output)}\n"
      } else {
        "Slide \{slide.index}\t\{human_text(slide.part, max_output)}\t\{slide.shape_count} shapes\n"
      }
      budget.reserve_plain_text(line)
      output.write_string(line)
    }
    emit_docx_success(
      checked_office_human_output(output.to_string(), max_output, "pptx"),
    )
  }
}