;; super-send tests.
;;
;; super looks up methods starting at the *defining class*'s superclass —
;; not the receiver's class. This means an inherited method that uses
;; `super` always reaches the same parent regardless of where in the
;; subclass chain the receiver actually sits.

(set! st-test-pass 0)
(set! st-test-fail 0)
(set! st-test-fails (list))

(st-bootstrap-classes!)

(define ev (fn (src) (smalltalk-eval src)))
(define evp (fn (src) (smalltalk-eval-program src)))

;; ── 1. Basic super: subclass override calls parent ──
(st-class-define! "Animal" "Object" (list))
(st-class-add-method! "Animal" "speak"
  (st-parse-method "speak ^ #generic"))

(st-class-define! "Dog" "Animal" (list))
(st-class-add-method! "Dog" "speak"
  (st-parse-method "speak ^ super speak"))

(st-test
  "super reaches parent's speak"
  (str (evp "^ Dog new speak"))
  "generic")

(st-class-add-method! "Dog" "loud"
  (st-parse-method "loud ^ super speak , #'!' asString"))
;; The above tries to use `, #'!' asString` which won't quite work with my
;; primitives. Replace with a simpler test.
(st-class-add-method! "Dog" "loud"
  (st-parse-method "loud | s | s := super speak. ^ s"))

(st-test
  "method calls super and returns same"
  (str (evp "^ Dog new loud"))
  "generic")

;; ── 2. Super with argument ──
(st-class-add-method! "Animal" "greet:"
  (st-parse-method "greet: name ^ name , ' (animal)'"))
(st-class-add-method! "Dog" "greet:"
  (st-parse-method "greet: name ^ super greet: name"))

(st-test
  "super with arg reaches parent and threads value"
  (evp "^ Dog new greet: 'Rex'")
  "Rex (animal)")

;; ── 3. Inherited method uses *defining* class for super ──
;; A defines speak ^ 'A'
;; A defines speakLog: which sends `super speak`. super starts at Object → no
;; speak there → DNU. So invoke speakLog from A subclass to test that super
;; resolves to A's parent (Object), not the subclass's parent.
(st-class-define! "RootSpeaker" "Object" (list))
(st-class-add-method! "RootSpeaker" "speak"
  (st-parse-method "speak ^ #root"))
(st-class-add-method! "RootSpeaker" "speakDelegate"
  (st-parse-method "speakDelegate ^ super speak"))
;; Object has no speak (and we add a temporary DNU for testing).
(st-class-add-method! "Object" "doesNotUnderstand:"
  (st-parse-method "doesNotUnderstand: aMessage ^ #dnu"))

(st-class-define! "ChildSpeaker" "RootSpeaker" (list))
(st-class-add-method! "ChildSpeaker" "speak"
  (st-parse-method "speak ^ #child"))

(st-test
  "inherited speakDelegate uses RootSpeaker's super, not ChildSpeaker's"
  (str (evp "^ ChildSpeaker new speakDelegate"))
  "dnu")

;; A non-inherited path: ChildSpeaker overrides speak, but speakDelegate is
;; inherited from RootSpeaker. The super inside speakDelegate must resolve to
;; *Object* (RootSpeaker's parent), not to RootSpeaker (ChildSpeaker's parent).
(st-test
  "inherited method's super does not call subclass override"
  (str (evp "^ ChildSpeaker new speak"))
  "child")

;; Remove the Object DNU shim now that those tests are done.
(st-class-remove-method! "Object" "doesNotUnderstand:")

;; ── 4. Multi-level: A → B → C ──
(st-class-define! "GA" "Object" (list))
(st-class-add-method! "GA" "level"
  (st-parse-method "level ^ #ga"))

(st-class-define! "GB" "GA" (list))
(st-class-add-method! "GB" "level"
  (st-parse-method "level ^ super level"))

(st-class-define! "GC" "GB" (list))
(st-class-add-method! "GC" "level"
  (st-parse-method "level ^ super level"))

(st-test
  "super chains to grandparent"
  (str (evp "^ GC new level"))
  "ga")

;; ── 5. Super inside a block ──
(st-class-add-method! "Dog" "delayed"
  (st-parse-method "delayed ^ [super speak] value"))
(st-test
  "super inside a block resolves correctly"
  (str (evp "^ Dog new delayed"))
  "generic")

;; ── 6. Super send keeps receiver as self ──
(st-class-define! "Counter" "Object" (list "count"))
(st-class-add-method! "Counter" "init"
  (st-parse-method "init count := 0. ^ self"))
(st-class-add-method! "Counter" "incr"
  (st-parse-method "incr count := count + 1. ^ self"))
(st-class-add-method! "Counter" "count"
  (st-parse-method "count ^ count"))

(st-class-define! "DoubleCounter" "Counter" (list))
(st-class-add-method! "DoubleCounter" "incr"
  (st-parse-method "incr super incr. super incr. ^ self"))

(st-test
  "super uses same receiver — ivars on self update"
  (evp "| c | c := DoubleCounter new init. c incr. ^ c count")
  2)

;; ── 7. Super on a class without an immediate parent definition ──
;; Mid-chain class with no override at this level: super resolves correctly
;; through the missing rung.
(st-class-define! "Mid" "Animal" (list))
(st-class-define! "Pup" "Mid" (list))
(st-class-add-method! "Pup" "speak"
  (st-parse-method "speak ^ super speak"))

(st-test
  "super walks past intermediate class with no override"
  (str (evp "^ Pup new speak"))
  "generic")

;; ── 8. Super outside any method errors ──
;; (We don't have try/catch in SX from here; skip the negative test —
;;  documented behaviour is that st-super-send errors when method-class is nil.)

(list st-test-pass st-test-fail)
