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

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

`regulog` integrates with Shiny through two dedicated functions and works
naturally with all the standard `log_*` functions inside server logic.

| Function | Purpose |
|---|---|
| `regulog_shiny_init()` | Initialise a session inside `server()` — resolves the authenticated user from `session$user` and auto-logs `session_start` / `session_end` |
| `regulog_observer()` | Wrap `observeEvent()` to log an action whenever a reactive input fires |

All other `regulog` functions — `log_action()`, `log_change()`,
`log_note()`, `log_signature()`, `rl_read()`, `with_log()`,
`filter_log()`, `export_audit_trail()` — work identically inside Shiny
server functions.

## 1. Basic pattern

Call `regulog_shiny_init()` at the top of `server()`. Store the returned
log object as a local variable and pass it to `log_*` calls inside
observers.

```{r basic}
library(shiny)
library(regulog)

server <- function(input, output, session) {
  log <- regulog_shiny_init(
    session = session,
    app     = "clinical-review-tool",
    version = "1.2.0",
    path    = "logs/review_audit.rlog"
  )

  observeEvent(input$approve, {
    req(nzchar(input$justification))
    log_action(log,
      action = "approved",
      object = input$dataset_id,
      reason = input$justification
    )
    showNotification("Approval recorded.", type = "message")
  })

  observeEvent(input$reject, {
    req(nzchar(input$rejection_reason))
    log_action(log,
      action = "rejected",
      object = input$dataset_id,
      reason = input$rejection_reason
    )
    showNotification("Rejection recorded.", type = "warning")
  })
}
```

## 2. User resolution

`session$user` is the authenticated identity set by Shiny Server Pro or
Posit Connect. `regulog_shiny_init()` resolves the user in this order:

1. `session$user` — when Shiny Server Pro / Posit Connect authentication
   is configured (the value in regulated production deployments)
2. `Sys.info()[["user"]]` — OS-level user, as a fallback with a warning

In regulated environments, always configure authentication so that
`session$user` carries a real, non-repudiable identity. The `user` field
appears on every log entry and is the basis for user attribution under
21 CFR Part 11 §11.50.

```{r user-check}
# In a development context, confirm what user is being captured:
server <- function(input, output, session) {
  log <- regulog_shiny_init(
    session = session,
    app     = "review-tool",
    version = "1.0.0"
  )

  output$debug_user <- renderText({
    paste("Logging as:", log$user)
  })
}
```

## 3. Session lifecycle events

`regulog_shiny_init()` automatically adds two entries — no additional
code needed:

| Entry | Trigger | `action` | `reason` |
|---|---|---|---|
| `session_start` | `regulog_shiny_init()` call | `"session_start"` | `"Shiny session opened"` |
| `session_end` | `session$onSessionEnded()` | `"session_end"` | `"Shiny session closed"` |

These bracket all user-driven entries and give a complete picture of each
session — who was logged in, when, and for how long.

## 4. Using regulog_observer()

`regulog_observer()` reduces boilerplate when many UI events need
auditing. The `object` and `reason` arguments accept both fixed strings
and reactive expressions.

```{r observer}
server <- function(input, output, session) {
  log <- regulog_shiny_init(
    session = session,
    app     = "data-review",
    version = "2.0.0",
    path    = "logs/review.rlog"
  )

  # Static strings
  regulog_observer(log, session,
    eventExpr = input$lock_database,
    action    = "database_locked",
    object    = "study_database",
    reason    = "Database lock confirmed by data manager"
  )

  # Reactive strings — evaluated at the time the event fires
  regulog_observer(log, session,
    eventExpr = input$approve_record,
    action    = "approved",
    object    = reactive(paste0("record_", input$record_id)),
    reason    = reactive(input$approval_reason)
  )

  regulog_observer(log, session,
    eventExpr = input$flag_discrepancy,
    action    = "flagged",
    object    = reactive(paste0("record_", input$record_id)),
    reason    = reactive(paste0("Discrepancy: ", input$discrepancy_detail))
  )
}
```

## 5. Notes and decisions from Shiny

Use `log_note()` inside observers to document analytical decisions made
through the UI — retention of outliers, query resolutions, deviations.

```{r notes}
observeEvent(input$retain_outlier, {
  req(nzchar(input$retention_rationale))

  log_note(log, paste0(
    "Outlier retained for subject ", input$subject_id, " at ",
    input$visit_label, ": ", input$outlier_value, ". ",
    input$retention_rationale
  ))

  showNotification(
    paste("Retention decision logged for subject", input$subject_id),
    type = "message"
  )
})

observeEvent(input$resolve_query, {
  req(nzchar(input$query_resolution))

  log_note(log, paste0(
    "Query Q-", input$query_id, " resolved: ",
    input$query_resolution,
    " (resolved by: ", session$user, ")"
  ))
})
```

## 6. Logging data changes from Shiny

When users edit records through a Shiny app, `log_change()` documents the
before/after values:

```{r changes}
observeEvent(input$save_correction, {
  req(input$original_value, input$corrected_value, input$correction_reason)

  log_change(log,
    object = paste0(input$subject_id, "_", input$field_name),
    field = input$field_name,
    before = input$original_value,
    after = input$corrected_value,
    reason = paste0(
      input$correction_reason,
      " — corrected by ", session$user
    )
  )

  showNotification("Correction recorded in audit trail.", type = "message")
})
```

## 7. Electronic signatures from Shiny

Require a meaningful sign-off before a dataset is locked or an export
is authorised:

```{r signature}
observeEvent(input$sign_off, {
  req(nzchar(input$signoff_meaning))

  log_signature(log, meaning = input$signoff_meaning)

  # Export signed audit trail alongside the lock
  export_path <- sprintf(
    "exports/audit_%s_%s.csv",
    gsub("[^a-zA-Z0-9]", "_", input$study_id),
    format(Sys.Date(), "%Y%m%d")
  )

  export_audit_trail(log,
    format = "csv",
    signed = TRUE,
    path   = export_path
  )

  showModal(modalDialog(
    title = "Sign-off complete",
    paste("Signature recorded. Audit trail exported to:", export_path),
    easyClose = TRUE
  ))
})
```

## 8. Download handler for the audit trail

Let reviewers download a signed CSV of the current session's log:

```{r download}
output$download_audit <- downloadHandler(
  filename = function() {
    sprintf("audit_%s.csv", format(Sys.Date(), "%Y%m%d"))
  },
  content = function(file) {
    export_audit_trail(log, format = "csv", signed = TRUE, path = file)
  }
)

# In UI:
# downloadButton("download_audit", "Download audit trail (CSV)") # nolint: commented_code_linter
```

## 9. Per-session vs shared log

### Per-session (default)

Each browser session gets its own independent audit trail. This is the
default — `regulog_shiny_init()` creates a new log for each `server()`
call. Individual `.rlog` files can later be combined for a study-level view.

### Shared log across users

For applications where one audit file covers all user activity:

```{r shared}
# In global.R — outside server(), evaluated once at startup
shared_log <- regulog_init(
  app     = "multi-user-review",
  version = "1.0.0",
  user    = "system", # will be overridden per action
  path    = "logs/shared_audit.rlog"
)

server <- function(input, output, session) {
  # Resolve per-request user
  current_user <- reactive({
    u <- session$user
    if (is.null(u) || !nzchar(u)) Sys.info()[["user"]] else u
  })

  observeEvent(input$approve, {
    req(nzchar(input$reason))
    log_action(shared_log,
      action = "approved",
      object = input$record_id,
      reason = input$reason,
      user   = current_user() # explicit per-action user
    )
  })
}
```

## 10. Querying and verifying from outside the app

Session logs are plain `.rlog` files and can be verified or queried at
any time without a running Shiny app — important for QC reviewers who
may not have access to the application itself.

```{r offline}
# Verify integrity of a session log
result <- verify_log("logs/review_audit.rlog")
result$intact

# All signatures
filter_log("logs/review_audit.rlog", type = "SIGNATURE")

# All actions by a specific user
filter_log("logs/review_audit.rlog",
  type = "ACTION",
  user = "jsmith"
)

# Changes made within a date range
filter_log("logs/review_audit.rlog",
  type = "CHANGE",
  from = "2026-06-01",
  to   = "2026-06-30"
)

# Export signed CSV from file path — no session required
export_audit_trail("logs/review_audit.rlog",
  format = "csv",
  signed = TRUE,
  path   = "exports/review_audit_signed.csv"
)
```

## 11. Logging data reads from Shiny

Inside a server function, use `with_log()` to log data reads scoped to
the current session. Because `with_log()` resolves its local `read()`
binding through lexical scope rather than shared package state, this is
safe under concurrent use — each browser session's reads are logged only
to that session's own `log`, even when multiple users are active in the
same R process at once.

```{r shiny-reads}
server <- function(input, output, session) {
  log <- regulog_shiny_init(
    session = session,
    app     = "data-review",
    version = "1.0.0",
    path    = sprintf("logs/review_%s.rlog", session$token)
  )

  observeEvent(input$load_dataset, {
    with_log(log, {
      dataset <- read(haven::read_sas, input$dataset_path) # nolint: object_usage_linter
    })
    showNotification("Dataset loaded and logged.", type = "message")
  })
}
```

For a single read outside a scoped block, `rl_read()` is equivalent:

```{r shiny-rl-read}
observeEvent(input$load_dataset, {
  dataset <- rl_read(log, haven::read_sas, input$dataset_path)
})
```

## 12. Complete minimal example

```{r complete-example}
library(shiny)
library(regulog)

ui <- fluidPage(
  titlePanel("Clinical Data Review"),
  sidebarLayout(
    sidebarPanel(
      selectInput("dataset_id", "Dataset", c("ADSL", "ADAE", "ADLB")),
      textAreaInput("justification", "Justification", rows = 3),
      actionButton("approve", "Approve", class = "btn-success"),
      actionButton("reject", "Reject", class = "btn-danger"),
      hr(),
      textAreaInput("note_text", "Add note", rows = 3),
      actionButton("add_note", "Add note"),
      hr(),
      textAreaInput("signoff_meaning", "Signature meaning", rows = 3),
      actionButton("sign_off", "Sign off"),
      hr(),
      downloadButton("download_audit", "Download audit trail")
    ),
    mainPanel(
      tableOutput("log_table")
    )
  )
)

server <- function(input, output, session) {
  log <- regulog_shiny_init(
    session = session,
    app     = "data-review",
    version = "1.0.0",
    path    = tempfile(fileext = ".rlog")
  )

  observeEvent(input$approve, {
    req(nzchar(input$justification))
    log_action(log, "approved", input$dataset_id, input$justification)
    showNotification("Approval recorded.", type = "message")
  })

  observeEvent(input$reject, {
    req(nzchar(input$justification))
    log_action(log, "rejected", input$dataset_id, input$justification)
    showNotification("Rejection recorded.", type = "warning")
  })

  observeEvent(input$add_note, {
    req(nzchar(input$note_text))
    log_note(log, input$note_text)
    showNotification("Note recorded.", type = "message")
  })

  observeEvent(input$sign_off, {
    req(nzchar(input$signoff_meaning))
    log_signature(log, input$signoff_meaning)
    showNotification("Signature recorded.", type = "message")
  })

  output$download_audit <- downloadHandler(
    filename = function() sprintf("audit_%s.csv", Sys.Date()),
    content = function(file) {
      export_audit_trail(log, format = "csv", signed = TRUE, path = file)
    }
  )

  output$log_table <- renderTable({
    input$approve
    input$reject
    input$add_note
    input$sign_off
    df <- filter_log(log)
    if (nrow(df) == 0L) {
      return(NULL)
    }
    df[, c("entry_id", "type", "action", "object", "reason")]
  })
}

shinyApp(ui, server)
```
