;; mop-trace.sx — :before/:after method tracing with CLOS
;;
;; Classic CLOS pattern: instrument generic functions with :before and :after
;; qualifiers to print call/return traces without modifying the primary method.
;;
;; Depends on: lib/common-lisp/runtime.sx, lib/common-lisp/clos.sx

;; ── trace log (mutable accumulator) ───────────────────────────────────────

(define trace-log (list))

(define
  trace-push
  (fn (msg) (set! trace-log (append trace-log (list msg)))))

(define trace-clear (fn () (set! trace-log (list))))

;; ── domain classes ─────────────────────────────────────────────────────────

(clos-defclass "shape" (list "t") (list {:initform "white" :initarg ":color" :reader nil :writer nil :accessor nil :name "color"}))

(clos-defclass "circle" (list "shape") (list {:initform 1 :initarg ":radius" :reader nil :writer nil :accessor nil :name "radius"}))

(clos-defclass "rect" (list "shape") (list {:initform 1 :initarg ":width" :reader nil :writer nil :accessor nil :name "width"} {:initform 1 :initarg ":height" :reader nil :writer nil :accessor nil :name "height"}))

;; ── generic function: area ─────────────────────────────────────────────────

(clos-defgeneric "area" {})

;; primary methods
(clos-defmethod
  "area"
  (list)
  (list "circle")
  (fn
    (args next-fn)
    (let
      ((c (first args)))
      (let ((r (clos-slot-value c "radius"))) (* r r)))))

(clos-defmethod
  "area"
  (list)
  (list "rect")
  (fn
    (args next-fn)
    (let
      ((r (first args)))
      (* (clos-slot-value r "width") (clos-slot-value r "height")))))

;; :before tracing
(clos-defmethod
  "area"
  (list "before")
  (list "shape")
  (fn
    (args next-fn)
    (trace-push (str "BEFORE area(" (clos-class-of (first args)) ")"))))

;; :after tracing
(clos-defmethod
  "area"
  (list "after")
  (list "shape")
  (fn
    (args next-fn)
    (trace-push (str "AFTER area(" (clos-class-of (first args)) ")"))))

;; ── generic function: describe-shape ──────────────────────────────────────

(clos-defgeneric "describe-shape" {})

(clos-defmethod
  "describe-shape"
  (list)
  (list "shape")
  (fn
    (args next-fn)
    (let
      ((s (first args)))
      (str "shape[" (clos-slot-value s "color") "]"))))

(clos-defmethod
  "describe-shape"
  (list)
  (list "circle")
  (fn
    (args next-fn)
    (let
      ((c (first args)))
      (str
        "circle[r="
        (clos-slot-value c "radius")
        " "
        (clos-call-next-method next-fn)
        "]"))))

(clos-defmethod
  "describe-shape"
  (list)
  (list "rect")
  (fn
    (args next-fn)
    (let
      ((r (first args)))
      (str
        "rect["
        (clos-slot-value r "width")
        "x"
        (clos-slot-value r "height")
        " "
        (clos-call-next-method next-fn)
        "]"))))

;; :before on base shape (fires for all subclasses too)
(clos-defmethod
  "describe-shape"
  (list "before")
  (list "shape")
  (fn
    (args next-fn)
    (trace-push
      (str "BEFORE describe-shape(" (clos-class-of (first args)) ")"))))

;; ── tests ─────────────────────────────────────────────────────────────────

(define passed 0)
(define failed 0)
(define failures (list))

(define
  check
  (fn
    (label got expected)
    (if
      (= got expected)
      (set! passed (+ passed 1))
      (begin
        (set! failed (+ failed 1))
        (set!
          failures
          (append
            failures
            (list
              (str
                "FAIL ["
                label
                "]: got="
                (inspect got)
                " expected="
                (inspect expected)))))))))

;; ── area tests ────────────────────────────────────────────────────────────

;; circle area = r*r (no pi — integer arithmetic for predictability)
(let
  ((c (clos-make-instance "circle" ":radius" 5 ":color" "red")))
  (do
    (trace-clear)
    (check "circle area" (clos-call-generic "area" (list c)) 25)
    (check
      ":before fired for circle"
      (= (first trace-log) "BEFORE area(circle)")
      true)
    (check
      ":after fired for circle"
      (= (first (rest trace-log)) "AFTER area(circle)")
      true)
    (check "trace length 2" (len trace-log) 2)))

;; rect area = w*h
(let
  ((r (clos-make-instance "rect" ":width" 4 ":height" 6 ":color" "blue")))
  (do
    (trace-clear)
    (check "rect area" (clos-call-generic "area" (list r)) 24)
    (check
      ":before fired for rect"
      (= (first trace-log) "BEFORE area(rect)")
      true)
    (check
      ":after fired for rect"
      (= (first (rest trace-log)) "AFTER area(rect)")
      true)
    (check "trace length 2 (rect)" (len trace-log) 2)))

;; ── describe-shape tests ───────────────────────────────────────────────────

(let
  ((c (clos-make-instance "circle" ":radius" 3 ":color" "green")))
  (do
    (trace-clear)
    (check
      "circle describe"
      (clos-call-generic "describe-shape" (list c))
      "circle[r=3 shape[green]]")
    (check
      ":before fired for describe circle"
      (= (first trace-log) "BEFORE describe-shape(circle)")
      true)))

(let
  ((r (clos-make-instance "rect" ":width" 2 ":height" 7 ":color" "black")))
  (do
    (trace-clear)
    (check
      "rect describe"
      (clos-call-generic "describe-shape" (list r))
      "rect[2x7 shape[black]]")
    (check
      ":before fired for describe rect"
      (= (first trace-log) "BEFORE describe-shape(rect)")
      true)))

;; ── call-next-method: circle -> shape ─────────────────────────────────────

(let
  ((c (clos-make-instance "circle" ":radius" 1 ":color" "purple")))
  (check
    "call-next-method result in describe"
    (clos-call-generic "describe-shape" (list c))
    "circle[r=1 shape[purple]]"))

;; ── summary ────────────────────────────────────────────────────────────────

(define mop-passed passed)
(define mop-failed failed)
(define mop-failures failures)
