---
title: "Batch execution with zot"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Batch execution with zot}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
```

`zot` provides a preview, approval-record, execution, ledger, and verification
sequence for batch plans. This vignette runs the sequence against an in-memory
request performer. It makes no network requests and changes no Zotero library.

## Create a plan

A plan is a `data.table` with one action per row. Supported actions are
`"patch"`, `"create"`, and `"trash"`. Patch and Trash rows identify an
existing item; create rows contain a new-item payload.

```{r}
library(data.table)
library(zot)

Plan <- data.table(
  action = "patch",
  library = "user",
  itemKey = "ABCD1234",
  payload = list(list(title = "A reviewed title"))
)

Preview <- zotPreview(Plan, describe = "Correct one reviewed title")
Preview
```

`zotPreview()` validates the required columns and action values, copies the
rows, and records a SHA-256 digest. It does not make an API request.

## Create an approval record

`zotApprove()` stores the preview digest, optional notes, and a timestamp.

```{r}
Approval <- zotApprove(Preview, notes = "Offline documentation example")
identical(Approval$digest, Preview$digest)
```

The function records application metadata; it does not authenticate a person
or determine whether execution is authorized. The executor requires the
approval and preview digests to match. Callers should not modify
`preview$plan` after creating the approval because the current executor does
not recalculate its digest.

## Execute with a ledger

The performer below models a versioned Zotero item. It supports the GET and
PATCH calls used by the example.

```{r}
Store <- new.env(parent = emptyenv())
Store$version <- 1L
Store$title <- "Original title"

Performer <- function(config, method, path, query, body, version) {
  if (method == "GET") {
    return(list(
      status = 200L,
      headers = list(),
      body = list(
        version = Store$version,
        data = list(title = Store$title)
      )
    ))
  }

  if (method == "PATCH" && identical(as.integer(version), Store$version)) {
    Store$title <- body$title
    Store$version <- Store$version + 1L
    return(list(status = 204L, headers = list(), body = NULL))
  }

  list(status = 412L, headers = list(), body = NULL)
}

Config <- zotConfig(
  userID = "42",
  key = "example-key",
  performer = Performer
)

LedgerPath <- tempfile(fileext = ".json")
Ledger <- zotExecute(
  Config,
  preview = Preview,
  approval = Approval,
  ledgerPath = LedgerPath
)

Ledger$items$ABCD1234$status
Store$title
```

For a patch row, `zotExecute()` reads the current item version, sends a
conditional patch, reads the item again, and compares the requested fields.
A matching result is recorded as `"done"`. Create rows are recorded as done
after the new item can be read; Trash rows use the status returned by
`zotTrash()`.

Reusing the ledger path for the same digest skips entries already marked done:

```{r}
LedgerAgain <- zotExecute(
  Config,
  preview = Preview,
  approval = Approval,
  ledgerPath = LedgerPath
)
LedgerAgain$items$ABCD1234$status
```

## Sample completed patches

`zotVerify()` summarizes ledger statuses and re-reads at most ten completed
patch rows. It does not sample create or Trash rows.

```{r}
Verification <- zotVerify(Config, preview = Preview, ledger = LedgerAgain)
Verification
unlink(LedgerPath)
```

Applications should inspect failed or missing ledger entries separately.
Local SQLite and semantic-index results are useful for discovery but do not
replace an API read of remote state.
