;; ==========================================================================
;; test-sets.sx — Tests for set primitives
;; ==========================================================================

;; --------------------------------------------------------------------------
;; make-set / set?
;; --------------------------------------------------------------------------

(defsuite
  "sets:create"
  (deftest "make-set returns a set" (assert (set? (make-set))))
  (deftest "empty set has size 0" (assert= (set-size (make-set)) 0))
  (deftest
    "make-set from list"
    (let ((s (make-set (list 1 2 3)))) (assert= (set-size s) 3)))
  (deftest
    "make-set deduplicates"
    (let ((s (make-set (list 1 2 2 3 3)))) (assert= (set-size s) 3)))
  (deftest "set? true for sets" (assert (set? (make-set))))
  (deftest "set? false for list" (assert (not (set? (list 1 2 3)))))
  (deftest "set? false for nil" (assert (not (set? nil))))
  (deftest "set? false for number" (assert (not (set? 42)))))

;; --------------------------------------------------------------------------
;; set-add! / set-member? / set-remove!
;; --------------------------------------------------------------------------

(defsuite
  "sets:mutation"
  (deftest
    "set-add! increases size"
    (let
      ((s (make-set)))
      (set-add! s 1)
      (assert= (set-size s) 1)))
  (deftest
    "set-add! idempotent"
    (let
      ((s (make-set)))
      (set-add! s 1)
      (set-add! s 1)
      (assert= (set-size s) 1)))
  (deftest
    "set-member? true after add"
    (let
      ((s (make-set)))
      (set-add! s "hello")
      (assert (set-member? s "hello"))))
  (deftest
    "set-member? false for absent"
    (let
      ((s (make-set (list 1 2 3))))
      (assert (not (set-member? s 99)))))
  (deftest
    "set-remove! reduces size"
    (let
      ((s (make-set (list 1 2 3))))
      (set-remove! s 2)
      (assert= (set-size s) 2)))
  (deftest
    "set-remove! removes element"
    (let
      ((s (make-set (list 1 2 3))))
      (set-remove! s 2)
      (assert (not (set-member? s 2)))))
  (deftest
    "set-remove! no-op for absent"
    (let
      ((s (make-set (list 1 2 3))))
      (set-remove! s 99)
      (assert= (set-size s) 3)))
  (deftest
    "set handles strings"
    (let
      ((s (make-set)))
      (set-add! s "a")
      (set-add! s "b")
      (assert (and (set-member? s "a") (set-member? s "b")))))
  (deftest
    "set handles symbols"
    (let
      ((s (make-set)))
      (set-add! s (quote foo))
      (assert (set-member? s (quote foo))))))

;; --------------------------------------------------------------------------
;; set->list / list->set
;; --------------------------------------------------------------------------

(defsuite
  "sets:conversion"
  (deftest
    "list->set creates set"
    (let ((s (list->set (list 1 2 3)))) (assert (set? s))))
  (deftest
    "list->set size"
    (let ((s (list->set (list 1 2 3)))) (assert= (set-size s) 3)))
  (deftest
    "list->set deduplicates"
    (let ((s (list->set (list 1 1 2)))) (assert= (set-size s) 2)))
  (deftest
    "set->list has all elements"
    (let
      ((s (make-set (list 1 2 3)))
       (lst (set->list s)))
      (assert= (length lst) 3)))
  (deftest
    "set->list round-trip membership"
    (let
      ((s (make-set (list 10 20 30)))
       (lst (set->list s)))
      (assert
        (and
          (set-member? (list->set lst) 10)
          (set-member? (list->set lst) 20)
          (set-member? (list->set lst) 30))))))

;; --------------------------------------------------------------------------
;; set-union / set-intersection / set-difference
;; --------------------------------------------------------------------------

(defsuite
  "sets:operations"
  (deftest
    "union size"
    (let
      ((a (make-set (list 1 2 3)))
       (b (make-set (list 3 4 5))))
      (assert= (set-size (set-union a b)) 5)))
  (deftest
    "union contains all"
    (let
      ((u (set-union (make-set (list 1 2)) (make-set (list 3 4)))))
      (assert
        (and
          (set-member? u 1)
          (set-member? u 3)
          (set-member? u 4)))))
  (deftest
    "intersection size"
    (let
      ((a (make-set (list 1 2 3)))
       (b (make-set (list 2 3 4))))
      (assert= (set-size (set-intersection a b)) 2)))
  (deftest
    "intersection contains overlap"
    (let
      ((i (set-intersection (make-set (list 1 2 3)) (make-set (list 2 3 4)))))
      (assert (and (set-member? i 2) (set-member? i 3) (not (set-member? i 1))))))
  (deftest
    "intersection empty when disjoint"
    (let
      ((a (make-set (list 1 2)))
       (b (make-set (list 3 4))))
      (assert= (set-size (set-intersection a b)) 0)))
  (deftest
    "difference size"
    (let
      ((a (make-set (list 1 2 3)))
       (b (make-set (list 2 3))))
      (assert= (set-size (set-difference a b)) 1)))
  (deftest
    "difference keeps only a-exclusive"
    (let
      ((d (set-difference (make-set (list 1 2 3)) (make-set (list 2 3 4)))))
      (assert (and (set-member? d 1) (not (set-member? d 2)) (not (set-member? d 4))))))
  (deftest
    "union does not mutate inputs"
    (let
      ((a (make-set (list 1 2)))
       (b (make-set (list 3 4))))
      (set-union a b)
      (assert= (set-size a) 2))))

;; --------------------------------------------------------------------------
;; set-for-each / set-map
;; --------------------------------------------------------------------------

(defsuite
  "sets:higher-order"
  (deftest
    "set-for-each visits all"
    (let
      ((s (make-set (list 1 2 3)))
       (acc (list)))
      (set-for-each s (fn (v) (set! acc (cons v acc))))
      (assert= (length acc) 3)))
  (deftest
    "set-map doubles values"
    (let
      ((s (make-set (list 1 2 3)))
       (s2 (set-map s (fn (v) (* v 2)))))
      (assert
        (and
          (set-member? s2 2)
          (set-member? s2 4)
          (set-member? s2 6)))))
  (deftest
    "set-map result is a set"
    (assert (set? (set-map (make-set (list 1 2)) (fn (v) v))))))
