---
title: "Downstream C API"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Downstream C API}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

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

Rminibwa alignment batches are native external pointers with borrowed column
buffers. R users can inspect them through ALTREP vectors, but downstream
packages should consume them through the installed C header:

```c
#include <Rminibwa.h>
```

The header resolves functions with `R_GetCCallable()`. A downstream package
therefore needs `LinkingTo: Rminibwa` for the header and an ordinary R package
runtime dependency to ensure Rminibwa is loaded before the first call.

This vignette uses `Rtinycc` to compile the downstream consumer in-process. The
C source is displayed with Rtinycc's C rendering helper and compiled from the
same `rminibwa_capi_code` object.

## Build a tiny alignment batch

```{r tiny-batch}
library(Rminibwa)

td <- tempfile("rminibwa-capi-")
dir.create(td)
ref <- paste(rep("ACGT", 1000), collapse = "")
fa <- file.path(td, "ref.fa")
writeLines(c(">chr1", ref), fa, useBytes = TRUE)
prefix <- file.path(td, "idx")

mb_index_build(fa, prefix, threads = 1L)
idx <- mb_index_load(prefix)
aln <- charToRaw(substr(ref, 1L, 100L)) |>
  mb_map(idx, opt = mb_opts("sr", out_n = 0L), name = charToRaw("read1"))

mb_align_n(aln)
mb_align_col(aln, "tid")[[1]]
```

## The downstream C consumer

```{r c-consumer-source, echo = FALSE, results = "asis"}
capi_candidates <- c(
  file.path("inst", "capi", "rminibwa_tinycc_consumer.c"),
  file.path("..", "inst", "capi", "rminibwa_tinycc_consumer.c"),
  system.file("capi", "rminibwa_tinycc_consumer.c", package = "Rminibwa")
)
capi_path <- capi_candidates[file.exists(capi_candidates)][[1L]]
rminibwa_capi_code <- paste(readLines(capi_path, warn = FALSE), collapse = "\n")
Rtinycc:::rtinycc_c_block(rminibwa_capi_code)
```

## Compile and call it with Rtinycc

```{r compile-c-consumer}
ffi <- tryCatch(
  Rtinycc::tcc_ffi() |>
    Rtinycc::tcc_include(system.file("include", package = "Rminibwa")) |>
    Rtinycc::tcc_source(rminibwa_capi_code) |>
    Rtinycc::tcc_bind(
      rminibwa_capi_summary = list(args = list("sexp"), returns = "sexp")
    ) |>
    Rtinycc::tcc_compile(),
  error = identity
)

if (inherits(ffi, "error")) {
  cat("Rtinycc compilation is unavailable on this platform during vignette build:\n")
  cat(conditionMessage(ffi), "\n")
} else {
  ffi$rminibwa_capi_summary(aln)
}
```

The C function never asks R to materialize a data frame. It receives the batch
SEXP, obtains the opaque `RmbAlignBatch *`, and reads borrowed `int32_t`,
`int64_t`, and packed CIGAR buffers directly.

## Lossless query-group stream

For BAM preparation, use `mb_query_stream()` rather than repeatedly converting
alignment batches. It requires an explicit `mode` and total `threads` budget,
and returns exactly one normalized QNAME group per native `next` call. In
paired mode it rejects mismatched names, mate ordering/count errors, and
malformed or truncated FASTQ before an ambiguous group is emitted. A supplied
`@RG` record is retained exactly; no sample or library metadata is invented.

The installed API is deliberately a versioned POD/view interface rather than a
`bam1_t` handoff, so a consumer compiled against its own htslib can encode each
record once. Its header view reports `SO:unsorted` and `GO:query`: contiguous
groups preserve input order but are not query-name sorted:

```c
if (Rminibwa_stream_abi_version() != RMINIBWA_STREAM_ABI_VERSION)
    Rf_error("Rminibwa stream ABI mismatch");

RmbQueryStream *stream = Rminibwa_stream_from_sexp(stream_x);
const RmbQueryGroup *group = NULL;
Rminibwa_header_view header;
Rminibwa_query_group_view_t view;

Rminibwa_stream_header(stream, &header); /* @SQ, exact @RG, @PG facts */
while (Rminibwa_stream_next(stream, &group) == RMINIBWA_STREAM_OK) {
    Rminibwa_query_group_view(group, &view);
    /* Consume view.reads and view.records before requesting the next group. */
    /* record.cigar and typed AS/NM/MQ/MC/RG tags are native, not SAM text. */
}
```

Every pointer in `Rminibwa_query_group_view_t` is borrowed and is valid only
until `Rminibwa_stream_next()`, `Rminibwa_stream_cancel()`, or stream
destruction. This creates one-group back-pressure: a consumer pauses simply by
not requesting another group. `Rminibwa_stream_cancel()` releases the current
alignment buffers and leaves a reason code/message available through the C API
(or `mb_query_stream_error()` for diagnostics). The producer has no extra
worker pool; `threads` is the complete minibwa mapping budget.

```{r cleanup, include = FALSE}
unlink(td, recursive = TRUE)
```
