## -----------------------------------------------------------------------------
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  eval = FALSE
)

## -----------------------------------------------------------------------------
# # From R-universe
# install.packages("automerge", repos = "https://posit-dev.r-universe.dev")
# 
# # From GitHub
# pak::pak("posit-dev/automerge-r")

## -----------------------------------------------------------------------------
# library(automerge)
# 
# # Create
# doc <- am_create() # New document
# doc <- am_create("fd3ca50687f13477f1f9ea2b216b958a") # With custom actor ID
# 
# # Load/Save
# bytes <- am_save(doc) # Save to bytes
# doc <- am_load(bytes) # Load from bytes
# 
# # Actor ID (document-bound)
# actor <- am_get_actor(doc) # Get actor ID (raw bytes)
# actor_hex <- am_get_actor_hex(doc) # Get actor ID as hex string
# am_set_actor(doc, actor_hex) # Set actor ID (raw, hex, or NULL)
# 
# # Fork/Merge/Clone
# doc2 <- am_fork(doc) # Create independent copy
# doc3 <- am_clone(doc) # Deep copy (independent document)
# am_merge(doc, doc2) # Merge doc2 into doc1
# 
# # Compare documents
# am_equal(doc, doc2) # TRUE if same state
# 
# # Transactions
# am_commit(doc, "message") # Commit changes
# am_rollback(doc) # Cancel pending changes
# am_pending_ops(doc) # Number of uncommitted operations
# am_commit_empty(doc, "checkpoint") # Create empty change (bookkeeping)
# 
# # Incremental save/load
# inc <- am_save_incremental(doc) # Only changes since last save
# am_load_incremental(doc, inc) # Apply incremental changes
# 
# # Cleanup
# am_close(doc) # Explicitly free resources (optional)

## -----------------------------------------------------------------------------
# # S3 operators
# doc[["key"]] <- "value" # Set value
# value <- doc[["key"]] # Get value
# doc$key <- value # Alternative syntax
# value <- doc$key # Alternative syntax
# 
# # Functional API
# am_delete(doc, AM_ROOT, "key") # Delete key
# am_put(doc, AM_ROOT, "key", value) # Set value
# value <- am_get(doc, AM_ROOT, "key") # Get value
# 
# # Introspection
# keys <- names(doc) # Get all keys
# keys <- am_keys(doc, AM_ROOT) # Functional version
# values <- am_values(doc, AM_ROOT) # Get all values
# n <- length(doc) # Number of keys
# n <- am_length(doc, AM_ROOT) # Functional version
# items <- am_items(doc, AM_ROOT) # All key-value entries
# 
# # All conflicting values for a key (after concurrent edits)
# conflicts <- am_map_get_all(doc, AM_ROOT, "key")
# 
# # Range of entries by key (alphabetical)
# entries <- am_map_range(doc, AM_ROOT, "a", "m")

## -----------------------------------------------------------------------------
# # Automatic recursive conversion (recommended)
# am_put(
#   doc,
#   AM_ROOT,
#   "user",
#   list(
#     name = "Alice",
#     age = 30L,
#     address = list(city = "NYC", zip = 10001L)
#   )
# )
# 
# # Path-based access (simple for deep structures)
# am_put_path(doc, c("user", "address", "city"), "Boston")
# city <- am_get_path(doc, c("user", "address", "city"))
# am_delete_path(doc, c("user", "address"))
# 
# # Manual access
# user_obj <- am_get(doc, AM_ROOT, "user")
# am_put(doc, user_obj, "name", "Bob")
# name <- am_get(doc, user_obj, "name")

## -----------------------------------------------------------------------------
# # Create list
# am_put(doc, AM_ROOT, "items", AM_OBJ_TYPE_LIST)
# items <- am_get(doc, AM_ROOT, "items")
# 
# # Operations (1-based indexing)
# am_insert(doc, items, 1, "first") # Insert at index 1
# am_put(doc, items, 1, "FIRST") # Replace at index 1
# am_put(doc, items, "end", "append") # Append to end
# value <- am_get(doc, items, 1) # Get index 1
# am_delete(doc, items, 1) # Delete index 1
# 
# # Introspection
# n <- am_length(doc, items) # List length
# items_entries <- am_items(doc, items) # All index-value entries
# 
# # All conflicting values at index (after concurrent edits)
# conflicts <- am_list_get_all(doc, items, 1)
# 
# # Subrange (1-based indexing)
# sub <- am_list_range(doc, items, 2, 4)

## -----------------------------------------------------------------------------
# # Text objects use 0-based inter-character positions
# # For the text "Hello":
# #  H e l l o
# # 0 1 2 3 4 5  <- positions (0-based, between characters)
# 
# # Create text object
# am_put(doc, AM_ROOT, "content", am_text("Hello"))
# text_obj <- am_get(doc, AM_ROOT, "content")
# 
# # Operations
# am_text_splice(text_obj, 5, 0, " World") # Insert at position 5
# content <- am_text_content(text_obj) # Get full text
# 
# # Update text (ideal for collaborative editing)
# am_text_update(text_obj, "Hello Universe") # Computes and applies diff
# 
# # Cursors (stable positions)
# cursor <- am_cursor(text_obj, 5) # Create at position 5
# pos <- am_cursor_position(cursor) # Get current position
# cursor <- am_cursor(text_obj, 5, heads) # Create at historical state
# pos <- am_cursor_position(cursor, heads) # Position at historical state
# 
# # Cursor serialization (persistence across sessions)
# bytes <- am_cursor_to_bytes(cursor) # Serialize to raw
# cursor <- am_cursor_from_bytes(bytes, text_obj) # Restore from raw
# str <- am_cursor_to_string(cursor) # Serialize to string
# cursor <- am_cursor_from_string(str, text_obj) # Restore from string
# am_cursor_equal(cursor1, cursor2) # Compare cursors
# 
# # Marks (formatting)
# am_mark(text_obj, 0, 5, "bold", TRUE, expand = "none")
# marks <- am_marks(text_obj) # Get all marks
# marks <- am_marks(text_obj, heads) # Marks at historical state
# marks_at <- am_marks_at(text_obj, 2) # Marks at position 2
# marks_at <- am_marks_at(text_obj, 2, heads) # At position, historical state
# am_mark_clear(text_obj, 0, 5, "bold") # Remove a mark from range

## -----------------------------------------------------------------------------
# # NULL
# am_put(doc, AM_ROOT, "null", NULL)
# 
# # Boolean
# am_put(doc, AM_ROOT, "bool", TRUE)
# 
# # Integer
# am_put(doc, AM_ROOT, "int", 42L)
# 
# # Double
# am_put(doc, AM_ROOT, "float", 3.14)
# 
# # String
# am_put(doc, AM_ROOT, "str", "text")
# 
# # Raw bytes
# am_put(doc, AM_ROOT, "bytes", raw(10))
# 
# # Timestamp
# am_put(doc, AM_ROOT, "time", Sys.time())
# 
# # Counter
# am_put(doc, AM_ROOT, "score", am_counter(0))
# am_counter_increment(doc, AM_ROOT, "score", 10)
# value <- am_get(doc, AM_ROOT, "score")
# 
# # Unsigned 64-bit integer (for cross-platform interop)
# am_put(doc, AM_ROOT, "id", am_uint64(12345))
# 
# # Explicit type constructors
# am_put(doc, AM_ROOT, "items", am_list()) # Empty list
# am_put(doc, AM_ROOT, "config", am_map()) # Empty map
# am_put(doc, AM_ROOT, "notes", am_text()) # Text object

## -----------------------------------------------------------------------------
# # Bidirectional sync (auto-converge)
# # Documents are modified in place
# rounds <- am_sync(doc1, doc2)
# cat("Converged in", rounds, "rounds\n")
# 
# # One-way merge
# am_merge(doc1, doc2) # Merge doc2 into doc1

## -----------------------------------------------------------------------------
# # Create sync state
# sync_state <- am_sync_state()
# 
# # Encode/decode messages
# msg <- am_sync_encode(doc, sync_state)
# am_sync_decode(doc, sync_state, msg)
# 
# # Serialize/restore sync state (for persistent connections)
# sync_bytes <- am_sync_state_encode(sync_state)
# sync_state <- am_sync_state_decode(sync_bytes)
# 
# # Manual sync loop
# repeat {
#   msg1 <- am_sync_encode(doc1, sync1)
#   if (is.null(msg1)) {
#     break
#   }
#   am_sync_decode(doc2, sync2, msg1)
# 
#   msg2 <- am_sync_encode(doc2, sync2)
#   if (is.null(msg2)) {
#     break
#   }
#   am_sync_decode(doc1, sync1, msg2)
# }

## -----------------------------------------------------------------------------
# # Get heads (fingerprint of current state)
# heads <- am_get_heads(doc)
# 
# # Get changes (returns am_change objects)
# changes <- am_get_changes(doc, NULL)  # All changes
# changes <- am_get_changes(doc, heads) # Changes since heads (i.e. none)
# 
# # Typical pattern: remember a checkpoint, then get new changes later
# checkpoint <- am_get_heads(doc)
# am_put(doc, AM_ROOT, "new_key", "new_value")
# am_commit(doc, "Add new_key")
# new_changes <- am_get_changes(doc, checkpoint) # Only the new commit
# 
# # Apply changes
# am_apply_changes(doc, changes)
# 
# # Full history (returns am_change objects)
# history <- am_get_changes(doc)
# 
# # Change introspection
# change <- history[[1]]
# hash <- am_change_hash(change) # 32-byte hash
# msg <- am_change_message(change) # Commit message or NULL
# time <- am_change_time(change) # POSIXct timestamp
# actor <- am_change_actor_id(change) # Actor ID (raw bytes)
# seq <- am_change_seq(change) # Sequence number (double)
# deps <- am_change_deps(change) # List of parent hashes
# size <- am_change_size(change) # Number of operations
# bytes <- am_change_to_bytes(change) # Serialize to raw
# 
# # Deserialize from raw bytes (for stored changes)
# change <- am_change_from_bytes(bytes)
# 
# # Decompose saved document into individual changes
# changes <- am_load_changes(bytes) # From am_save() output
# 
# # Check document completeness
# missing <- am_get_missing_deps(doc)
# missing <- am_get_missing_deps(doc, heads) # With specific heads

## -----------------------------------------------------------------------------
# # R → Automerge
# doc <- as_automerge(list(name = "Alice", age = 30L))
# 
# # Automerge → R
# r_list <- from_automerge(doc)
# 
# r_list <- as.list(doc) # S3 method for documents
# text_str <- as.character(text_obj) # S3 method for text objects

## -----------------------------------------------------------------------------
# doc <- am_create() |>
#   am_put(AM_ROOT, "name", "Alice") |>
#   am_put(AM_ROOT, "age", 30L) |>
#   am_commit("Initial data")

## -----------------------------------------------------------------------------
# # Save to file
# writeBin(am_save(doc), "document.automerge")
# 
# # Load from file
# doc <- am_load(readBin("document.automerge", "raw", 1e6))

## -----------------------------------------------------------------------------
# AM_ROOT # Root object (NULL)
# AM_OBJ_TYPE_LIST # "list"
# AM_OBJ_TYPE_MAP # "map"
# AM_OBJ_TYPE_TEXT # "text"
# 
# AM_MARK_EXPAND_NONE # "none" (mark doesn't expand at boundaries)
# AM_MARK_EXPAND_BEFORE # "before" (expands when text inserted before start)
# AM_MARK_EXPAND_AFTER # "after" (expands when text inserted after end)
# AM_MARK_EXPAND_BOTH # "both" (expands at both boundaries)

## -----------------------------------------------------------------------------
# # Function help
# ?am_create
# ?am_put
# ?am_sync
# 
# # Package help
# ?automerge
# help(package = "automerge")
# 
# # Vignettes
# vignette("automerge", "automerge")
# vignette("crdt-concepts", "automerge")
# vignette("sync-protocol", "automerge")
# vignette(package = "automerge") # List all

