;;; Arl Standard Library - R Nonstandard Evaluation (NSE) Wrappers
;;;
;;; These macros wrap base R functions that use nonstandard evaluation.
;;; NSE functions need unevaluated expressions; these quote arguments
;;; appropriately before passing them to the underlying R functions.

(module r-interop
  (export suppressWarnings suppressMessages with within subset
          transform substitute)

  (import core :refer (get error))
  (import strings :refer (string-append))
  (import list :refer (call))

;;' @section R Nonstandard Evaluation Wrappers
;;' These macros provide Arl-friendly interfaces to R functions that use
;;' nonstandard evaluation (NSE). They automatically quote expressions so
;;' you can write natural Arl code without manual quoting.

  ;;' @description Suppress warnings generated by evaluating expr.
  ;;' @param expr Expression to evaluate with warnings suppressed
  ;;' @examples
  ;;' (suppressWarnings (as.numeric "not a number"))  ; => NA (warning suppressed)
  ;;' (suppressWarnings (log -1))                     ; => NaN (warning suppressed)
  ;;' @assert
  ;;' (assert-true (is.na (suppressWarnings (as.numeric "not a number"))))
  ;;' (assert-true (is.nan (suppressWarnings (log -1))))
  ;;' @seealso suppressMessages
  (defmacro suppressWarnings (expr)
    (begin
      (define sw (get "suppressWarnings" :envir (baseenv)))
      `(r-eval (call (list ,sw ',expr)) (current-env))))

  ;;' @description Suppress messages generated by evaluating expr.
  ;;' @param expr Expression to evaluate with messages suppressed
  ;;' @examples
  ;;' (suppressMessages (message "hello"))            ; => #nil (message suppressed)
  ;;' @assert
  ;;' (assert-true (is.null (suppressMessages (message "hello"))))
  ;;' @seealso suppressWarnings
  (defmacro suppressMessages (expr)
    (begin
      (define sm (get "suppressMessages" :envir (baseenv)))
      `(r-eval (call (list ,sm ',expr)) (current-env))))

  ;;' @description Evaluate expr in the context of data (a data frame or list).
  ;;' @param data Data frame or list providing the evaluation context
  ;;' @param expr Expression evaluated with data columns as variables
  ;;' @examples
  ;;' (define df (data.frame :x (c 1 2 3) :y (c 4 5 6)))
  ;;' (with df (+ x y))         ; => c(5, 7, 9)
  ;;' @assert
  ;;' (assert-equal (c 5 7 9) (with (data.frame :x (c 1 2 3) :y (c 4 5 6)) (+ x y)))
  ;;' @seealso within
  (defmacro with (data expr)
    (begin
      (define w (get "with" :envir (baseenv)))
      `(r-eval (call (list ,w ,data ',expr)) (current-env))))

  ;;' @description Evaluate expr within data, returning modified data.
  ;;' @param data Data frame to modify
  ;;' @param expr Expression that can assign new/modified columns
  ;;' @examples
  ;;' (define df (data.frame :x (c 1 2 3)))
  ;;' (within df (<- z (* x 2)))   ; returns df with new column z
  ;;' @assert
  ;;' (assert-true (%in% "z" (names (within (data.frame :x (c 1 2 3)) (<- z (* x 2))))))
  ;;' @seealso with
  (defmacro within (data expr)
    (begin
      (define w (get "within" :envir (baseenv)))
      `(r-eval (call (list ,w ,data ',expr)) (current-env))))

  ;;' @description Subset x using condition. Optional rest args for select, drop, etc.
  ;;' @param x Data frame to subset
  ;;' @param condition Row-selection expression evaluated in x's context
  ;;' @param rest Optional additional arguments (e.g. select)
  ;;' @examples
  ;;' (define df (data.frame :x (c 1 2 3) :y (c 10 20 30)))
  ;;' (subset df (> x 1))       ; rows where x > 1
  ;;' @assert
  ;;' (assert-equal 2L (nrow (subset (data.frame :x (c 1 2 3) :y (c 10 20 30)) (> x 1))))
  ;;' @seealso with, within
  (defmacro subset (x condition . rest)
    (begin
      (define s (get "subset" :envir (baseenv)))
      `(r-eval (call (list ,s ,x ',condition ,@rest)) (current-env))))

  ;;' @description transform is difficult to implement - use within() or dplyr::mutate() instead.
  ;;' @param args Arguments (not used; always signals an error)
  ;;' @note Not yet supported due to R named-argument syntax. Use `within` or `dplyr::mutate` instead.
  ;;' @seealso within
  (define transform
    (lambda (. args)
      (error (string-append
        "transform() is difficult to implement and is not yet supported due to
         its use of R's named argument syntax.\n"
        "Alternatives:\n"
        "  - Use (within df ...) for simple transformations\n"
        "  - Use dplyr::mutate() for more complex cases\n"
        "  - Call R's transform directly from R code if needed"))))

  ;;' @description Perform substitution in an expression, or error if called with 1 arg.
  ;;' @param args Expression and environment for substitution
  ;;' @note Single-argument `substitute` does not work in Arl due to eager evaluation. Use macros or explicit quoting instead. Two-argument form works normally.
  ;;' @seealso defmacro
  (define substitute
    (lambda (. args)
      (define nargs (length args))
      (if (= nargs 1)
        (error (string-append
          "substitute(x) doesn't work in Arl functions due to eager evaluation.\n"
          "Arl evaluates arguments before calling functions, so there's no
           unevaluated expression to capture.\n"
          "For NSE in Arl:\n"
          "  - Use macros: (defmacro my-fn (x) `(quote ,x))\n"
          "  - Or require explicit quoting: (my-fn 'expr)\n"
          "  - Or use delay: (my-fn (delay expr)) with (promise-expr ...)"))
        (if (= nargs 2)
          (begin
            (define sub (get "substitute" :envir (baseenv)))
            (define expr (car args))
            (define env (car (cdr args)))
            (r-eval (call (list sub expr env)) (current-env)))
          (error "substitute requires 1 or 2 arguments"))))))
