---
title: "Curation plans"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Curation plans}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

The curation helpers in `zot` convert candidate records into plan rows or
Zotero payloads. The returned objects can be inspected before any API write.

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

## Normalize incoming metadata

`ztCleanName()` preserves a filename extension while removing recognized
source labels and copy suffixes. `ztCleanTitle()` also removes a trailing file
extension. `ztCleanPayload()` applies the corresponding rules to text fields,
creator names, and tags.

```{r}
ztCleanName("Dynamics (1).pdf")
ztCleanTitle("Dynamics (1).pdf")

Payload <- list(
  itemType = "book",
  title = "Structural Analysis (1).pdf",
  creators = list(list(
    creatorType = "author",
    firstName = "A.",
    lastName = "Author"
  )),
  filename = "Structural Analysis (1).pdf"
)
ztCleanPayload(Payload)
```

Other parenthetical text is retained. `zotPreview()` rejects a payload that
still contains one of the source labels recognized by the package.

## Retitle changed records

`ztRetitle()` applies a transformation and returns a patch row only when the
result differs from the original title.

```{r}
Items <- data.table(
  itemKey = c("A", "B"),
  library = "user",
  title = c("Reviewed book (1).pdf", "Existing title")
)

RetitlePlan <- ztRetitle(Items, transform = ztCleanTitle)
zotPreview(RetitlePlan, describe = "Normalize reviewed titles")
```

`ztRefile()` builds collection-membership patches. It reads each item's
current memberships and includes their union with the requested collection,
because the Zotero API replaces the complete membership list.

## Compare duplicate candidates

`zotSameItem()` compares exact DOI, exact attachment MD5, or normalized title
with year and first-author family name. A title alone does not produce a
positive match. A record with a file is not considered equivalent to a
candidate without that file.

```{r}
zotSameItem(
  list(doi = "10.1000/Example", hasFile = TRUE),
  list(doi = "10.1000/example", hasFile = TRUE)
)

zotSameItem(
  list(title = "Introduction"),
  list(title = "Introduction")
)
```

`ztTwins()` evaluates pairs within normalized-title groups and returns the
matching verdict and its evidence class.

```{r}
Records <- data.table(
  itemKey = c("A", "B", "C"),
  title = c("Paper", "Paper", "Paper"),
  year = c(2026L, 2026L, NA_integer_),
  author = c("Lovelace", "Lovelace", NA_character_),
  doi = NA_character_,
  md5 = NA_character_,
  hasFile = FALSE
)
ztTwins(Records)
```

## Map Crossref metadata

`ztCrossref()` normally requests a DOI record from Crossref. Its injectable
fetcher also permits offline and reproducible mapping.

```{r}
Message <- function(doi) list(
  title = list("A reproducible record"),
  author = list(list(given = "Ada", family = "Lovelace")),
  `container-title` = list("Example Journal"),
  issued = list(`date-parts` = list(list(2026L)))
)

ztCrossref("10.1000/example", getJson = Message)
```

The result is a payload; the function does not create a Zotero item.

## Build a cross-library move

Zotero item keys belong to one library. `ztBlockMoveCreate()` therefore builds
create rows for the destination. It rejects records with imported files,
because copying their metadata alone would omit attachments.

```{r}
MoveRecords <- data.table(
  itemKey = "OLD1",
  payload = list(list(itemType = "book", title = "Reviewed book")),
  hasFile = FALSE
)

CreatePlan <- ztBlockMoveCreate(
  MoveRecords,
  toLibrary = "5107760",
  collectionKey = "DEST"
)
CreatePlan
```

`ztBlockMoveTrash()` adds an original item to a Trash plan only when its
corresponding create-ledger entry is marked `"done"`.

```{r}
CreateLedger <- list(items = list(
  `create-1` = list(status = "done", new = "NEW1")
))

TrashPlan <- ztBlockMoveTrash(
  MoveRecords,
  createLedger = CreateLedger,
  fromLibrary = "user"
)
TrashPlan
```

The second plan is independent of the first execution. Trash is recoverable;
the package does not empty Zotero's Trash.
