;; lib/common-lisp/runtime.sx — CL built-ins + condition system on SX
;;
;; Section 1-9:  Type predicates, arithmetic, characters, strings, gensym,
;;               multiple values, sets, radix formatting, list utilities.
;; Section 10:   Condition system (define-condition, signal/error/warn,
;;               handler-bind, handler-case, restart-case, invoke-restart).
;;
;; Primitives used from spec:
;;   char/char->integer/integer->char/char-upcase/char-downcase
;;   format   gensym   rational/rational?   make-set/set-member?/etc
;;   modulo/remainder/quotient/gcd/lcm/expt   number->string

;; ---------------------------------------------------------------------------
;; 1.  Type predicates
;; ---------------------------------------------------------------------------

(define (cl-null? x) (= x nil))
(define (cl-consp? x) (and (list? x) (not (cl-empty? x))))
(define (cl-listp? x) (or (cl-empty? x) (list? x)))
(define (cl-atom? x) (not (cl-consp? x)))

(define
  (cl-numberp? x)
  (let ((t (type-of x))) (or (= t "number") (= t "rational"))))

(prefix-rename "cl-"
  '(
    (integerp? integer?)
    (floatp? float?)
    (rationalp? rational?)
    ))

(define (cl-realp? x) (or (integer? x) (float? x) (rational? x)))

(prefix-rename "cl-"
  '(
    (characterp? char?)
    ))
(define cl-stringp? (fn (x) (= (type-of x) "string")))
(define cl-symbolp? (fn (x) (= (type-of x) "symbol")))
(define cl-keywordp? (fn (x) (= (type-of x) "keyword")))

(define
  (cl-functionp? x)
  (let
    ((t (type-of x)))
    (or
      (= t "function")
      (= t "lambda")
      (= t "native-fn")
      (= t "component"))))

(prefix-rename "cl-"
  '(
    (vectorp? vector?)
    (arrayp? vector?)
    ))

;; sx_server: (rest (list x)) returns () not nil — cl-empty? handles both
(define
  (cl-empty? x)
  (or (nil? x) (and (list? x) (= (len x) 0))))

;; ---------------------------------------------------------------------------
;; 2.  Arithmetic — thin aliases to spec primitives
;; ---------------------------------------------------------------------------

(prefix-rename "cl-"
  '(
    (mod modulo)
    (rem remainder)
    gcd
    lcm
    expt
    floor
    (ceiling ceil)
    truncate
    round
    ))
(define cl-abs (fn (x) (if (< x 0) (- 0 x) x)))
(define cl-min (fn (a b) (if (< a b) a b)))
(define cl-max (fn (a b) (if (> a b) a b)))
(prefix-rename "cl-"
  '(
    quotient
    ))

(define
  (cl-signum x)
  (cond
    ((> x 0) 1)
    ((< x 0) -1)
    (else 0)))

(define (cl-evenp? n) (= (modulo n 2) 0))
(define (cl-oddp? n) (= (modulo n 2) 1))
(define (cl-zerop? n) (= n 0))
(define (cl-plusp? n) (> n 0))
(define (cl-minusp? n) (< n 0))

;; ---------------------------------------------------------------------------
;; 3.  Character functions — alias spec char primitives + CL name mapping
;; ---------------------------------------------------------------------------

(prefix-rename "cl-"
  '(
    char->integer
    integer->char
    char-upcase
    char-downcase
    (char-code char->integer)
    (code-char integer->char)
    ))

(prefix-rename "cl-"
  '(
    char=?
    char<?
    char>?
    char<=?
    char>=?
    char-ci=?
    char-ci<?
    char-ci>?
    ))

;; Inline predicates — char-alphabetic?/char-numeric? unreliable in sx_server
(define
  (cl-alpha-char-p c)
  (let
    ((n (char->integer c)))
    (or
      (and (>= n 65) (<= n 90))
      (and (>= n 97) (<= n 122)))))

(define
  (cl-digit-char-p c)
  (let ((n (char->integer c))) (and (>= n 48) (<= n 57))))

(define
  (cl-alphanumericp c)
  (let
    ((n (char->integer c)))
    (or
      (and (>= n 48) (<= n 57))
      (and (>= n 65) (<= n 90))
      (and (>= n 97) (<= n 122)))))

(define
  (cl-upper-case-p c)
  (let ((n (char->integer c))) (and (>= n 65) (<= n 90))))

(define
  (cl-lower-case-p c)
  (let ((n (char->integer c))) (and (>= n 97) (<= n 122))))

;; Named character constants
(define cl-char-space (integer->char 32))
(define cl-char-newline (integer->char 10))
(define cl-char-tab (integer->char 9))
(define cl-char-backspace (integer->char 8))
(define cl-char-return (integer->char 13))
(define cl-char-null (integer->char 0))
(define cl-char-escape (integer->char 27))
(define cl-char-delete (integer->char 127))

;; ---------------------------------------------------------------------------
;; 4.  String + IO — use spec format and ports
;; ---------------------------------------------------------------------------

;; CL format: (cl-format nil "~a ~a" x y) — nil destination means return string
(define
  (cl-format dest template &rest args)
  (let ((s (apply format (cons template args)))) (if (= dest nil) s s)))

(prefix-rename "cl-"
  '(
    write-to-string
    (princ-to-string display-to-string)
    ))

;; CL read-from-string: parse value from a string using SX port
(define
  (cl-read-from-string s)
  (let ((p (open-input-string s))) (read p)))

;; String stream (output)
(prefix-rename "cl-"
  '(
    (make-string-output-stream open-output-string)
    (get-output-stream-string get-output-string)
    ))

;; String stream (input)
(prefix-rename "cl-"
  '(
    (make-string-input-stream open-input-string)
    ))

;; ---------------------------------------------------------------------------
;; 5.  Gensym
;; ---------------------------------------------------------------------------

(prefix-rename "cl-"
  '(
    gensym
    (gentemp gensym)
    ))

;; ---------------------------------------------------------------------------
;; 6.  Multiple values (CL: values / nth-value)
;; ---------------------------------------------------------------------------

(define (cl-values &rest args) {:_values true :_list args})

(define
  (cl-call-with-values producer consumer)
  (let
    ((mv (producer)))
    (if
      (and (dict? mv) (get mv :_values))
      (apply consumer (get mv :_list))
      (consumer mv))))

(define
  (cl-nth-value n mv)
  (cond
    ((and (dict? mv) (get mv :_values))
      (let
        ((lst (get mv :_list)))
        (if (>= n (len lst)) nil (nth lst n))))
    ((= n 0) mv)
    (else nil)))

;; ---------------------------------------------------------------------------
;; 7.  Sets (CL: adjoin / member / union / intersection / set-difference)
;; ---------------------------------------------------------------------------

(prefix-rename "cl-"
  '(
    make-set
    set?
    (set-add set-add!)
    (set-memberp set-member?)
    (set-remove set-remove!)
    set-union
    (set-intersect set-intersection)
    set-difference
    list->set
    set->list
    ))

;; CL: (member item list) — returns tail starting at item, or nil
(define
  (cl-member item lst)
  (cond
    ((cl-empty? lst) nil)
    ((equal? item (first lst)) lst)
    (else (cl-member item (rest lst)))))

;; CL: (adjoin item list) — cons only if not already present
(define (cl-adjoin item lst) (if (cl-member item lst) lst (cons item lst)))

;; ---------------------------------------------------------------------------
;; 8.  Radix formatting  (CL: (write-to-string n :base radix))
;; ---------------------------------------------------------------------------

(define (cl-integer-to-string n radix) (number->string n radix))

(define (cl-string-to-integer s radix) (string->number s radix))

;; CL ~R directive helpers
(define (cl-format-binary n) (number->string n 2))
(define (cl-format-octal n) (number->string n 8))
(define (cl-format-hex n) (number->string n 16))
(define (cl-format-decimal n) (number->string n 10))

;; ---------------------------------------------------------------------------
;; 9.  List utilities — cl-empty? guards against () from rest
;; ---------------------------------------------------------------------------

(define
  (cl-last lst)
  (cond
    ((cl-empty? lst) nil)
    ((cl-empty? (rest lst)) lst)
    (else (cl-last (rest lst)))))

(define
  (cl-butlast lst)
  (if
    (or (cl-empty? lst) (cl-empty? (rest lst)))
    nil
    (cons (first lst) (cl-butlast (rest lst)))))

(define
  (cl-nthcdr n lst)
  (if (= n 0) lst (cl-nthcdr (- n 1) (rest lst))))

(define (cl-nth n lst) (first (cl-nthcdr n lst)))

(define (cl-list-length lst) (len lst))

(define
  (cl-copy-list lst)
  (if (cl-empty? lst) nil (cons (first lst) (cl-copy-list (rest lst)))))

(define
  (cl-flatten lst)
  (cond
    ((cl-empty? lst) nil)
    ((list? (first lst))
      (append (cl-flatten (first lst)) (cl-flatten (rest lst))))
    (else (cons (first lst) (cl-flatten (rest lst))))))

;; CL: (assoc key alist) — returns matching pair or nil
(define
  (cl-assoc key alist)
  (cond
    ((cl-empty? alist) nil)
    ((equal? key (first (first alist))) (first alist))
    (else (cl-assoc key (rest alist)))))

;; CL: (rassoc val alist) — reverse assoc (match on second element)
(define
  (cl-rassoc val alist)
  (cond
    ((cl-empty? alist) nil)
    ((equal? val (first (rest (first alist)))) (first alist))
    (else (cl-rassoc val (rest alist)))))

;; CL: (getf plist key) — property list lookup
(define
  (cl-getf plist key)
  (cond
    ((or (cl-empty? plist) (cl-empty? (rest plist))) nil)
    ((equal? (first plist) key) (first (rest plist)))
    (else (cl-getf (rest (rest plist)) key))))

;; ---------------------------------------------------------------------------
;; 10.  Condition system (Phase 3)
;;
;; Condition objects:
;;   {:cl-type "cl-condition" :class "NAME" :slots {slot-name val ...}}
;;
;; The built-in handler-bind / restart-case expect LITERAL handler specs in
;; source (they operate on the raw AST), so we implement our own handler and
;; restart stacks as mutable SX globals.
;; ---------------------------------------------------------------------------

;; ── condition class registry ───────────────────────────────────────────────
;;
;; Populated at load time with all ANSI standard condition types.
;; Also mutated by cl-define-condition.

(define
  cl-condition-classes
  (dict
    "condition"
    {:parents (list) :slots (list) :name "condition"}
    "serious-condition"
    {:parents (list "condition") :slots (list) :name "serious-condition"}
    "error"
    {:parents (list "serious-condition") :slots (list) :name "error"}
    "warning"
    {:parents (list "condition") :slots (list) :name "warning"}
    "simple-condition"
    {:parents (list "condition") :slots (list "format-control" "format-arguments") :name "simple-condition"}
    "simple-error"
    {:parents (list "error" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-error"}
    "simple-warning"
    {:parents (list "warning" "simple-condition") :slots (list "format-control" "format-arguments") :name "simple-warning"}
    "type-error"
    {:parents (list "error") :slots (list "datum" "expected-type") :name "type-error"}
    "arithmetic-error"
    {:parents (list "error") :slots (list "operation" "operands") :name "arithmetic-error"}
    "division-by-zero"
    {:parents (list "arithmetic-error") :slots (list) :name "division-by-zero"}
    "cell-error"
    {:parents (list "error") :slots (list "name") :name "cell-error"}
    "unbound-variable"
    {:parents (list "cell-error") :slots (list) :name "unbound-variable"}
    "undefined-function"
    {:parents (list "cell-error") :slots (list) :name "undefined-function"}
    "program-error"
    {:parents (list "error") :slots (list) :name "program-error"}
    "storage-condition"
    {:parents (list "serious-condition") :slots (list) :name "storage-condition"}))

;; ── condition predicates ───────────────────────────────────────────────────

(define
  cl-condition?
  (fn (x) (and (dict? x) (= (get x "cl-type") "cl-condition"))))

;; cl-condition-of-type? walks the class hierarchy.
;; We capture cl-condition-classes at define time via let to avoid
;; free-variable scoping issues at call time.

(define
  cl-condition-of-type?
  (let
    ((classes cl-condition-classes))
    (fn
      (c type-name)
      (if
        (not (cl-condition? c))
        false
        (let
          ((class-name (get c "class")))
          (define
            check
            (fn
              (n)
              (if
                (= n type-name)
                true
                (let
                  ((entry (get classes n)))
                  (if
                    (nil? entry)
                    false
                    (some (fn (p) (check p)) (get entry "parents")))))))
          (check class-name))))))

;; ── condition constructors ─────────────────────────────────────────────────

;; cl-define-condition registers a new condition class.
;; name:       string (condition class name)
;; parents:    list of strings (parent class names)
;; slot-names: list of strings

(define
  cl-define-condition
  (fn
    (name parents slot-names)
    (begin (dict-set! cl-condition-classes name {:parents parents :slots slot-names :name name}) name)))

;; cl-make-condition constructs a condition object.
;; Keyword args (alternating slot-name/value pairs) populate the slots dict.

(define
  cl-make-condition
  (fn
    (name &rest kw-args)
    (let
      ((slots (dict)))
      (define
        fill
        (fn
          (args)
          (when
            (>= (len args) 2)
            (begin
              (dict-set! slots (first args) (first (rest args)))
              (fill (rest (rest args)))))))
      (fill kw-args)
      {:cl-type "cl-condition" :slots slots :class name})))

;; ── condition accessors ────────────────────────────────────────────────────

(define
  cl-condition-slot
  (fn
    (c slot-name)
    (if (cl-condition? c) (get (get c "slots") slot-name) nil)))

(define
  cl-condition-message
  (fn
    (c)
    (if
      (not (cl-condition? c))
      (str c)
      (let
        ((slots (get c "slots")))
        (or
          (get slots "message")
          (get slots "format-control")
          (str "Condition: " (get c "class")))))))

(define
  cl-simple-condition-format-control
  (fn (c) (cl-condition-slot c "format-control")))

(define
  cl-simple-condition-format-arguments
  (fn (c) (cl-condition-slot c "format-arguments")))

(define cl-type-error-datum (fn (c) (cl-condition-slot c "datum")))

(define
  cl-type-error-expected-type
  (fn (c) (cl-condition-slot c "expected-type")))

(define
  cl-arithmetic-error-operation
  (fn (c) (cl-condition-slot c "operation")))

(define
  cl-arithmetic-error-operands
  (fn (c) (cl-condition-slot c "operands")))

;; ── mutable handler + restart stacks ──────────────────────────────────────
;;
;; Handler entry: {:type "type-name" :fn (fn (condition) result)}
;; Restart entry: {:name "restart-name" :fn (fn (&optional arg) result) :escape k}
;;
;; New handlers are prepended (checked first = most recent handler wins).

(define cl-handler-stack (list))
(define cl-restart-stack (list))

(define
  cl-push-handlers
  (fn (entries) (set! cl-handler-stack (append entries cl-handler-stack))))

(define
  cl-pop-handlers
  (fn
    (n)
    (set! cl-handler-stack (slice cl-handler-stack n (len cl-handler-stack)))))

(define
  cl-push-restarts
  (fn (entries) (set! cl-restart-stack (append entries cl-restart-stack))))

(define
  cl-pop-restarts
  (fn
    (n)
    (set! cl-restart-stack (slice cl-restart-stack n (len cl-restart-stack)))))

;; ── *debugger-hook* + invoke-debugger ────────────────────────────────────
;;
;; cl-debugger-hook: called when an error propagates with no handler.
;; Signature: (fn (condition hook) result). The hook arg is itself
;; (so the hook can rebind it to nil to prevent recursion).
;; nil = use default (re-raise as host error).

(define cl-debugger-hook nil)

(define cl-invoke-debugger
  (fn (c)
    (if (nil? cl-debugger-hook)
      (error (str "Debugger: " (cl-condition-message c)))
      (let ((hook cl-debugger-hook))
        (set! cl-debugger-hook nil)
        (let ((result (hook c hook)))
          (set! cl-debugger-hook hook)
          result)))))

;; ── *break-on-signals* ────────────────────────────────────────────────────
;;
;; When set to a type name string, cl-signal invokes the debugger hook
;; before walking handlers if the condition is of that type.
;; nil = disabled (ANSI default).

(define cl-break-on-signals nil)

;; ── invoke-restart-interactively ──────────────────────────────────────────
;;
;; Like invoke-restart but calls the restart's fn with no arguments
;; (real CL would prompt the user for each arg via :interactive).

(define cl-invoke-restart-interactively
  (fn (name)
    (let ((entry (cl-find-restart-entry name cl-restart-stack)))
      (if (nil? entry)
        (error (str "No active restart: " name))
        (let ((restart-fn (get entry "fn"))
              (escape     (get entry "escape")))
          (escape (restart-fn)))))))

;; ── cl-signal (non-unwinding) ─────────────────────────────────────────────
;;
;; Walks cl-handler-stack; for each matching entry, calls the handler fn.
;; Handlers return normally — signal continues to the next matching handler.

(define
  cl-signal-obj
  (fn
    (obj stack)
    (if
      (empty? stack)
      nil
      (let
        ((entry (first stack)))
        (if
          (cl-condition-of-type? obj (get entry "type"))
          (begin ((get entry "fn") obj) (cl-signal-obj obj (rest stack)))
          (cl-signal-obj obj (rest stack)))))))

(define cl-signal
  (fn (c)
    (let ((obj (if (cl-condition? c)
                 c
                 (cl-make-condition "simple-condition"
                   "format-control" (str c)))))
      ;; *break-on-signals*: invoke debugger hook when type matches
      (when (and (not (nil? cl-break-on-signals))
                 (cl-condition-of-type? obj cl-break-on-signals))
        (cl-invoke-debugger obj))
      (cl-signal-obj obj cl-handler-stack))))

;; ── cl-error ───────────────────────────────────────────────────────────────
;;
;; Signals an error. If no handler catches it, raises a host-level error.

(define
  cl-error
  (fn
    (c &rest args)
    (let
      ((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-error" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-error" "format-control" (str c))))))
      (cl-signal-obj obj cl-handler-stack)
      (cl-invoke-debugger obj))))

;; ── cl-warn ────────────────────────────────────────────────────────────────

(define
  cl-warn
  (fn
    (c &rest args)
    (let
      ((obj (cond ((cl-condition? c) c) ((string? c) (cl-make-condition "simple-warning" "format-control" c "format-arguments" args)) (:else (cl-make-condition "simple-warning" "format-control" (str c))))))
      (cl-signal-obj obj cl-handler-stack))))

;; ── cl-handler-bind (non-unwinding) ───────────────────────────────────────
;;
;; bindings: list of (type-name handler-fn) pairs
;; thunk:    (fn () body)

(define
  cl-handler-bind
  (fn
    (bindings thunk)
    (let
      ((entries (map (fn (b) {:fn (first (rest b)) :type (first b)}) bindings)))
      (begin
        (cl-push-handlers entries)
        (let
          ((result (thunk)))
          (begin (cl-pop-handlers (len entries)) result))))))

;; ── cl-handler-case (unwinding) ───────────────────────────────────────────
;;
;; thunk: (fn () body)
;; cases: list of (type-name handler-fn) pairs
;;
;; Uses call/cc for the escape continuation.

(define
  cl-handler-case
  (fn
    (thunk &rest cases)
    (call/cc
      (fn
        (escape)
        (let
          ((entries (map (fn (c) {:fn (fn (x) (escape ((first (rest c)) x))) :type (first c)}) cases)))
          (begin
            (cl-push-handlers entries)
            (let
              ((result (thunk)))
              (begin (cl-pop-handlers (len entries)) result))))))))

;; ── cl-restart-case ────────────────────────────────────────────────────────
;;
;; thunk:    (fn () body)
;; restarts: list of (name params body-fn) triples
;;   body-fn is (fn () val) or (fn (arg) val)

(define
  cl-restart-case
  (fn
    (thunk &rest restarts)
    (call/cc
      (fn
        (escape)
        (let
          ((entries (map (fn (r) {:fn (first (rest (rest r))) :escape escape :name (first r)}) restarts)))
          (begin
            (cl-push-restarts entries)
            (let
              ((result (thunk)))
              (begin (cl-pop-restarts (len entries)) result))))))))

;; ── cl-with-simple-restart ─────────────────────────────────────────────────

(define
  cl-with-simple-restart
  (fn
    (name description thunk)
    (cl-restart-case thunk (list name (list) (fn () nil)))))

;; ── find-restart / invoke-restart / compute-restarts ──────────────────────

(define
  cl-find-restart-entry
  (fn
    (name stack)
    (if
      (empty? stack)
      nil
      (let
        ((entry (first stack)))
        (if
          (= (get entry "name") name)
          entry
          (cl-find-restart-entry name (rest stack)))))))

(define
  cl-find-restart
  (fn (name) (cl-find-restart-entry name cl-restart-stack)))

(define
  cl-invoke-restart
  (fn
    (name &rest args)
    (let
      ((entry (cl-find-restart-entry name cl-restart-stack)))
      (if
        (nil? entry)
        (error (str "No active restart: " name))
        (let
          ((restart-fn (get entry "fn")) (escape (get entry "escape")))
          (escape
            (if (empty? args) (restart-fn) (restart-fn (first args)))))))))

(define
  cl-compute-restarts
  (fn () (map (fn (e) (get e "name")) cl-restart-stack)))

;; ── with-condition-restarts (stub — association is advisory) ──────────────

(define cl-with-condition-restarts (fn (c restarts thunk) (thunk)))

;; ── cl-cerror ──────────────────────────────────────────────────────────────
;;
;; Signals a continuable error. The "continue" restart is established;
;; invoke-restart "continue" to proceed past the error.



;; ── cl-cerror ──────────────────────────────────────────────────────────────
;;
;; Signals a continuable error. The "continue" restart is established;
;; invoke-restart "continue" to proceed past the error.

(define cl-cerror
  (fn (continue-string c &rest args)
    (let ((obj (if (cl-condition? c)
                 c
                 (cl-make-condition "simple-error"
                   "format-control" (str c)
                   "format-arguments" args))))
      (cl-restart-case
        (fn () (cl-signal-obj obj cl-handler-stack))
        (list "continue" (list) (fn () nil))))))