;; Erlang sequential evaluator — tree-walking interpreter over the
;; parser AST. Phase 2 of plans/erlang-on-sx.md.
;;
;; Entry points:
;;   (erlang-eval-ast SRC)         -- parse body, eval, return last value
;;   (er-eval-expr NODE ENV)       -- evaluate one AST node
;;   (er-eval-body NODES ENV)      -- evaluate a comma-sequence, return last
;;
;; Runtime values:
;;   integers / floats      -> SX number
;;   atoms                  -> {:tag "atom" :name <string>}
;;   booleans               -> atoms 'true' / 'false'
;;   strings                -> SX string  (char-list semantics deferred)
;;   empty list             -> {:tag "nil"}
;;   cons cell              -> {:tag "cons" :head V :tail V}
;;   tuple                  -> {:tag "tuple" :elements (list V ...)}
;;
;; Environment: mutable dict from variable name (string) to value.

;; ── value constructors / predicates ────────────────────────────────
(define er-mk-atom (fn (name) {:name name :tag "atom"}))
(define er-atom-true (er-mk-atom "true"))
(define er-atom-false (er-mk-atom "false"))
(define er-mk-nil (fn () {:tag "nil"}))
(define er-mk-cons (fn (h t) {:tag "cons" :head h :tail t}))
(define er-mk-tuple (fn (elems) {:tag "tuple" :elements elems}))
(define er-mk-binary (fn (bytes) {:tag "binary" :bytes bytes}))
(define er-binary? (fn (v) (er-is-tagged? v "binary")))
(define er-bool (fn (b) (if b er-atom-true er-atom-false)))

(define
  er-is-tagged?
  (fn (v tag) (and (= (type-of v) "dict") (= (get v :tag) tag))))
(define er-atom? (fn (v) (er-is-tagged? v "atom")))
(define er-nil? (fn (v) (er-is-tagged? v "nil")))
(define er-cons? (fn (v) (er-is-tagged? v "cons")))
(define er-tuple? (fn (v) (er-is-tagged? v "tuple")))

(define
  er-is-atom-named?
  (fn (v name) (and (er-atom? v) (= (get v :name) name))))
(define er-truthy? (fn (v) (er-is-atom-named? v "true")))

;; ── environment ───────────────────────────────────────────────────
(define er-env-new (fn () {}))

(define
  er-env-lookup
  (fn
    (env name)
    (if
      (dict-has? env name)
      (get env name)
      (error (str "Erlang: unbound variable '" name "'")))))

(define er-env-bind! (fn (env name val) (dict-set! env name val)))

;; ── entry ─────────────────────────────────────────────────────────
(define
  erlang-eval-ast
  (fn
    (src)
    (let
      ((st (er-state-make (er-tokenize src))))
      (let
        ((body (er-parse-body st)))
        (er-sched-init!)
        (let
          ((env (er-env-new)))
          (let
            ((main-fun
              (er-mk-fun
                (list
                  {:patterns (list)
                    :body body
                    :guards (list)
                    :name nil})
                env)))
            (let
              ((main-proc (er-proc-new! env)))
              (dict-set! main-proc :initial-fun main-fun)
              (er-sched-run-all!)
              (let
                ((main-pid (get main-proc :pid)))
                (if
                  (not (= (er-proc-field main-pid :state) "dead"))
                  (error
                    "Erlang: deadlock — main process never terminated")
                  (er-proc-field main-pid :exit-result))))))))))

(define
  er-eval-body
  (fn
    (exprs env)
    (let
      ((last (list nil)))
      (for-each
        (fn (i) (set-nth! last 0 (er-eval-expr (nth exprs i) env)))
        (range 0 (len exprs)))
      (nth last 0))))

;; ── dispatch ──────────────────────────────────────────────────────
(define
  er-eval-expr
  (fn
    (node env)
    (let
      ((ty (get node :type)))
      (cond
        (= ty "integer")
          (let ((n (parse-number (get node :value))))
            (cond
              (= n nil) (error (str "Erlang: invalid integer literal: "
                                     (get node :value)))
              :else (truncate n)))
        (= ty "float") (parse-number (get node :value))
        (= ty "atom") (er-mk-atom (get node :value))
        (= ty "string") (get node :value)
        (= ty "nil") (er-mk-nil)
        (= ty "var") (er-eval-var node env)
        (= ty "tuple") (er-eval-tuple node env)
        (= ty "cons") (er-eval-cons node env)
        (= ty "op") (er-eval-op node env)
        (= ty "unop") (er-eval-unop node env)
        (= ty "block") (er-eval-body (get node :exprs) env)
        (= ty "if") (er-eval-if node env)
        (= ty "case") (er-eval-case node env)
        (= ty "call") (er-eval-call node env)
        (= ty "fun") (er-eval-fun node env)
        (= ty "send") (er-eval-send node env)
        (= ty "receive") (er-eval-receive node env)
        (= ty "try") (er-eval-try node env)
        (= ty "lc") (er-eval-lc node env)
        (= ty "binary") (er-eval-binary node env)
        (= ty "match") (er-eval-match node env)
        :else (error (str "Erlang eval: unsupported node type '" ty "'"))))))

(define
  er-eval-var
  (fn
    (node env)
    (let
      ((name (get node :name)))
      (if
        (= name "_")
        (error "Erlang: '_' cannot be used as a value")
        (er-env-lookup env name)))))

(define
  er-eval-tuple
  (fn
    (node env)
    (let
      ((out (list)))
      (for-each
        (fn
          (i)
          (append! out (er-eval-expr (nth (get node :elements) i) env)))
        (range 0 (len (get node :elements))))
      (er-mk-tuple out))))

(define
  er-eval-cons
  (fn
    (node env)
    (er-mk-cons
      (er-eval-expr (get node :head) env)
      (er-eval-expr (get node :tail) env))))

;; ── match expression ─────────────────────────────────────────────
(define
  er-eval-match
  (fn
    (node env)
    (let
      ((lhs (get node :lhs))
        (rhs-val (er-eval-expr (get node :rhs) env)))
      (if
        (er-match! lhs rhs-val env)
        rhs-val
        (error "Erlang: badmatch")))))

;; ── pattern matching ─────────────────────────────────────────────
;; Unifies PAT against VAL, binding fresh vars into ENV.
;; Returns true on success, false otherwise. On failure ENV may hold
;; partial bindings — callers trying multiple clauses must snapshot
;; ENV and restore it between attempts.
(define
  er-match!
  (fn
    (pat val env)
    (let
      ((ty (get pat :type)))
      (cond
        (= ty "var") (er-match-var pat val env)
        (= ty "integer")
        (and (= (type-of val) "number") (= (parse-number (get pat :value)) val))
        (= ty "float")
        (and (= (type-of val) "number") (= (parse-number (get pat :value)) val))
        (= ty "atom") (and (er-atom? val) (= (get val :name) (get pat :value)))
        (= ty "string")
        (and (= (type-of val) "string") (= val (get pat :value)))
        (= ty "nil") (er-nil? val)
        (= ty "tuple") (er-match-tuple pat val env)
        (= ty "cons") (er-match-cons pat val env)
        (= ty "binary") (er-match-binary pat val env)
        :else (error (str "Erlang match: unsupported pattern type '" ty "'"))))))

(define
  er-match-var
  (fn
    (pat val env)
    (let
      ((name (get pat :name)))
      (cond
        (= name "_") true
        (dict-has? env name) (er-equal? (get env name) val)
        :else (do (er-env-bind! env name val) true)))))

(define
  er-match-tuple
  (fn
    (pat val env)
    (and
      (er-tuple? val)
      (let
        ((ps (get pat :elements)) (vs (get val :elements)))
        (if (not (= (len ps) (len vs))) false (er-match-all ps vs 0 env))))))

(define
  er-match-all
  (fn
    (ps vs i env)
    (if
      (>= i (len ps))
      true
      (if
        (er-match! (nth ps i) (nth vs i) env)
        (er-match-all ps vs (+ i 1) env)
        false))))

(define
  er-match-cons
  (fn
    (pat val env)
    (and
      (er-cons? val)
      (and
        (er-match! (get pat :head) (get val :head) env)
        (er-match! (get pat :tail) (get val :tail) env)))))

;; Match `<<Seg1, Seg2, ...>>` against a binary value. Walks the
;; segment list left-to-right, consuming bytes from the front of the
;; binary for each segment. Integer segments decode big-endian and
;; bind/check the pattern; binary-spec segments without size capture
;; the trailing bytes as a binary value.
(define
  er-match-binary
  (fn
    (pat val env)
    (and
      (er-binary? val)
      (let
        ((segs (get pat :segments)) (cursor (list 0)))
        (and
          (er-match-binary-segs segs val env cursor 0)
          (= (nth cursor 0) (len (get val :bytes))))))))

(define
  er-match-binary-segs
  (fn
    (segs val env cursor i)
    (cond
      (>= i (len segs)) true
      :else (let
        ((seg (nth segs i)))
        (let
          ((spec (get seg :spec))
            (size-node (get seg :size)))
          (cond
            (= spec "integer")
            (er-match-binary-int seg val env cursor segs i)
            (= spec "binary")
            (er-match-binary-tail seg val env cursor segs i)
            :else false))))))

(define
  er-match-binary-int
  (fn
    (seg val env cursor segs i)
    (let
      ((bits (cond
               (= (get seg :size) nil) 8
               :else (er-eval-expr (get seg :size) env))))
      (cond
        (or (not (= (remainder bits 8) 0)) (<= bits 0)) false
        :else (let
          ((nbytes (truncate (/ bits 8))) (bytes (get val :bytes)) (start (nth cursor 0)))
          (cond
            (> (+ start nbytes) (len bytes)) false
            :else (let
              ((decoded (er-decode-int bytes start nbytes)))
              (set-nth! cursor 0 (+ start nbytes))
              (and
                (er-match! (get seg :value) decoded env)
                (er-match-binary-segs segs val env cursor (+ i 1))))))))))

(define
  er-decode-int
  (fn
    (bytes start nbytes)
    (let
      ((acc (list 0)))
      (for-each
        (fn
          (j)
          (set-nth!
            acc
            0
            (+ (* (nth acc 0) 256) (nth bytes (+ start j)))))
        (range 0 nbytes))
      (nth acc 0))))

(define
  er-match-binary-tail
  (fn
    (seg val env cursor segs i)
    (cond
      (not (= (get seg :size) nil)) false
      (not (= (+ i 1) (len segs))) false
      :else (let
        ((bytes (get val :bytes))
          (start (nth cursor 0))
          (rest-bytes (list)))
        (for-each
          (fn (k) (append! rest-bytes (nth bytes k)))
          (range start (len bytes)))
        (set-nth! cursor 0 (len bytes))
        (er-match! (get seg :value) (er-mk-binary rest-bytes) env)))))

;; ── env snapshot / restore ────────────────────────────────────────
(define
  er-env-copy
  (fn
    (env)
    (let
      ((out {}))
      (for-each (fn (k) (dict-set! out k (get env k))) (keys env))
      out)))

(define
  er-env-restore!
  (fn
    (env snap)
    (for-each (fn (k) (dict-delete! env k)) (keys env))
    (for-each (fn (k) (dict-set! env k (get snap k))) (keys snap))))

;; ── case ─────────────────────────────────────────────────────────
(define
  er-eval-case
  (fn
    (node env)
    (let
      ((subject (er-eval-expr (get node :expr) env)))
      (er-eval-case-clauses (get node :clauses) 0 subject env))))

(define
  er-eval-case-clauses
  (fn
    (clauses i subject env)
    (if
      (>= i (len clauses))
      (error "Erlang: case_clause: no matching clause")
      (let
        ((c (nth clauses i)) (snap (er-env-copy env)))
        (if
          (and
            (er-match! (get c :pattern) subject env)
            (er-eval-guards (get c :guards) env))
          (er-eval-body (get c :body) env)
          (do
            (er-env-restore! env snap)
            (er-eval-case-clauses clauses (+ i 1) subject env)))))))

;; ── operators ─────────────────────────────────────────────────────
(define
  er-eval-op
  (fn
    (node env)
    (let
      ((op (get node :op)) (args (get node :args)))
      (cond
        (= op "andalso") (er-eval-andalso args env)
        (= op "orelse") (er-eval-orelse args env)
        :else (er-apply-binop
          op
          (er-eval-expr (nth args 0) env)
          (er-eval-expr (nth args 1) env))))))

(define
  er-eval-andalso
  (fn
    (args env)
    (let
      ((a (er-eval-expr (nth args 0) env)))
      (if (er-truthy? a) (er-eval-expr (nth args 1) env) a))))

(define
  er-eval-orelse
  (fn
    (args env)
    (let
      ((a (er-eval-expr (nth args 0) env)))
      (if (er-truthy? a) a (er-eval-expr (nth args 1) env)))))

(define
  er-apply-binop
  (fn
    (op a b)
    (cond
      (= op "+") (+ a b)
      (= op "-") (- a b)
      (= op "*") (* a b)
      (= op "/") (/ a b)
      (= op "div") (truncate (/ a b))
      (= op "rem") (remainder a b)
      (= op "==") (er-bool (er-equal? a b))
      (= op "/=") (er-bool (not (er-equal? a b)))
      (= op "=:=") (er-bool (er-exact-equal? a b))
      (= op "=/=") (er-bool (not (er-exact-equal? a b)))
      (= op "<") (er-bool (er-lt? a b))
      (= op ">") (er-bool (er-lt? b a))
      (= op "=<") (er-bool (not (er-lt? b a)))
      (= op ">=") (er-bool (not (er-lt? a b)))
      (= op "++") (er-list-append a b)
      (= op "and") (er-bool (and (er-truthy? a) (er-truthy? b)))
      (= op "or") (er-bool (or (er-truthy? a) (er-truthy? b)))
      :else (error (str "Erlang eval: unsupported operator '" op "'")))))

(define
  er-eval-unop
  (fn
    (node env)
    (let
      ((op (get node :op)) (a (er-eval-expr (get node :arg) env)))
      (cond
        (= op "-") (- 0 a)
        (= op "+") a
        (= op "not") (er-bool (not (er-truthy? a)))
        :else (error (str "Erlang eval: unsupported unary '" op "'"))))))

;; ── equality / comparison ─────────────────────────────────────────
(define
  er-equal?
  (fn
    (a b)
    (cond
      (and (= (type-of a) "number") (= (type-of b) "number")) (= a b)
      (and (er-atom? a) (er-atom? b)) (= (get a :name) (get b :name))
      (and (er-nil? a) (er-nil? b)) true
      (and (er-cons? a) (er-cons? b))
      (and
        (er-equal? (get a :head) (get b :head))
        (er-equal? (get a :tail) (get b :tail)))
      (and (er-tuple? a) (er-tuple? b))
      (let
        ((ea (get a :elements)) (eb (get b :elements)))
        (and
          (= (len ea) (len eb))
          (every?
            (fn (i) (er-equal? (nth ea i) (nth eb i)))
            (range 0 (len ea)))))
      (and (= (type-of a) "string") (= (type-of b) "string")) (= a b)
      (and (er-pid? a) (er-pid? b)) (= (get a :id) (get b :id))
      (and (er-ref? a) (er-ref? b)) (= (get a :id) (get b :id))
      (and (er-binary? a) (er-binary? b))
      (let
        ((ba (get a :bytes)) (bb (get b :bytes)))
        (and
          (= (len ba) (len bb))
          (every? (fn (i) (= (nth ba i) (nth bb i))) (range 0 (len ba)))))
      :else false)))

;; Exact equality: 1 =/= 1.0 in Erlang.
(define
  er-exact-equal?
  (fn
    (a b)
    (if
      (and (= (type-of a) "number") (= (type-of b) "number"))
      (and (= (integer? a) (integer? b)) (= a b))
      (er-equal? a b))))

(define
  er-lt?
  (fn
    (a b)
    (cond
      (and (= (type-of a) "number") (= (type-of b) "number")) (< a b)
      (and (er-atom? a) (er-atom? b)) (< (get a :name) (get b :name))
      (and (= (type-of a) "string") (= (type-of b) "string")) (< a b)
      :else (< (er-type-order a) (er-type-order b)))))

(define
  er-type-order
  (fn
    (v)
    (cond
      (= (type-of v) "number") 0
      (er-atom? v) 1
      (er-tuple? v) 2
      (er-nil? v) 3
      (er-cons? v) 3
      (= (type-of v) "string") 4
      (er-pid? v) 5
      :else 6)))

(define
  er-list-append
  (fn
    (a b)
    (cond
      (er-nil? a) b
      (er-cons? a)
      (er-mk-cons (get a :head) (er-list-append (get a :tail) b))
      :else (error "Erlang: ++ left argument is not a proper list"))))

;; ── if ────────────────────────────────────────────────────────────
(define er-eval-if (fn (node env) (er-eval-if-clauses (get node :clauses) 0 env)))

(define
  er-eval-if-clauses
  (fn
    (clauses i env)
    (if
      (>= i (len clauses))
      (error "Erlang: if: no clause matched")
      (let
        ((c (nth clauses i)))
        (if
          (er-eval-guards (get c :guards) env)
          (er-eval-body (get c :body) env)
          (er-eval-if-clauses clauses (+ i 1) env))))))

;; Guards: outer list = OR, inner list = AND. Empty outer = always pass.
(define
  er-eval-guards
  (fn
    (alts env)
    (if (= (len alts) 0) true (er-eval-guards-any alts 0 env))))

(define
  er-eval-guards-any
  (fn
    (alts i env)
    (if
      (>= i (len alts))
      false
      (if
        (er-eval-guard-conj (nth alts i) env)
        true
        (er-eval-guards-any alts (+ i 1) env)))))

(define er-eval-guard-conj (fn (conj env) (er-eval-guard-conj-iter conj 0 env)))

(define
  er-eval-guard-conj-iter
  (fn
    (conj i env)
    (if
      (>= i (len conj))
      true
      (if
        (er-truthy? (er-eval-expr (nth conj i) env))
        (er-eval-guard-conj-iter conj (+ i 1) env)
        false))))

;; ── function calls ───────────────────────────────────────────────
(define
  er-eval-call
  (fn
    (node env)
    (let
      ((fun-node (get node :fun)) (args (get node :args)))
      (cond
        (= (get fun-node :type) "atom")
        (let
          ((name (get fun-node :value)) (vs (er-eval-args args env)))
          (cond
            (and (dict-has? env name) (er-fun? (get env name)))
            (er-apply-fun (get env name) vs)
            :else (er-apply-bif name vs)))
        (= (get fun-node :type) "remote")
        (let
          ((mod-name (er-resolve-call-name (get fun-node :mod) env "module"))
            (fn-name (er-resolve-call-name (get fun-node :fun) env "function")))
          (er-apply-remote-bif mod-name fn-name (er-eval-args args env)))
        :else
        (let
          ((fv (er-eval-expr fun-node env)))
          (if
            (er-fun? fv)
            (er-apply-fun fv (er-eval-args args env))
            (error "Erlang: not a function")))))))

(define
  er-eval-args
  (fn
    (args env)
    (let
      ((out (list)))
      (for-each
        (fn (i) (append! out (er-eval-expr (nth args i) env)))
        (range 0 (len args)))
      out)))

;; Resolve a remote call's module/function reference into a string.
;; Atom AST nodes use their `:value` directly. For any other shape
;; (typically a var or another expression), evaluate it and require
;; the result to be an atom.
(define
  er-resolve-call-name
  (fn
    (node env kind)
    (cond
      (= (get node :type) "atom") (get node :value)
      :else (let
        ((v (er-eval-expr node env)))
        (if
          (er-atom? v)
          (get v :name)
          (error
            (str "Erlang: call " kind " must be an atom, got " (er-format-value v))))))))

;; ── fun values ───────────────────────────────────────────────────
(define
  er-mk-fun
  (fn (clauses env) {:env env :clauses clauses :tag "fun"}))
(define er-fun? (fn (v) (er-is-tagged? v "fun")))

(define
  er-eval-fun
  (fn (node env) (er-mk-fun (get node :clauses) env)))

(define
  er-apply-fun
  (fn
    (fv vs)
    (er-apply-fun-clauses (get fv :clauses) vs (get fv :env) 0)))

(define
  er-apply-fun-clauses
  (fn
    (clauses vs closure-env i)
    (if
      (>= i (len clauses))
      (error "Erlang: function_clause: no matching fun clause")
      (let
        ((c (nth clauses i))
          (ps (get c :patterns))
          (call-env (er-env-copy closure-env)))
        (if
          (not (= (len ps) (len vs)))
          (er-apply-fun-clauses clauses vs closure-env (+ i 1))
          (if
            (and
              (er-match-all ps vs 0 call-env)
              (er-eval-guards (get c :guards) call-env))
            (er-eval-body (get c :body) call-env)
            (er-apply-fun-clauses clauses vs closure-env (+ i 1))))))))

;; ── BIFs ─────────────────────────────────────────────────────────
(define er-atom-ok (er-mk-atom "ok"))

(define
  er-apply-bif
  (fn (name vs)
    (let ((entry (er-lookup-bif "erlang" name (len vs))))
      (if (not (= entry nil))
        ((get entry :fn) vs)
        (error (str "Erlang: undefined function '" name "/" (len vs) "'"))))))

(define
  er-apply-remote-bif
  (fn (mod name vs)
    (cond
      (dict-has? (er-modules-get) mod)
        (er-apply-user-module mod name vs)
      :else
        (let ((entry (er-lookup-bif mod name (len vs))))
          (if (not (= entry nil))
            ((get entry :fn) vs)
            (error (str "Erlang: undefined remote function '" mod ":" name "/" (len vs) "'")))))))

(define
  er-bif-arg1
  (fn
    (vs name)
    (if
      (= (len vs) 1)
      (nth vs 0)
      (error (str "Erlang: " name ": wrong arity")))))

(define
  er-bif-is-integer
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "is_integer")))
      (er-bool (and (= (type-of v) "number") (integer? v))))))

(define
  er-bif-is-atom
  (fn (vs) (er-bool (er-atom? (er-bif-arg1 vs "is_atom")))))

(define
  er-bif-is-list
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "is_list")))
      (er-bool (or (er-nil? v) (er-cons? v))))))

(define
  er-bif-is-tuple
  (fn (vs) (er-bool (er-tuple? (er-bif-arg1 vs "is_tuple")))))

(define
  er-bif-is-number
  (fn
    (vs)
    (er-bool (= (type-of (er-bif-arg1 vs "is_number")) "number"))))

(define
  er-bif-is-float
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "is_float")))
      (er-bool (and (= (type-of v) "number") (not (integer? v)))))))

(define
  er-bif-is-boolean
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "is_boolean")))
      (er-bool
        (or (er-is-atom-named? v "true") (er-is-atom-named? v "false"))))))

(define
  er-bif-is-binary
  (fn (vs) (er-bool (er-binary? (er-bif-arg1 vs "is_binary")))))

(define
  er-bif-byte-size
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "byte_size")))
      (cond
        (er-binary? v) (len (get v :bytes))
        :else (raise (er-mk-error-marker (er-mk-atom "badarg")))))))

;; ── list / tuple BIFs ────────────────────────────────────────────
(define er-bif-length (fn (vs) (er-list-length (er-bif-arg1 vs "length"))))

(define
  er-list-length
  (fn
    (v)
    (cond
      (er-nil? v) 0
      (er-cons? v) (+ 1 (er-list-length (get v :tail)))
      :else (error "Erlang: length: not a proper list"))))

(define
  er-bif-hd
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "hd")))
      (if
        (er-cons? v)
        (get v :head)
        (error "Erlang: hd: empty list or non-list")))))

(define
  er-bif-tl
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "tl")))
      (if
        (er-cons? v)
        (get v :tail)
        (error "Erlang: tl: empty list or non-list")))))

(define
  er-bif-element
  (fn
    (vs)
    (if
      (not (= (len vs) 2))
      (error "Erlang: element: arity")
      (let
        ((i (nth vs 0)) (t (nth vs 1)))
        (if
          (and (= (type-of i) "number") (er-tuple? t))
          (let
            ((elems (get t :elements)))
            (if
              (and (>= i 1) (<= i (len elems)))
              (nth elems (- i 1))
              (error "Erlang: element: badarg (index out of range)")))
          (error "Erlang: element: badarg"))))))

(define
  er-bif-tuple-size
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "tuple_size")))
      (if
        (er-tuple? v)
        (len (get v :elements))
        (error "Erlang: tuple_size: not a tuple")))))

(define er-string->charlist
  (fn (s)
    (let ((cs (string->list s)) (out (er-mk-nil)))
      (for-each
        (fn (i)
          (set! out (er-mk-cons
                      (char->integer (nth cs (- (- (len cs) 1) i)))
                      out)))
        (range 0 (len cs)))
      out)))

(define
  er-bif-atom-to-list
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "atom_to_list")))
      ;; Standard Erlang: atom_to_list/1 returns an Erlang charlist
      ;; (list of integer char codes). Was: SX string of :name —
      ;; unusable from Erlang-land for [Char|T] / ++ / binary segments.
      (if
        (er-atom? v)
        (er-string->charlist (get v :name))
        (raise (er-mk-error-marker (er-mk-atom "badarg")))))))

(define
  er-bif-list-to-atom
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "list_to_atom")))
      ;; Accept Erlang charlist (cons of ints) or SX string.
      (let ((s (er-source-to-string v)))
        (cond
          (= s nil) (raise (er-mk-error-marker (er-mk-atom "badarg")))
          :else (er-mk-atom s))))))

;; ── lists module ─────────────────────────────────────────────────
(define
  er-bif-lists-reverse
  (fn
    (vs)
    (er-list-reverse-iter (er-bif-arg1 vs "lists:reverse") (er-mk-nil))))

(define
  er-list-reverse-iter
  (fn
    (v acc)
    (cond
      (er-nil? v) acc
      (er-cons? v)
      (er-list-reverse-iter (get v :tail) (er-mk-cons (get v :head) acc))
      :else (error "Erlang: lists:reverse: not a list"))))

(define
  er-bif-lists-map
  (fn
    (vs)
    (if
      (not (= (len vs) 2))
      (error "Erlang: lists:map: arity")
      (er-list-reverse-iter
        (er-map-iter (nth vs 0) (nth vs 1) (er-mk-nil))
        (er-mk-nil)))))

(define
  er-map-iter
  (fn
    (f lst acc)
    (cond
      (er-nil? lst) acc
      (er-cons? lst)
      (er-map-iter
        f
        (get lst :tail)
        (er-mk-cons (er-apply-fun f (list (get lst :head))) acc))
      :else (error "Erlang: lists:map: not a list"))))

(define
  er-bif-lists-foldl
  (fn
    (vs)
    (if
      (not (= (len vs) 3))
      (error "Erlang: lists:foldl: arity")
      (er-foldl-iter (nth vs 0) (nth vs 1) (nth vs 2)))))

(define
  er-foldl-iter
  (fn
    (f acc lst)
    (cond
      (er-nil? lst) acc
      (er-cons? lst)
      (er-foldl-iter
        f
        (er-apply-fun f (list (get lst :head) acc))
        (get lst :tail))
      :else (error "Erlang: lists:foldl: not a list"))))

;; ── io module ────────────────────────────────────────────────────
(define er-io-buffer (list ""))
(define er-io-flush! (fn () (set-nth! er-io-buffer 0 "")))
(define er-io-buffer-content (fn () (nth er-io-buffer 0)))

(define
  er-bif-io-format
  (fn
    (vs)
    (let
      ((s
        (cond
          (= (len vs) 1) (er-format-string (nth vs 0) (list))
          (= (len vs) 2)
          (er-format-string (nth vs 0) (er-list-to-sx-list (nth vs 1)))
          :else (error "Erlang: io:format: arity"))))
      (set-nth! er-io-buffer 0 (str (nth er-io-buffer 0) s))
      er-atom-ok)))

(define
  er-list-to-sx-list
  (fn
    (lst)
    (let
      ((out (list)))
      (er-list-to-sx-collect lst out)
      out)))

(define
  er-list-to-sx-collect
  (fn
    (lst out)
    (cond
      (er-nil? lst) nil
      (er-cons? lst)
      (do
        (append! out (get lst :head))
        (er-list-to-sx-collect (get lst :tail) out))
      :else (error "Erlang: expected proper list"))))

;; ── format string rendering (~n, ~~, ~p, ~w, ~s) ────────────────
(define
  er-format-string
  (fn (fmt args) (er-format-walk fmt 0 args 0 "")))

(define
  er-format-walk
  (fn
    (fmt i args ai out)
    (if
      (>= i (len fmt))
      out
      (let
        ((c (char-at fmt i)))
        (cond
          (and (= c "~") (< (+ i 1) (len fmt)))
          (let
            ((d (char-at fmt (+ i 1))))
            (cond
              (= d "n")
              (er-format-walk fmt (+ i 2) args ai (str out "\n"))
              (= d "~") (er-format-walk fmt (+ i 2) args ai (str out "~"))
              (or (= d "p") (= d "w") (= d "s"))
              (er-format-walk
                fmt
                (+ i 2)
                args
                (+ ai 1)
                (str out (er-format-value (nth args ai))))
              :else (er-format-walk
                fmt
                (+ i 2)
                args
                ai
                (str out "~" d))))
          :else (er-format-walk fmt (+ i 1) args ai (str out c)))))))

(define
  er-format-value
  (fn
    (v)
    (cond
      (= (type-of v) "number") (str v)
      (= (type-of v) "string") (str "\"" v "\"")
      (er-atom? v) (get v :name)
      (er-nil? v) "[]"
      (er-cons? v) (str "[" (er-format-list-elems v) "]")
      (er-tuple? v) (str "{" (er-format-tuple-elems (get v :elements)) "}")
      (er-fun? v) "#Fun"
      (er-pid? v) (str "<pid:" (get v :id) ">")
      (er-ref? v) (str "#Ref<" (get v :id) ">")
      (er-binary? v) (str "<<" (er-format-bytes (get v :bytes)) ">>")
      :else (str v))))

(define
  er-format-bytes
  (fn
    (bs)
    (cond
      (= (len bs) 0) ""
      :else (let
        ((out (list (str (nth bs 0)))))
        (for-each
          (fn (i) (append! out ",") (append! out (str (nth bs i))))
          (range 1 (len bs)))
        (reduce str "" out)))))

(define
  er-format-list-elems
  (fn
    (v)
    (cond
      (er-nil? v) ""
      (and (er-cons? v) (er-nil? (get v :tail)))
      (er-format-value (get v :head))
      (er-cons? v)
      (str
        (er-format-value (get v :head))
        ","
        (er-format-list-elems (get v :tail)))
      :else (str "|" (er-format-value v)))))

(define
  er-format-tuple-elems
  (fn
    (elems)
    (if
      (= (len elems) 0)
      ""
      (let
        ((out (list (er-format-value (nth elems 0)))))
        (for-each
          (fn
            (i)
            (append! out ",")
            (append! out (er-format-value (nth elems i))))
          (range 1 (len elems)))
        (reduce str "" out)))))

;; ── send: Pid ! Msg ──────────────────────────────────────────────
;; Target may be a pid or a registered atom name. Atom resolution
;; goes through the scheduler's `:registered` table.
(define
  er-eval-send
  (fn
    (node env)
    (let
      ((to-val (er-eval-expr (get node :to) env))
        (msg-val (er-eval-expr (get node :msg) env)))
      (let
        ((pid (er-resolve-send-target to-val)))
        (when
          (er-proc-exists? pid)
          (er-proc-mailbox-push! pid msg-val)
          (when
            (= (er-proc-field pid :state) "waiting")
            (er-proc-set! pid :state "runnable")
            (er-sched-enqueue! pid)))
        msg-val))))

(define
  er-resolve-send-target
  (fn
    (v)
    (cond
      (er-pid? v) v
      (er-atom? v)
      (let
        ((name (get v :name)))
        (if
          (dict-has? (er-registered) name)
          (get (er-registered) name)
          (raise
            (er-mk-error-marker
              (er-mk-tuple
                (list (er-mk-atom "badarg") v))))))
      :else (raise
        (er-mk-error-marker
          (er-mk-tuple (list (er-mk-atom "badarg") v)))))))

;; ── receive (selective, delimited-continuation suspension) ──────
(define
  er-eval-receive
  (fn
    (node env)
    (let
      ((pid (er-sched-current-pid))
        (after-node (get node :after-ms)))
      (if
        (= after-node nil)
        (er-eval-receive-loop node pid env)
        (er-eval-receive-with-after node pid env after-node)))))

(define
  er-eval-receive-loop
  (fn
    (node pid env)
    (let
      ((r (er-try-receive (get node :clauses) pid env)))
      (if
        (get r :matched)
        (get r :value)
        (do
          (call/cc
            (fn
              (k)
              (er-proc-set! pid :continuation k)
              (er-proc-set! pid :state "waiting")
              (raise er-suspend-marker)))
          (er-eval-receive-loop node pid env))))))

(define
  er-eval-receive-with-after
  (fn
    (node pid env after-node)
    (let
      ((ms (er-eval-expr after-node env)))
      (cond
        (and (er-atom? ms) (= (get ms :name) "infinity"))
        (er-eval-receive-loop node pid env)
        (= ms 0) (er-eval-receive-poll node pid env)
        :else (er-eval-receive-timed node pid env)))))

;; after 0 — poll once; on no match, run the after-body immediately.
(define
  er-eval-receive-poll
  (fn
    (node pid env)
    (let
      ((r (er-try-receive (get node :clauses) pid env)))
      (if
        (get r :matched)
        (get r :value)
        (er-eval-body (get node :after-body) env)))))

;; after Ms — suspend; on resume check :timed-out. When the scheduler
;; runs out of other work it fires one pending timeout per round.
(define
  er-eval-receive-timed
  (fn
    (node pid env)
    (let
      ((r (er-try-receive (get node :clauses) pid env)))
      (if
        (get r :matched)
        (get r :value)
        (do
          (er-proc-set! pid :has-timeout true)
          (call/cc
            (fn
              (k)
              (er-proc-set! pid :continuation k)
              (er-proc-set! pid :state "waiting")
              (raise er-suspend-marker)))
          (if
            (er-proc-field pid :timed-out)
            (do
              (er-proc-set! pid :timed-out false)
              (er-proc-set! pid :has-timeout false)
              (er-eval-body (get node :after-body) env))
            (er-eval-receive-timed node pid env)))))))

;; Scan mailbox in arrival order. For each msg, try every clause.
;; On first match: remove that msg from mailbox and return body value.
(define
  er-try-receive
  (fn
    (clauses pid env)
    (let
      ((mbox (er-proc-field pid :mailbox)))
      (er-try-receive-loop clauses mbox env 0))))

(define
  er-try-receive-loop
  (fn
    (clauses mbox env i)
    (if
      (>= i (er-q-len mbox))
      {:matched false}
      (let
        ((msg (er-q-nth mbox i))
          (cr (er-try-receive-clauses clauses msg env 0)))
        (if
          (get cr :matched)
          (do
            (er-q-delete-at! mbox i)
            {:value (er-eval-body (get cr :body) env) :matched true})
          (er-try-receive-loop clauses mbox env (+ i 1)))))))

;; Try clauses against a message. On match: bind vars into env and
;; return `{:matched true :body <clause body>}` WITHOUT evaluating the
;; body — the caller must remove the message from the mailbox first,
;; otherwise a recursive `receive` inside the body would re-match the
;; same msg and loop forever.
(define
  er-try-receive-clauses
  (fn
    (clauses msg env i)
    (if
      (>= i (len clauses))
      {:matched false}
      (let
        ((c (nth clauses i)) (snap (er-env-copy env)))
        (if
          (and
            (er-match! (get c :pattern) msg env)
            (er-eval-guards (get c :guards) env))
          {:body (get c :body) :matched true}
          (do
            (er-env-restore! env snap)
            (er-try-receive-clauses clauses msg env (+ i 1))))))))

;; ── try/of/catch/after ────────────────────────────────────────────
;; The outer guard captures any exception so the `after` body is
;; guaranteed to run, then re-raises. The inner guard runs the
;; expression body, optional `of` clauses on success, and `catch`
;; clauses on a thrown/erred/exited condition. If no catch clause
;; matches the raised class+pattern, the inner guard's clause
;; re-raises by returning nothing (handled via re-raise marker).
(define
  er-eval-try
  (fn
    (node env)
    (let
      ((after-body (get node :after))
        (saved-exc (list nil))
        (result-ref (list nil)))
      (guard
        (c (:else (do (set-nth! saved-exc 0 c) nil)))
        (set-nth! result-ref 0 (er-eval-try-inner node env)))
      (when
        (> (len after-body) 0)
        (er-eval-body after-body env))
      (if
        (= (nth saved-exc 0) nil)
        (nth result-ref 0)
        (raise (nth saved-exc 0))))))

(define
  er-eval-try-inner
  (fn
    (node env)
    (let
      ((catch-clauses (get node :catch-clauses))
        (of-clauses (get node :of-clauses))
        (caught-ref (list false))
        (result-ref (list nil))
        (re-raise-ref (list nil)))
      (guard
        (c
          ((er-thrown? c)
            (er-eval-try-catch
              catch-clauses "throw" (get c :reason) env
              caught-ref result-ref re-raise-ref))
          ((er-errored? c)
            (er-eval-try-catch
              catch-clauses "error" (get c :reason) env
              caught-ref result-ref re-raise-ref))
          ((er-exited? c)
            (er-eval-try-catch
              catch-clauses "exit" (get c :reason) env
              caught-ref result-ref re-raise-ref)))
        (let
          ((r (er-eval-body (get node :exprs) env)))
          (if
            (= (len of-clauses) 0)
            (set-nth! result-ref 0 r)
            (set-nth!
              result-ref
              0
              (er-eval-of-clauses of-clauses r env 0)))))
      (when (not (= (nth re-raise-ref 0) nil))
        (raise (nth re-raise-ref 0)))
      (nth result-ref 0))))

;; Try catch-clauses against (Class, Reason). If a clause matches,
;; runs its body and writes to result-ref. If none match, queues a
;; re-raise marker.
(define
  er-eval-try-catch
  (fn
    (clauses class-name reason env caught-ref result-ref re-raise-ref)
    (er-eval-try-catch-iter
      clauses class-name reason env 0 caught-ref result-ref re-raise-ref)))

(define
  er-eval-try-catch-iter
  (fn
    (clauses class-name reason env i caught-ref result-ref re-raise-ref)
    (if
      (>= i (len clauses))
      (set-nth!
        re-raise-ref
        0
        (er-mk-class-marker class-name reason))
      (let
        ((c (nth clauses i))
          (snap (er-env-copy env))
          (clause-class (get (get c :class) :value)))
        (cond
          (not (= clause-class class-name))
          (er-eval-try-catch-iter
            clauses class-name reason env (+ i 1)
            caught-ref result-ref re-raise-ref)
          :else
          (if
            (and
              (er-match! (get c :pattern) reason env)
              (er-eval-guards (get c :guards) env))
            (do
              (set-nth! caught-ref 0 true)
              (set-nth!
                result-ref
                0
                (er-eval-body (get c :body) env)))
            (do
              (er-env-restore! env snap)
              (er-eval-try-catch-iter
                clauses class-name reason env (+ i 1)
                caught-ref result-ref re-raise-ref))))))))

(define
  er-mk-class-marker
  (fn
    (class-name reason)
    (cond
      (= class-name "throw") (er-mk-throw-marker reason)
      (= class-name "error") (er-mk-error-marker reason)
      (= class-name "exit") (er-mk-exit-marker reason)
      :else (er-mk-error-marker reason))))

(define
  er-eval-of-clauses
  (fn
    (clauses subject env i)
    (if
      (>= i (len clauses))
      (raise
        (er-mk-error-marker
          (er-mk-tuple
            (list (er-mk-atom "try_clause") subject))))
      (let
        ((c (nth clauses i)) (snap (er-env-copy env)))
        (if
          (and
            (er-match! (get c :pattern) subject env)
            (er-eval-guards (get c :guards) env))
          (er-eval-body (get c :body) env)
          (do
            (er-env-restore! env snap)
            (er-eval-of-clauses clauses subject env (+ i 1))))))))

;; ── list comprehensions ─────────────────────────────────────────
;; `[E || Pat <- Source, FilterExpr, ...]`. Walk qualifiers in order:
;; generators iterate their source list and bind the pattern (with
;; env snapshot/restore so each iteration starts from the same
;; baseline); filters skip when falsy. At the end of the qualifier
;; chain, evaluate `head` and append to the accumulator. Build the
;; final cons chain in O(n) with a single right-fold.
(define
  er-eval-lc
  (fn
    (node env)
    (let
      ((acc (list)))
      (er-lc-walk (get node :qualifiers) 0 (get node :head) env acc)
      (er-list-from-sx-list acc))))

(define
  er-lc-walk
  (fn
    (quals i head env acc)
    (if
      (>= i (len quals))
      (append! acc (er-eval-expr head env))
      (let
        ((q (nth quals i)))
        (cond
          (= (get q :kind) "gen")
          (let
            ((src (er-eval-expr (get q :source) env)))
            (er-lc-iter-gen
              src
              (get q :pattern)
              quals
              i
              head
              env
              acc))
          (= (get q :kind) "filter")
          (when
            (er-truthy? (er-eval-expr (get q :expr) env))
            (er-lc-walk quals (+ i 1) head env acc))
          :else (error "Erlang LC: unknown qualifier"))))))

(define
  er-lc-iter-gen
  (fn
    (src pat quals i head env acc)
    (cond
      (er-nil? src) nil
      (er-cons? src)
      (let
        ((snap (er-env-copy env)))
        (when
          (er-match! pat (get src :head) env)
          (er-lc-walk quals (+ i 1) head env acc))
        (er-env-restore! env snap)
        (er-lc-iter-gen
          (get src :tail)
          pat
          quals
          i
          head
          env
          acc))
      :else (error "Erlang LC: generator source is not a list"))))

(define
  er-list-from-sx-list
  (fn
    (xs)
    (let
      ((acc (list (er-mk-nil))))
      (for-each
        (fn
          (i)
          (let
            ((j (- (- (len xs) 1) i)))
            (set-nth! acc 0 (er-mk-cons (nth xs j) (nth acc 0)))))
        (range 0 (len xs)))
      (nth acc 0))))

;; ── binaries ────────────────────────────────────────────────────
;; Each segment is `Value : Size / Spec`. Supported specs: `integer`
;; (default; size in bits, must be multiple of 8 — 8/16/24/32 typical)
;; and `binary` (concatenate the segment's binary value into the
;; result). Default size for `integer` segments is 8 bits.
(define
  er-eval-binary
  (fn
    (node env)
    (let
      ((segs (get node :segments)) (out (list)))
      (for-each
        (fn (i) (er-eval-binary-segment (nth segs i) env out))
        (range 0 (len segs)))
      (er-mk-binary out))))

(define
  er-eval-binary-segment
  (fn
    (seg env out)
    (let
      ((spec (get seg :spec))
        (val (er-eval-expr (get seg :value) env))
        (size (er-eval-binary-size (get seg :size) env)))
      (cond
        (= spec "integer")
        (let
          ((bits (if (= size nil) 8 size)))
          (er-emit-int! out val bits))
        (= spec "binary")
        (cond
          (er-binary? val)
          (for-each
            (fn (i) (append! out (nth (get val :bytes) i)))
            (range 0 (len (get val :bytes))))
          :else (raise
            (er-mk-error-marker (er-mk-atom "badarg"))))
        :else (error
          (str "Erlang: binary spec '" spec "' not supported"))))))

(define
  er-eval-binary-size
  (fn
    (node env)
    (cond
      (= node nil) nil
      :else (er-eval-expr node env))))

;; Big-endian byte emission for an N-bit integer (N must be multiple
;; of 8). For bits=8 this is just `(append! out (mod v 256))`.
(define
  er-emit-int!
  (fn
    (out v bits)
    (cond
      (or (not (= (remainder bits 8) 0)) (<= bits 0))
      (error
        (str "Erlang: binary integer size must be a positive multiple of 8 (got " bits ")"))
      :else (let
        ((nbytes (truncate (/ bits 8))))
        (for-each
          (fn
            (i)
            (let
              ((shift (* 8 (- (- nbytes 1) i))))
              (append!
                out
                (remainder (truncate (/ v (er-int-pow 2 shift))) 256))))
          (range 0 nbytes))))))

(define
  er-int-pow
  (fn
    (b e)
    (cond
      (= e 0) 1
      :else (* b (er-int-pow b (- e 1))))))

;; ── extra erlang BIFs ───────────────────────────────────────────
(define
  er-bif-abs
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "abs")))
      (cond
        (not (= (type-of v) "number"))
        (raise (er-mk-error-marker (er-mk-atom "badarg")))
        (< v 0) (- 0 v)
        :else v))))

(define
  er-bif-min
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: min/2: arity")
      :else (let
        ((a (nth vs 0)) (b (nth vs 1)))
        (if (er-lt? b a) b a)))))

(define
  er-bif-max
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: max/2: arity")
      :else (let
        ((a (nth vs 0)) (b (nth vs 1)))
        (if (er-lt? a b) b a)))))

(define
  er-bif-tuple-to-list
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "tuple_to_list")))
      (cond
        (not (er-tuple? v))
        (raise (er-mk-error-marker (er-mk-atom "badarg")))
        :else (let
          ((elems (get v :elements)) (out (er-mk-nil)))
          (for-each
            (fn
              (i)
              (let
                ((j (- (- (len elems) 1) i)))
                (set! out (er-mk-cons (nth elems j) out))))
            (range 0 (len elems)))
          out)))))

(define
  er-bif-list-to-tuple
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "list_to_tuple")) (elems (list)))
      (er-list-to-elem-list v elems)
      (er-mk-tuple elems))))

(define
  er-list-to-elem-list
  (fn
    (lst out)
    (cond
      (er-nil? lst) nil
      (er-cons? lst)
      (do
        (append! out (get lst :head))
        (er-list-to-elem-list (get lst :tail) out))
      :else (raise (er-mk-error-marker (er-mk-atom "badarg"))))))

(define
  er-bif-integer-to-list
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "integer_to_list")))
      ;; Standard Erlang: integer_to_list/1 returns an Erlang charlist
      ;; (e.g. integer_to_list(42) -> [$4, $2] -> [52, 50]).
      (cond
        (not (= (type-of v) "number"))
        (raise (er-mk-error-marker (er-mk-atom "badarg")))
        :else (er-string->charlist (str v))))))

(define
  er-bif-list-to-integer
  (fn
    (vs)
    (let
      ((v (er-bif-arg1 vs "list_to_integer")))
      ;; Accept Erlang charlist (cons of ints) or SX string.
      (let ((s (er-source-to-string v)))
        (cond
          (= s nil) (raise (er-mk-error-marker (er-mk-atom "badarg")))
          :else (let ((n (parse-number s)))
            (cond
              (= n nil) (raise (er-mk-error-marker (er-mk-atom "badarg")))
              :else n)))))))

(define
  er-bif-is-function
  (fn
    (vs)
    (cond
      (= (len vs) 1) (er-bool (er-fun? (nth vs 0)))
      (= (len vs) 2)
      (let
        ((v (nth vs 0)) (n (nth vs 1)))
        (cond
          (not (er-fun? v)) (er-bool false)
          :else (er-bool (er-fun-has-arity? v n))))
      :else (error "Erlang: is_function: arity"))))

(define
  er-fun-has-arity?
  (fn
    (fv n)
    (let
      ((clauses (get fv :clauses)) (found (list false)))
      (for-each
        (fn
          (i)
          (when
            (= (len (get (nth clauses i) :patterns)) n)
            (set-nth! found 0 true)))
        (range 0 (len clauses)))
      (nth found 0))))

;; ── extra lists BIFs ───────────────────────────────────────────
(define
  er-bif-lists-seq
  (fn
    (vs)
    (cond
      (= (len vs) 2) (er-lists-seq-build (nth vs 0) (nth vs 1) 1)
      (= (len vs) 3) (er-lists-seq-build (nth vs 0) (nth vs 1) (nth vs 2))
      :else (error "Erlang: lists:seq: arity"))))

(define
  er-lists-seq-build
  (fn
    (from to step)
    (let
      ((acc (er-mk-nil)))
      (for-each
        (fn
          (i)
          (let
            ((v (- to (* i step))))
            (when
              (and (>= v from) (<= v to))
              (set! acc (er-mk-cons v acc)))))
        (range 0 (+ 1 (truncate (/ (- to from) step)))))
      acc)))

(define
  er-bif-lists-sum
  (fn
    (vs)
    (let
      ((lst (er-bif-arg1 vs "lists:sum")))
      (er-lists-sum-iter lst 0))))

(define
  er-lists-sum-iter
  (fn
    (lst acc)
    (cond
      (er-nil? lst) acc
      (er-cons? lst)
      (er-lists-sum-iter (get lst :tail) (+ acc (get lst :head)))
      :else (raise (er-mk-error-marker (er-mk-atom "badarg"))))))

(define
  er-bif-lists-nth
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:nth: arity")
      :else (er-lists-nth-iter (nth vs 1) (nth vs 0)))))

(define
  er-lists-nth-iter
  (fn
    (lst i)
    (cond
      (or (<= i 0) (er-nil? lst))
      (raise (er-mk-error-marker (er-mk-atom "badarg")))
      (= i 1) (get lst :head)
      :else (er-lists-nth-iter (get lst :tail) (- i 1)))))

(define
  er-bif-lists-last
  (fn
    (vs)
    (let
      ((lst (er-bif-arg1 vs "lists:last")))
      (cond
        (er-nil? lst)
        (raise (er-mk-error-marker (er-mk-atom "badarg")))
        :else (er-lists-last-iter lst)))))

(define
  er-lists-last-iter
  (fn
    (lst)
    (cond
      (and (er-cons? lst) (er-nil? (get lst :tail))) (get lst :head)
      (er-cons? lst) (er-lists-last-iter (get lst :tail))
      :else (raise (er-mk-error-marker (er-mk-atom "badarg"))))))

(define
  er-bif-lists-member
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:member: arity")
      :else (er-bool (er-lists-member-iter (nth vs 0) (nth vs 1))))))

(define
  er-lists-member-iter
  (fn
    (target lst)
    (cond
      (er-nil? lst) false
      (er-cons? lst)
      (cond
        (er-equal? target (get lst :head)) true
        :else (er-lists-member-iter target (get lst :tail)))
      :else false)))

(define
  er-bif-lists-append
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:append: arity")
      :else (er-list-append (nth vs 0) (nth vs 1)))))

(define
  er-bif-lists-filter
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:filter: arity")
      :else (er-lists-filter-build
        (nth vs 0)
        (nth vs 1)
        (er-mk-nil)))))

(define
  er-lists-filter-build
  (fn
    (pred lst acc)
    (cond
      (er-nil? lst) (er-list-reverse-iter acc (er-mk-nil))
      (er-cons? lst)
      (let
        ((kept
          (cond
            (er-truthy? (er-apply-fun pred (list (get lst :head))))
            (er-mk-cons (get lst :head) acc)
            :else acc)))
        (er-lists-filter-build pred (get lst :tail) kept))
      :else (raise (er-mk-error-marker (er-mk-atom "badarg"))))))

(define
  er-bif-lists-any
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:any: arity")
      :else (er-bool (er-lists-any-iter (nth vs 0) (nth vs 1))))))

(define
  er-lists-any-iter
  (fn
    (pred lst)
    (cond
      (er-nil? lst) false
      (er-cons? lst)
      (cond
        (er-truthy? (er-apply-fun pred (list (get lst :head)))) true
        :else (er-lists-any-iter pred (get lst :tail)))
      :else false)))

(define
  er-bif-lists-all
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:all: arity")
      :else (er-bool (er-lists-all-iter (nth vs 0) (nth vs 1))))))

(define
  er-lists-all-iter
  (fn
    (pred lst)
    (cond
      (er-nil? lst) true
      (er-cons? lst)
      (cond
        (er-truthy? (er-apply-fun pred (list (get lst :head))))
        (er-lists-all-iter pred (get lst :tail))
        :else false)
      :else false)))

(define
  er-bif-lists-duplicate
  (fn
    (vs)
    (cond
      (not (= (len vs) 2)) (error "Erlang: lists:duplicate: arity")
      :else (let
        ((n (nth vs 0)) (v (nth vs 1)) (out (er-mk-nil)))
        (for-each
          (fn (_) (set! out (er-mk-cons v out)))
          (range 0 n))
        out))))


;; ── code module (Phase 7 hot-reload) ─────────────────────────────
(define er-source-walk-bytes!
  (fn (n bytes-box)
    (cond
      (er-nil? n) true
      (er-cons? n)
        (let ((h (get n :head)))
          (cond
            (= (type-of h) "number")
              (do (append! (nth bytes-box 0) h)
                  (er-source-walk-bytes! (get n :tail) bytes-box))
            :else (do (set-nth! bytes-box 0 nil) false)))
      :else (do (set-nth! bytes-box 0 nil) false))))

(define er-source-to-string
  (fn (v)
    (cond
      (= (type-of v) "string") v
      (er-binary? v) (list->string (map integer->char (get v :bytes)))
      (or (er-nil? v) (er-cons? v))
        (let ((box (list (list))))
          (er-source-walk-bytes! v box)
          (cond
            (= (nth box 0) nil) nil
            :else (list->string (map integer->char (nth box 0)))))
      :else nil)))

(define er-bif-code-load-binary
  (fn (vs)
    (let ((mod-arg (nth vs 0)) (src-arg (nth vs 2)))
      (cond
        (not (er-atom? mod-arg))
          (er-mk-tuple (list (er-mk-atom "error") (er-mk-atom "badarg")))
        :else
          (let ((src-str (er-source-to-string src-arg)))
            (cond
              (= src-str nil)
                (er-mk-tuple (list (er-mk-atom "error") (er-mk-atom "badarg")))
              :else
                (let ((result-box (list nil)) (failed-box (list false)))
                  (guard
                    (c (:else (set-nth! failed-box 0 true)))
                    (set-nth! result-box 0 (erlang-load-module src-str)))
                  (cond
                    (nth failed-box 0)
                      (er-mk-tuple
                        (list (er-mk-atom "error") (er-mk-atom "badfile")))
                    (not (= (get (nth result-box 0) :name) (get mod-arg :name)))
                      (er-mk-tuple
                        (list (er-mk-atom "error") (er-mk-atom "module_name_mismatch")))
                    :else
                      (er-mk-tuple (list (er-mk-atom "module") mod-arg))))))))))

(define er-env-derived-from?
  (fn (env target-env)
    ;; Object-identity check, NOT value `=`. On evaluators where dict `=`
    ;; is structural/deep, comparing closure envs (which are large and
    ;; cyclic — a module fun's env references the fun) does not terminate.
    ;; `identical?` is pointer identity on every host and is the actual
    ;; intended semantics: "is this the same env object".
    (cond
      (identical? env target-env) true
      :else
        (let ((ks (keys env)) (found-ref (list false)))
          (for-each
            (fn (i)
              (when (not (nth found-ref 0))
                (let ((v (get env (nth ks i))))
                  (when (and (er-fun? v) (identical? (get v :env) target-env))
                    (set-nth! found-ref 0 true)))))
            (range 0 (len ks)))
          (nth found-ref 0)))))

(define er-procs-on-env
  (fn (target-env)
    (let ((all-keys (keys (er-sched-processes)))
          (matches (list)))
      (for-each
        (fn (i)
          (let ((proc (get (er-sched-processes) (nth all-keys i))))
            (let ((init-fun (get proc :initial-fun)))
              (when (and (not (= init-fun nil))
                         (er-fun? init-fun)
                         (er-env-derived-from? (get init-fun :env) target-env)
                         (not (= (get proc :state) "dead")))
                (append! matches (get proc :pid))))))
        (range 0 (len all-keys)))
      matches)))

(define er-bif-code-purge
  (fn (vs)
    (let ((mod-arg (nth vs 0)))
      (cond
        (not (er-atom? mod-arg))
          (raise (er-mk-error-marker (er-mk-atom "badarg")))
        :else
          (let ((registry (er-modules-get)) (mod-name (get mod-arg :name)))
            (cond
              (not (dict-has? registry mod-name)) (er-mk-atom "false")
              :else
                (let ((slot (get registry mod-name)))
                  (cond
                    (= (er-module-old-env slot) nil) (er-mk-atom "false")
                    :else
                      (let ((procs (er-procs-on-env (er-module-old-env slot))))
                        (for-each
                          (fn (i) (er-cascade-exit! (nth procs i) (er-mk-atom "killed")))
                          (range 0 (len procs)))
                        (dict-set! registry mod-name
                          (er-mk-module-slot (er-module-current-env slot) nil
                            (er-module-version slot)))
                        (er-mk-atom "true"))))))))))

(define er-bif-code-soft-purge
  (fn (vs)
    (let ((mod-arg (nth vs 0)))
      (cond
        (not (er-atom? mod-arg))
          (raise (er-mk-error-marker (er-mk-atom "badarg")))
        :else
          (let ((registry (er-modules-get)) (mod-name (get mod-arg :name)))
            (cond
              (not (dict-has? registry mod-name)) (er-mk-atom "true")
              :else
                (let ((slot (get registry mod-name)))
                  (cond
                    (= (er-module-old-env slot) nil) (er-mk-atom "true")
                    :else
                      (let ((procs (er-procs-on-env (er-module-old-env slot))))
                        (cond
                          (> (len procs) 0) (er-mk-atom "false")
                          :else
                            (do
                              (dict-set! registry mod-name
                                (er-mk-module-slot (er-module-current-env slot) nil
                                  (er-module-version slot)))
                              (er-mk-atom "true"))))))))))))

(define er-bif-code-which
  (fn (vs)
    (let ((mod-arg (nth vs 0)))
      (cond
        (not (er-atom? mod-arg))
          (raise (er-mk-error-marker (er-mk-atom "badarg")))
        (dict-has? (er-modules-get) (get mod-arg :name))
          (er-mk-atom "loaded")
        :else (er-mk-atom "non_existing")))))

(define er-bif-code-is-loaded
  (fn (vs)
    (let ((mod-arg (nth vs 0)))
      (cond
        (not (er-atom? mod-arg))
          (raise (er-mk-error-marker (er-mk-atom "badarg")))
        (dict-has? (er-modules-get) (get mod-arg :name))
          (er-mk-tuple (list (er-mk-atom "file") (er-mk-atom "loaded")))
        :else (er-mk-atom "false")))))

(define er-bif-code-all-loaded
  (fn (vs)
    (let ((registry (er-modules-get))
          (ks (keys (er-modules-get)))
          (out (er-mk-nil)))
      (for-each
        (fn (i)
          (let ((k (nth ks (- (- (len ks) 1) i))))
            (set! out
              (er-mk-cons
                (er-mk-tuple
                  (list (er-mk-atom k) (er-mk-atom "loaded")))
                out))))
        (range 0 (len ks)))
      out)))


