;;; Arl Standard Library - List Helpers

(module list
  (export call
          caar cadr cdar cddr
          caaar caadr cadar caddr
          cdaar cdadr cddar cdddr
          cadddr cddddr
          list* append reverse
          first second third fourth rest last nth
          assoc assoc-by-equal? assoc-by-identical? assoc-by-==
          assq assv rassoc rassoc-by-equal?
          range iota make-list
          list-ref list-tail)

  (import core :refer (r-call error))
  (import types :refer (null? list?))

  ;;' @section List Operations
  ;;' Core list constructors and accessors, following Scheme conventions.
  ;;' Lists in Arl are backed by R lists; [`car`](#car)/[`cdr`](#cdr) provide the fundamental
  ;;' decomposition and [`cons`](#cons) the fundamental construction.

  ;;' @internal
  ;;' @description Return last element of list.
  (define _list-last
    (lambda (lst)
      ([[ lst (length lst))))

  ;;' @internal
  ;;' @description Return list without last element.
  (define _list-head
    (lambda (lst)
      (if (<= (length lst) 1)
        (list)
        (r-call "[" (list lst (seq 1 (- (length lst) 1)))))))

  ;;' @description Convert a list to a callable form.
  ;;' @param lst List to convert to a call object.
  ;;' @examples
  ;;' (call (list '+ 1 2))  ; => language: (+ 1 2)
  ;;' @assert
  ;;' (assert-true (is.call (call (list '+ 1 2))))
  ;;' @seealso eval, apply
  (define call
    (lambda (lst)
      (if (is.call lst)
        lst
        (as.call (_as-list lst)))))

  ;; Common composed list accessors
  ;; These intentionally compose the semantics of car/cdr (including #nil / empty list handling).
  ;;' @description car of car.
  ;;' @param x Nested list to access.
  (define caar (lambda (x) (car (car x))))
  ;;' @description car of cdr.
  ;;' @param x List to access.
  (define cadr (lambda (x) (car (cdr x))))
  ;;' @description cdr of car.
  ;;' @param x Nested list to access.
  (define cdar (lambda (x) (cdr (car x))))
  ;;' @description cdr of cdr.
  ;;' @param x List to access.
  (define cddr (lambda (x) (cdr (cdr x))))

  ;;' @description car of car of car.
  ;;' @param x Nested list to access.
  (define caaar (lambda (x) (car (car (car x)))))
  ;;' @description car of car of cdr.
  ;;' @param x Nested list to access.
  (define caadr (lambda (x) (car (car (cdr x)))))
  ;;' @description car of cdr of car.
  ;;' @param x Nested list to access.
  (define cadar (lambda (x) (car (cdr (car x)))))
  ;;' @description car of cdr of cdr.
  ;;' @param x List to access.
  (define caddr (lambda (x) (car (cdr (cdr x)))))

  ;;' @description cdr of car of car.
  ;;' @param x Nested list to access.
  (define cdaar (lambda (x) (cdr (car (car x)))))
  ;;' @description cdr of car of cdr.
  ;;' @param x Nested list to access.
  (define cdadr (lambda (x) (cdr (car (cdr x)))))
  ;;' @description cdr of cdr of car.
  ;;' @param x Nested list to access.
  (define cddar (lambda (x) (cdr (cdr (car x)))))
  ;;' @description cdr of cdr of cdr.
  ;;' @param x List to access.
  (define cdddr (lambda (x) (cdr (cdr (cdr x)))))

  ;;' @description car of cdr of cdr of cdr.
  ;;' @param x List to access.
  (define cadddr (lambda (x) (car (cdr (cdr (cdr x))))))
  ;;' @description cdr of cdr of cdr of cdr.
  ;;' @param x List to access.
  (define cddddr (lambda (x) (cdr (cdr (cdr (cdr x))))))

  ;;' @description Build list ending with last arg.
  ;;' @param args Elements to combine; the last argument is appended as a tail.
  ;;' @examples
  ;;' (list* 1 2 (list 3 4))  ; => (1 2 3 4)
  ;;' (list* 1 2 3)           ; => (1 2 3)
  ;;' (list*)                  ; => ()
  ;;' @assert
  ;;' (assert-equal (list 1 2 3 4) (list* 1 2 (list 3 4)))
  ;;' (assert-equal (list 1 2 3) (list* 1 2 3))
  ;;' (assert-equal (list) (list*))
  ;;' @seealso list, cons, append
  (define list*
    (lambda (. args)
      (if (= (length args) 0)
        (list)
        (if (= (length args) 1)
          (car args)
          (begin
            (define last (_list-last args))
            (define head (_list-head args))
            (if (is.list last)
              (c head (_as-list last))
              (if (is.call last)
                (c head (_as-list last))
                (c head (list last)))))))))

  ;;' @description Concatenate multiple lists into one.
  ;;' @param lists Lists to concatenate in order.
  ;;' @examples
  ;;' (append (list 1 2) (list 3 4))    ; => (1 2 3 4)
  ;;' (append (list 1) (list 2) (list 3)); => (1 2 3)
  ;;' (append (list 1 2) (list))         ; => (1 2)
  ;;' @assert
  ;;' (assert-equal (list 1 2 3 4) (append (list 1 2) (list 3 4)))
  ;;' (assert-equal (list 1 2 3) (append (list 1) (list 2) (list 3)))
  ;;' (assert-equal (list 1 2) (append (list 1 2) (list)))
  ;;' @seealso cons, list*
  (define append
    (lambda (. lists)
      (do.call c (lapply (_as-list lists) _as-list))))

  ;;' @description Reverse list.
  ;;' @param x List to reverse.
  ;;' @examples
  ;;' (reverse (list 1 2 3))  ; => (3 2 1)
  ;;' (reverse (list))        ; => ()
  ;;' @assert
  ;;' (assert-equal (list 3 2 1) (reverse (list 1 2 3)))
  ;;' (assert-equal (list) (reverse (list)))
  (define reverse
    (lambda (x)
      (rev (_as-list x))))

  ;;' @description Alias for car.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (first (list 10 20 30))   ; => 10
  ;;' @assert
  ;;' (assert-equal 10 (first (list 10 20 30)))
  ;;' @seealso car, rest, last
  (define first
    (lambda (lst)
      (car lst)))

  ;;' @description Return second element or #nil.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (second (list 10 20 30))  ; => 20
  ;;' @assert
  ;;' (assert-equal 20 (second (list 10 20 30)))
  ;;' @seealso first, third
  (define second
    (lambda (lst)
      (cadr lst)))

  ;;' @description Return third element or #nil.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (third (list 10 20 30))   ; => 30
  ;;' @assert
  ;;' (assert-equal 30 (third (list 10 20 30)))
  ;;' @seealso second, fourth
  (define third
    (lambda (lst)
      (caddr lst)))

  ;;' @description Return fourth element or #nil.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (fourth (list 1 2 3 4))   ; => 4
  ;;' @assert
  ;;' (assert-equal 4 (fourth (list 1 2 3 4)))
  ;;' @seealso third
  (define fourth
    (lambda (lst)
      (cadddr lst)))

  ;;' @description Alias for cdr.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (rest (list 1 2 3))       ; => (2 3)
  ;;' @assert
  ;;' (assert-equal (list 2 3) (rest (list 1 2 3)))
  ;;' @seealso cdr, first
  (define rest
    (lambda (lst)
      (cdr lst)))

  ;;' @description Return last item or #nil.
  ;;' @param lst Source list.
  ;;' @examples
  ;;' (last (list 1 2 3))   ; => 3
  ;;' (last (list 42))      ; => 42
  ;;' (last (list))         ; => #nil
  ;;' @assert
  ;;' (assert-equal 3 (last (list 1 2 3)))
  ;;' (assert-equal 42 (last (list 42)))
  ;;' (assert-equal #nil (last (list)))
  ;;' @seealso first, nth
  (define last
    (lambda (lst)
      (define items (_as-list lst))
      (if (= (length items) 0) #nil (_list-last items))))

  ;;' @description Return nth item (0-based).
  ;;' @param lst Source list.
  ;;' @param n 0-based index of the element to retrieve.
  ;;' @examples
  ;;' (nth (list 'a 'b 'c) 0)  ; => a
  ;;' (nth (list 'a 'b 'c) 2)  ; => c
  ;;' @assert
  ;;' (assert-equal 'a (nth (list 'a 'b 'c) 0))
  ;;' (assert-equal 'c (nth (list 'a 'b 'c) 2))
  ;;' @seealso first, last
  (define nth
    (lambda (lst n)
      (define items (_as-list lst))
      (if (< n 0)
        (stop (sprintf "Index %d out of bounds for list of length %d" n (length items)))
        (if (>= n (length items))
          (stop (sprintf "Index %d out of bounds for list of length %d" n (length items)))
          (r-call "[[" (list items (+ n 1)))))))

  ;; ============================================================================
  ;; Phase 2: Extended List Operations
  ;; ============================================================================

  ;;' @section Association Lists
  ;;' Association lists (alists) map keys to values. Each entry is a list
  ;;' whose `car` is the key and whose `cadr` is the value. `assoc` and
  ;;' friends search by key; `rassoc` searches by value.

  ;; Internal: predicate (key-in-entry target-key) -> boolean; entry = alist element (list or dotted pair).
  ;; R's base == can return a vector; treat as match only when scalar #t or all true.
  ;;' @internal
  ;;' @description Wrap R's == to handle vector results; returns #t only if all elements match.
  (define _r==-pred
    (lambda (a b)
      (define comp-result (r-call "==" (list a b)))
      (if (r-call "is.logical" (list comp-result))
        (if (> (r-call "length" (list comp-result)) 1)
          (isTRUE (r-call "all" (list comp-result)))
          (isTRUE comp-result))
        #f)))

  ;;' @internal
  ;;' @description Find first alist entry with matching key using given predicate.
  (define _assoc-by-pred
    (lambda (key alist pred)
      (define items (_as-list alist))
      (define n (length items))
      (define result #nil)
      (define i 1)
      (while (and (<= i n) (null? result))
        (begin
          (define pair ([[ items i))
          (if (and (list? pair) (>= (length pair) 1))
            (if (pred (car pair) key)
              (set! result pair)
              #nil)
            #nil)
          (set! i (+ i 1))))
      result))

  ;;' @description Find first alist entry (list or dotted pair) with matching key (uses ==).
  ;;' @param key Key to search for.
  ;;' @param alist Association list of (key value) entries.
  ;;' @examples
  ;;' (assoc 'b (list (list 'a 1) (list 'b 2) (list 'c 3)))  ; => (b 2)
  ;;' (assoc 'z (list (list 'a 1) (list 'b 2)))               ; => #nil
  ;;' @assert
  ;;' (assert-equal (list 'b 2) (assoc 'b (list (list 'a 1) (list 'b 2) (list 'c 3))))
  ;;' (assert-equal #nil (assoc 'z (list (list 'a 1) (list 'b 2))))
  ;;' @seealso rassoc, assoc-by-identical?, assoc-by-==
  (define assoc
    (lambda (key alist)
      (_assoc-by-pred key alist _r==-pred)))

  ;;' @description Find first alist entry with key matching under equal? (deep structural comparison).
  ;;' Requires `(import equality)` for `equal?`.
  ;;' @param key Key to search for.
  ;;' @param alist Association list of (key value) entries.
  ;;' @seealso assoc
  (define assoc-by-equal? assoc)

  ;;' @description Find first alist entry with matching key (uses R's identical?).
  ;;' @param key Key to search for.
  ;;' @param alist Association list of (key value) entries.
  ;;' @seealso assoc, assoc-by-==
  (define assoc-by-identical?
    (lambda (key alist)
      (_assoc-by-pred key alist identical)))

  ;;' @description Find first alist entry with matching key (uses R's ==).
  ;;' @param key Key to search for.
  ;;' @param alist Association list of (key value) entries.
  ;;' @seealso assoc, assoc-by-identical?
  (define assoc-by-==
    (lambda (key alist)
      (_assoc-by-pred key alist _r==-pred)))

  ;;' @description assq cannot be properly implemented in R (no eq?).
  ;;' Use assoc (equal?), assoc-by-identical?, or assoc-by-== instead.
  ;;' @param key Key to search for (unused; raises error).
  ;;' @param alist Association list (unused; raises error).
  ;;' @note assq cannot be properly implemented in R (no eq?). Use assoc, assoc-by-identical?, or assoc-by-== instead.
  (define assq
    (lambda (key alist)
      (error "assq cannot be properly implemented in R. Use assoc (equal?), assoc-by-identical?, or assoc-by-== instead.")))

  ;;' @description assv cannot be properly implemented in R (no eqv?).
  ;;' Use assoc (equal?), assoc-by-identical?, or assoc-by-== instead.
  ;;' @param key Key to search for (unused; raises error).
  ;;' @param alist Association list (unused; raises error).
  ;;' @note assv cannot be properly implemented in R (no eqv?). Use assoc, assoc-by-identical?, or assoc-by-== instead.
  (define assv
    (lambda (key alist)
      (error "assv cannot be properly implemented in R. Use assoc (equal?), assoc-by-identical?, or assoc-by-== instead.")))

  ;;' @description Find first alist entry with matching value
  ;;' (searches cdr of entries; uses equal?).
  ;;' @param value Value to search for in the second position of each entry.
  ;;' @param alist Association list of (key value) entries.
  ;;' @examples
  ;;' (rassoc 2 (list (list 'a 1) (list 'b 2) (list 'c 3)))  ; => (b 2)
  ;;' (rassoc 9 (list (list 'a 1) (list 'b 2)))               ; => #nil
  ;;' @assert
  ;;' (assert-equal (list 'b 2) (rassoc 2 (list (list 'a 1) (list 'b 2) (list 'c 3))))
  ;;' (assert-equal #nil (rassoc 9 (list (list 'a 1) (list 'b 2))))
  ;;' @seealso assoc, rassoc-by-equal?
  (define rassoc
    (lambda (value alist)
      (define items (_as-list alist))
      (define n (length items))
      (define result #nil)
      (define i 1)
      (while (and (<= i n) (null? result))
        (begin
          (define pair ([[ items i))
          (if (and (list? pair) (>= (length pair) 2))
            (if (= (cadr pair) value)
              (set! result pair)
              #nil)
            #nil)
          (set! i (+ i 1))))
      result))

  ;;' @description Alias for rassoc: find first alist entry with value matching under equal?.
  ;;' @param value Value to search for in the second position of each entry.
  ;;' @param alist Association list of (key value) entries.
  ;;' @seealso rassoc
  (define rassoc-by-equal?
    (lambda (value alist)
      (rassoc value alist)))

  ;;' @section List Generation
  ;;' Utilities for creating lists of numbers or repeated values.

  ;;' @description Generate numeric range from start to end (exclusive) with optional step.
  ;;' @param start Start value (inclusive).
  ;;' @param end End value (exclusive).
  ;;' @param step-args Optional step size (default 1).
  ;;' @examples
  ;;' (range 0 5)       ; => (0 1 2 3 4)
  ;;' (range 0 10 2)    ; => (0 2 4 6 8)
  ;;' (range 5 0 -1)    ; => (5 4 3 2 1)
  ;;' @assert
  ;;' (assert-equal (list 0 1 2 3 4) (range 0 5))
  ;;' (assert-equal (list 0 2 4 6 8) (range 0 10 2))
  ;;' (assert-equal (list 5 4 3 2 1) (range 5 0 -1))
  ;;' @seealso iota, make-list
  (define range
    (lambda (start end . step-args)
      (define step (if (> (length step-args) 0) (car step-args) 1))
      (if (= step 0)
        (stop "range: step cannot be zero")
        (if (> step 0)
          ;; Positive step: start < end
          (if (>= start end)
            (list)
            (as.list (seq start (- end 1) step)))
          ;; Negative step: start > end
          (if (<= start end)
            (list)
            (as.list (seq start (+ end 1) step)))))))

  ;;' @description Generate sequence of count numbers starting from
  ;;' start (default 0) with step (default 1).
  ;;' @param count Number of elements to generate.
  ;;' @param args Optional start (default 0) and step (default 1).
  ;;' @examples
  ;;' (iota 5)          ; => (0 1 2 3 4)
  ;;' (iota 5 1)        ; => (1 2 3 4 5)
  ;;' (iota 5 0 2)      ; => (0 2 4 6 8)
  ;;' @assert
  ;;' (assert-equal (list 0 1 2 3 4) (iota 5))
  ;;' (assert-equal (list 1 2 3 4 5) (iota 5 1))
  ;;' (assert-equal (list 0 2 4 6 8) (iota 5 0 2))
  ;;' @seealso range, make-list
  (define iota
    (lambda (count . args)
      (define start (if (> (length args) 0) (car args) 0))
      (define step (if (> (length args) 1) (cadr args) 1))
      (if (<= count 0)
        (list)
        (as.list (seq start (+ start (* (- count 1) step)) step)))))

  ;;' @description Create list of n copies of value.
  ;;' @param n Number of copies.
  ;;' @param value Element to repeat.
  ;;' @examples
  ;;' (make-list 3 'x)    ; => (x x x)
  ;;' (make-list 0 'x)    ; => ()
  ;;' @assert
  ;;' (assert-equal (list 'x 'x 'x) (make-list 3 'x))
  ;;' (assert-equal (list) (make-list 0 'x))
  ;;' @seealso iota, range
  (define make-list
    (lambda (n value)
      (if (<= n 0)
        (list)
        (replicate n value :simplify #f))))

  ;;' @section Additional List Accessors

  ;;' @description Scheme-style list accessor (alias for nth).
  ;;' @param lst Source list.
  ;;' @param index 0-based index of the element to retrieve.
  ;;' @examples
  ;;' (list-ref (list 'a 'b 'c) 1)  ; => b
  ;;' @assert
  ;;' (assert-equal 'b (list-ref (list 'a 'b 'c) 1))
  ;;' @seealso nth
  (define list-ref
    (lambda (lst index)
      (nth lst index)))

  ;;' @description Return list without first k elements.
  ;;' @param lst Source list.
  ;;' @param k Number of leading elements to skip.
  ;;' @examples
  ;;' (list-tail (list 'a 'b 'c 'd) 2)  ; => (c d)
  ;;' (list-tail (list 1 2 3) 0)         ; => (1 2 3)
  ;;' @assert
  ;;' (assert-equal (list 'c 'd) (list-tail (list 'a 'b 'c 'd) 2))
  ;;' (assert-equal (list 1 2 3) (list-tail (list 1 2 3) 0))
  ;;' @seealso drop
  (define list-tail
    (lambda (lst k)
      (define items (_as-list lst))
      (if (<= k 0)
        items
        (if (>= k (length items))
          (list)
          (as.list (r-call "[" (list items (seq (+ k 1) (length items)))))))))
)
