;; infer.sx — Hindley-Milner Algorithm W for Haskell-on-SX (Phase 4).
;;
;; Types: TVar, TCon, TArr, TApp, TTuple, TScheme
;; Substitution: apply, compose, restrict
;; Unification (with occurs check)
;; Instantiation + generalization (let-polymorphism)
;; Algorithm W for: literals, var, con, lambda, app, let, if, op, tuple, list

;; ─── Type constructors ────────────────────────────────────────────────────────

(define hk-tvar (fn (n) (list "TVar" n)))
(define hk-tcon (fn (s) (list "TCon" s)))
(define hk-tarr (fn (a b) (list "TArr" a b)))
(define hk-tapp (fn (a b) (list "TApp" a b)))
(define hk-ttuple (fn (ts) (list "TTuple" ts)))
(define hk-tscheme (fn (vs t) (list "TScheme" vs t)))

(define hk-tvar? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TVar"))))
(define hk-tcon? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TCon"))))
(define hk-tarr? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TArr"))))
(define hk-tapp? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TApp"))))
(define hk-ttuple? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TTuple"))))
(define hk-tscheme? (fn (t) (and (list? t) (not (empty? t)) (= (first t) "TScheme"))))

(define hk-tvar-name (fn (t) (nth t 1)))
(define hk-tcon-name (fn (t) (nth t 1)))
(define hk-tarr-t1 (fn (t) (nth t 1)))
(define hk-tarr-t2 (fn (t) (nth t 2)))
(define hk-tapp-t1 (fn (t) (nth t 1)))
(define hk-tapp-t2 (fn (t) (nth t 2)))
(define hk-ttuple-ts (fn (t) (nth t 1)))
(define hk-tscheme-vs (fn (t) (nth t 1)))
(define hk-tscheme-type (fn (t) (nth t 2)))

(define hk-t-int    (hk-tcon "Int"))
(define hk-t-bool   (hk-tcon "Bool"))
(define hk-t-string (hk-tcon "String"))
(define hk-t-char   (hk-tcon "Char"))
(define hk-t-float  (hk-tcon "Float"))
(define hk-t-list (fn (t) (hk-tapp (hk-tcon "[]") t)))

;; ─── Type formatter ──────────────────────────────────────────────────────────

(define
  hk-type->str
  (fn
    (t)
    (cond
      ((hk-tvar? t)   (hk-tvar-name t))
      ((hk-tcon? t)   (hk-tcon-name t))
      ((hk-tarr? t)
       (let ((s1 (if (hk-tarr? (hk-tarr-t1 t))
                   (str "(" (hk-type->str (hk-tarr-t1 t)) ")")
                   (hk-type->str (hk-tarr-t1 t)))))
         (str s1 " -> " (hk-type->str (hk-tarr-t2 t)))))
      ((hk-tapp? t)
       (let ((h (hk-tapp-t1 t)))
         (cond
           ((and (hk-tcon? h) (= (hk-tcon-name h) "[]"))
            (str "[" (hk-type->str (hk-tapp-t2 t)) "]"))
           (:else
            (str "(" (hk-type->str h) " " (hk-type->str (hk-tapp-t2 t)) ")")))))
      ((hk-ttuple? t)
       (str "(" (join ", " (map hk-type->str (hk-ttuple-ts t))) ")"))
      ((hk-tscheme? t)
       (str "forall " (join " " (hk-tscheme-vs t)) ". " (hk-type->str (hk-tscheme-type t))))
      (:else "<?>"))))

;; ─── Fresh variable counter ───────────────────────────────────────────────────

(define hk-fresh-ctr 0)
(define hk-fresh (fn () (set! hk-fresh-ctr (+ hk-fresh-ctr 1)) (hk-tvar (str "t" hk-fresh-ctr))))
(define hk-reset-fresh (fn () (set! hk-fresh-ctr 0)))

;; ─── Utilities ───────────────────────────────────────────────────────────────

(define hk-infer-member? (fn (x lst) (some (fn (y) (= x y)) lst)))

(define
  hk-nub
  (fn (lst)
    (reduce (fn (acc x) (if (hk-infer-member? x acc) acc (append acc (list x)))) (list) lst)))

;; ─── Free type variables ──────────────────────────────────────────────────────

(define
  hk-ftv
  (fn
    (t)
    (cond
      ((hk-tvar? t)    (list (hk-tvar-name t)))
      ((hk-tcon? t)    (list))
      ((hk-tarr? t)    (append (hk-ftv (hk-tarr-t1 t)) (hk-ftv (hk-tarr-t2 t))))
      ((hk-tapp? t)    (append (hk-ftv (hk-tapp-t1 t)) (hk-ftv (hk-tapp-t2 t))))
      ((hk-ttuple? t)  (reduce append (list) (map hk-ftv (hk-ttuple-ts t))))
      ((hk-tscheme? t)
       (filter
         (fn (v) (not (hk-infer-member? v (hk-tscheme-vs t))))
         (hk-ftv (hk-tscheme-type t))))
      (:else (list)))))

(define
  hk-ftv-env
  (fn (env)
    (reduce (fn (acc k) (append acc (hk-ftv (get env k)))) (list) (keys env))))

;; ─── Substitution ─────────────────────────────────────────────────────────────

(define hk-subst-empty (dict))

(define
  hk-subst-restrict
  (fn
    (s exclude)
    (let ((r (dict)))
      (for-each
        (fn (k)
          (when (not (hk-infer-member? k exclude))
            (dict-set! r k (get s k))))
        (keys s))
      r)))

(define
  hk-subst-apply
  (fn
    (s t)
    (cond
      ((hk-tvar? t)
       (let ((v (get s (hk-tvar-name t))))
         (if (nil? v) t (hk-subst-apply s v))))
      ((hk-tarr? t)
       (hk-tarr (hk-subst-apply s (hk-tarr-t1 t))
                (hk-subst-apply s (hk-tarr-t2 t))))
      ((hk-tapp? t)
       (hk-tapp (hk-subst-apply s (hk-tapp-t1 t))
                (hk-subst-apply s (hk-tapp-t2 t))))
      ((hk-ttuple? t)
       (hk-ttuple (map (fn (u) (hk-subst-apply s u)) (hk-ttuple-ts t))))
      ((hk-tscheme? t)
       (let ((s2 (hk-subst-restrict s (hk-tscheme-vs t))))
         (hk-tscheme (hk-tscheme-vs t)
                     (hk-subst-apply s2 (hk-tscheme-type t)))))
      (:else t))))

(define
  hk-subst-compose
  (fn
    (s2 s1)
    (let ((r (hk-dict-copy s2)))
      (for-each
        (fn (k)
          (when (nil? (get r k))
            (dict-set! r k (hk-subst-apply s2 (get s1 k)))))
        (keys s1))
      r)))

(define
  hk-env-apply-subst
  (fn
    (s env)
    (let ((r (dict)))
      (for-each (fn (k) (dict-set! r k (hk-subst-apply s (get env k)))) (keys env))
      r)))

;; ─── Unification ─────────────────────────────────────────────────────────────

(define
  hk-bind-var
  (fn
    (v t)
    (cond
      ((and (hk-tvar? t) (= (hk-tvar-name t) v))
       hk-subst-empty)
      ((hk-infer-member? v (hk-ftv t))
       (raise (str "Occurs check failed: " v " in " (hk-type->str t))))
      (:else
       (let ((s (dict)))
         (dict-set! s v t)
         s)))))

(define
  hk-zip-unify
  (fn
    (ts1 ts2 acc)
    (if (or (empty? ts1) (empty? ts2))
      acc
      (let ((s (hk-unify (hk-subst-apply acc (first ts1))
                         (hk-subst-apply acc (first ts2)))))
        (hk-zip-unify (rest ts1) (rest ts2) (hk-subst-compose s acc))))))

(define
  hk-unify
  (fn
    (t1 t2)
    (cond
      ((and (hk-tvar? t1) (hk-tvar? t2) (= (hk-tvar-name t1) (hk-tvar-name t2)))
       hk-subst-empty)
      ((hk-tvar? t1) (hk-bind-var (hk-tvar-name t1) t2))
      ((hk-tvar? t2) (hk-bind-var (hk-tvar-name t2) t1))
      ((and (hk-tcon? t1) (hk-tcon? t2) (= (hk-tcon-name t1) (hk-tcon-name t2)))
       hk-subst-empty)
      ((and (hk-tarr? t1) (hk-tarr? t2))
       (let ((s1 (hk-unify (hk-tarr-t1 t1) (hk-tarr-t1 t2))))
         (let ((s2 (hk-unify (hk-subst-apply s1 (hk-tarr-t2 t1))
                             (hk-subst-apply s1 (hk-tarr-t2 t2)))))
           (hk-subst-compose s2 s1))))
      ((and (hk-tapp? t1) (hk-tapp? t2))
       (let ((s1 (hk-unify (hk-tapp-t1 t1) (hk-tapp-t1 t2))))
         (let ((s2 (hk-unify (hk-subst-apply s1 (hk-tapp-t2 t1))
                             (hk-subst-apply s1 (hk-tapp-t2 t2)))))
           (hk-subst-compose s2 s1))))
      ((and (hk-ttuple? t1) (hk-ttuple? t2)
            (= (length (hk-ttuple-ts t1)) (length (hk-ttuple-ts t2))))
       (hk-zip-unify (hk-ttuple-ts t1) (hk-ttuple-ts t2) hk-subst-empty))
      (:else
       (raise (str "Cannot unify " (hk-type->str t1) " with " (hk-type->str t2)))))))

;; ─── Instantiation and generalization ────────────────────────────────────────

(define
  hk-instantiate
  (fn
    (t)
    (if (not (hk-tscheme? t))
      t
      (let ((s (dict)))
        (for-each (fn (v) (dict-set! s v (hk-fresh))) (hk-tscheme-vs t))
        (hk-subst-apply s (hk-tscheme-type t))))))

(define
  hk-generalize
  (fn
    (env t)
    (let ((free-t   (hk-nub (hk-ftv t)))
          (free-env (hk-nub (hk-ftv-env env))))
      (let ((bound (filter (fn (v) (not (hk-infer-member? v free-env))) free-t)))
        (if (empty? bound)
          t
          (hk-tscheme bound t))))))

;; ─── Pattern binding extraction ──────────────────────────────────────────────
;; Returns a dict of name → type bindings introduced by matching pat against tv.

(define
  hk-w-pat
  (fn
    (pat tv)
    (let ((tag (first pat)))
      (cond
        ((= tag "p-var")  (let ((d (dict))) (dict-set! d (nth pat 1) tv) d))
        ((= tag "p-wild") (dict))
        (:else            (dict))))))

;; ─── Algorithm W ─────────────────────────────────────────────────────────────
;; hk-w : env × expr → (list subst type)

(define
  hk-w-let
  (fn
    (env binds body)
    ;; Infer types for each binding in order, generalising at each step.
    (let
      ((env2
        (reduce
          (fn
            (cur-env b)
            (let ((tag (first b)))
              (cond
                ;; Simple pattern binding: let x = expr
                ((or (= tag "bind") (= tag "pat-bind"))
                 (let ((pat  (nth b 1))
                       (rhs  (nth b 2)))
                   (let ((tv (hk-fresh)))
                     (let ((r (hk-w cur-env rhs)))
                       (let ((s1 (first r)) (t1 (nth r 1)))
                         (let ((s2 (hk-unify (hk-subst-apply s1 tv) t1)))
                           (let ((s (hk-subst-compose s2 s1)))
                             (let ((t-gen (hk-generalize (hk-env-apply-subst s cur-env)
                                                         (hk-subst-apply s t1))))
                               (let ((bindings (hk-w-pat pat t-gen)))
                                 (let ((r2 (hk-dict-copy cur-env)))
                                   (for-each
                                     (fn (k) (dict-set! r2 k (get bindings k)))
                                     (keys bindings))
                                   r2))))))))))
                ;; Function clause: let f x y = expr
                ((= tag "fun-clause")
                 (let ((name (nth b 1))
                       (pats (nth b 2))
                       (body2 (nth b 3)))
                   ;; Treat as: let name = lambda pats body2
                   (let ((rhs (if (empty? pats)
                                body2
                                (list "lambda" pats body2))))
                     (let ((tv (hk-fresh)))
                       (let ((env-rec (hk-dict-copy cur-env)))
                         (dict-set! env-rec name tv)
                         (let ((r (hk-w env-rec rhs)))
                           (let ((s1 (first r)) (t1 (nth r 1)))
                             (let ((s2 (hk-unify (hk-subst-apply s1 tv) t1)))
                               (let ((s (hk-subst-compose s2 s1)))
                                 (let ((t-gen (hk-generalize
                                                (hk-env-apply-subst s cur-env)
                                                (hk-subst-apply s t1))))
                                   (let ((r2 (hk-dict-copy cur-env)))
                                     (dict-set! r2 name t-gen)
                                     r2)))))))))))
                (:else cur-env))))
          env
          binds)))
      (hk-w env2 body))))

(define
  hk-w
  (fn
    (env expr)
    (let ((tag (first expr)))
      (cond
        ;; Literals
        ((= tag "int")    (list hk-subst-empty hk-t-int))
        ((= tag "float")  (list hk-subst-empty hk-t-float))
        ((= tag "string") (list hk-subst-empty hk-t-string))
        ((= tag "char")   (list hk-subst-empty hk-t-char))

        ;; Variable
        ((= tag "var")
         (let ((name (nth expr 1)))
           (let ((scheme (get env name)))
             (if (nil? scheme)
               (raise (str "Unbound variable: " name))
               (list hk-subst-empty (hk-instantiate scheme))))))

        ;; Constructor (same lookup as var)
        ((= tag "con")
         (let ((name (nth expr 1)))
           (let ((scheme (get env name)))
             (if (nil? scheme)
               (list hk-subst-empty (hk-fresh))
               (list hk-subst-empty (hk-instantiate scheme))))))

        ;; Unary negation
        ((= tag "neg")
         (let ((r (hk-w env (nth expr 1))))
           (let ((s1 (first r)) (t1 (nth r 1)))
             (let ((s2 (hk-unify t1 hk-t-int)))
               (list (hk-subst-compose s2 s1) hk-t-int)))))

        ;; Lambda: ("lambda" pats body)
        ((= tag "lambda")
         (let ((pats (nth expr 1))
               (body (nth expr 2)))
           (if (empty? pats)
             (hk-w env body)
             (let ((pat  (first pats))
                   (rest (rest pats)))
               (let ((tv (hk-fresh)))
                 (let ((bindings (hk-w-pat pat tv)))
                   (let ((env2 (hk-dict-copy env)))
                     (for-each (fn (k) (dict-set! env2 k (get bindings k))) (keys bindings))
                     (let ((inner (if (empty? rest)
                                    body
                                    (list "lambda" rest body))))
                       (let ((r (hk-w env2 inner)))
                         (let ((s1 (first r)) (t1 (nth r 1)))
                           (list s1 (hk-tarr (hk-subst-apply s1 tv) t1))))))))))))

        ;; Application: ("app" f x)
        ((= tag "app")
         (let ((tv (hk-fresh)))
           (let ((r1 (hk-w env (nth expr 1))))
             (let ((s1 (first r1)) (tf (nth r1 1)))
               (let ((r2 (hk-w (hk-env-apply-subst s1 env) (nth expr 2))))
                 (let ((s2 (first r2)) (tx (nth r2 1)))
                   (let ((s3 (hk-unify (hk-subst-apply s2 tf) (hk-tarr tx tv))))
                     (let ((s (hk-subst-compose s3 (hk-subst-compose s2 s1))))
                       (list s (hk-subst-apply s3 tv))))))))))

        ;; Let: ("let" binds body)
        ((= tag "let")
         (hk-w-let env (nth expr 1) (nth expr 2)))

        ;; If: ("if" cond then else)
        ((= tag "if")
         (let ((r1 (hk-w env (nth expr 1))))
           (let ((s1 (first r1)) (tc (nth r1 1)))
             (let ((s2 (hk-unify tc hk-t-bool)))
               (let ((s12 (hk-subst-compose s2 s1)))
                 (let ((r2 (hk-w (hk-env-apply-subst s12 env) (nth expr 2))))
                   (let ((s3 (first r2)) (tt (nth r2 1)))
                     (let ((s123 (hk-subst-compose s3 s12)))
                       (let ((r3 (hk-w (hk-env-apply-subst s123 env) (nth expr 3))))
                         (let ((s4 (first r3)) (te (nth r3 1)))
                           (let ((s5 (hk-unify (hk-subst-apply s4 tt) te)))
                             (let ((s (hk-subst-compose s5 (hk-subst-compose s4 s123))))
                               (list s (hk-subst-apply s5 te))))))))))))))

        ;; Binary operator: ("op" op-name left right)
        ;; Desugar to double application.
        ((= tag "op")
         (hk-w env
               (list "app"
                     (list "app" (list "var" (nth expr 1)) (nth expr 2))
                     (nth expr 3))))

        ;; Tuple: ("tuple" [e1 e2 ...])
        ((= tag "tuple")
         (let ((elems (nth expr 1)))
           (let ((s-acc hk-subst-empty)
                 (ts    (list)))
             (for-each
               (fn (e)
                 (let ((r (hk-w (hk-env-apply-subst s-acc env) e)))
                   (set! s-acc (hk-subst-compose (first r) s-acc))
                   (set! ts    (append ts (list (nth r 1))))))
               elems)
             (list s-acc (hk-ttuple (map (fn (t) (hk-subst-apply s-acc t)) ts))))))

        ;; List literal: ("list" [e1 e2 ...])
        ((= tag "list")
         (let ((elems (nth expr 1)))
           (if (empty? elems)
             (list hk-subst-empty (hk-t-list (hk-fresh)))
             (let ((tv (hk-fresh)))
               (let ((s-acc hk-subst-empty))
                 (for-each
                   (fn (e)
                     (let ((r (hk-w (hk-env-apply-subst s-acc env) e)))
                       (let ((s2 (first r)) (te (nth r 1)))
                         (let ((s3 (hk-unify (hk-subst-apply s2 tv) te)))
                           (set! s-acc (hk-subst-compose s3 (hk-subst-compose s2 s-acc)))))))
                   elems)
                 (list s-acc (hk-t-list (hk-subst-apply s-acc tv))))))))

        ;; Location annotation: just delegate — position is for outer context.
        ((= tag "loc")
         (hk-w env (nth expr 3)))

        (:else
         (raise (str "hk-w: unhandled tag: " tag)))))))

;; ─── Initial type environment ─────────────────────────────────────────────────
;; Monomorphic numeric ops (no Num typeclass yet — upgraded in Phase 5).

(define
  hk-type-env0
  (fn ()
    (let ((env (dict)))
      ;; Integer arithmetic
      (for-each
        (fn (op)
          (dict-set! env op (hk-tarr hk-t-int (hk-tarr hk-t-int hk-t-int))))
        (list "+" "-" "*" "div" "mod" "quot" "rem"))
      ;; Integer comparison → Bool
      (for-each
        (fn (op)
          (dict-set! env op (hk-tarr hk-t-int (hk-tarr hk-t-int hk-t-bool))))
        (list "==" "/=" "<" "<=" ">" ">="))
      ;; Boolean operators
      (dict-set! env "&&" (hk-tarr hk-t-bool (hk-tarr hk-t-bool hk-t-bool)))
      (dict-set! env "||" (hk-tarr hk-t-bool (hk-tarr hk-t-bool hk-t-bool)))
      (dict-set! env "not" (hk-tarr hk-t-bool hk-t-bool))
      ;; Constructors
      (dict-set! env "True"  hk-t-bool)
      (dict-set! env "False" hk-t-bool)
      ;; Polymorphic list ops (using TScheme)
      (let ((a (hk-tvar "a")))
        (dict-set! env "head"  (hk-tscheme (list "a") (hk-tarr (hk-t-list a) a)))
        (dict-set! env "tail"  (hk-tscheme (list "a") (hk-tarr (hk-t-list a) (hk-t-list a))))
        (dict-set! env "null"  (hk-tscheme (list "a") (hk-tarr (hk-t-list a) hk-t-bool)))
        (dict-set! env "length" (hk-tscheme (list "a") (hk-tarr (hk-t-list a) hk-t-int)))
        (dict-set! env "reverse" (hk-tscheme (list "a") (hk-tarr (hk-t-list a) (hk-t-list a))))
        (dict-set! env ":"
          (hk-tscheme (list "a") (hk-tarr a (hk-tarr (hk-t-list a) (hk-t-list a))))))
      ;; negate
      (dict-set! env "negate" (hk-tarr hk-t-int hk-t-int))
      (dict-set! env "abs"    (hk-tarr hk-t-int hk-t-int))
      env)))

;; ─── Expression brief printer ────────────────────────────────────────────────
;; Produces a short human-readable label for an AST node used in error messages.

(define
  hk-expr->brief
  (fn
    (expr)
    (cond
      ((not (list? expr)) (str expr))
      ((empty? expr)      "()")
      (:else
       (let ((tag (first expr)))
         (cond
           ((= tag "var")    (nth expr 1))
           ((= tag "con")    (nth expr 1))
           ((= tag "int")    (str (nth expr 1)))
           ((= tag "float")  (str (nth expr 1)))
           ((= tag "string") (str "\"" (nth expr 1) "\""))
           ((= tag "char")   (str "'" (nth expr 1) "'"))
           ((= tag "neg")    (str "(-" (hk-expr->brief (nth expr 1)) ")"))
           ((= tag "app")
            (str "(" (hk-expr->brief (nth expr 1))
                 " " (hk-expr->brief (nth expr 2)) ")"))
           ((= tag "op")
            (str "(" (hk-expr->brief (nth expr 2))
                 " " (nth expr 1)
                 " " (hk-expr->brief (nth expr 3)) ")"))
           ((= tag "lambda") "(\\ ...)")
           ((= tag "let")    "(let ...)")
           ((= tag "if")     "(if ...)")
           ((= tag "tuple")  "(tuple ...)")
           ((= tag "list")   "[...]")
           ((= tag "loc")    (hk-expr->brief (nth expr 3)))
           (:else            (str "(" tag " ..."))))))))

;; ─── Loc-annotated inference ──────────────────────────────────────────────────
;; ("loc" LINE COL INNER) node: hk-w catches any error and re-raises with
;; "at LINE:COL: " prepended.  Emitted by the parser or test scaffolding.

;; Extended hk-w handles "loc" — handled inline in the cond below.

;; ─── Program-level inference ─────────────────────────────────────────────────
;; hk-infer-decl : env × decl → ("ok" name type-str) | ("err" msg) | nil
;;   Uses tagged results so callers don't need re-raise.

(define
  hk-infer-decl
  (fn
    (env decl)
    (let
      ((tag (first decl)))
      (cond
        ((= tag "fun-clause")
          (let
            ((name (nth decl 1)) (pats (nth decl 2)) (body (nth decl 3)))
            (let
              ((rhs (if (empty? pats) body (list "lambda" pats body))))
              (guard
                (e (#t (list "err" (str "in '" name "': " e))))
                (begin
                  (hk-reset-fresh)
                  (let
                    ((r (hk-w env rhs)))
                    (let
                      ((final-type (hk-subst-apply (first r) (nth r 1))))
                      (list "ok" name (hk-type->str final-type) final-type))))))))
        ((or (= tag "bind") (= tag "pat-bind"))
          (let
            ((pat (nth decl 1)) (body (nth decl 2)))
            (let
              ((label (if (and (list? pat) (= (first pat) "p-var")) (nth pat 1) "<binding>")))
              (guard
                (e (#t (list "err" (str "in '" label "': " e))))
                (begin
                  (hk-reset-fresh)
                  (let
                    ((r (hk-w env body)))
                    (let
                      ((final-type (hk-subst-apply (first r) (nth r 1))))
                      (list "ok" label (hk-type->str final-type) final-type))))))))
        (:else nil)))))

;; hk-infer-prog : program-ast × env → list of ("ok" name type) | ("err" msg)

(define
  hk-ast-type
  (fn
    (ast)
    (let
      ((tag (first ast)))
      (cond
        ((= tag "t-con") (list "TCon" (nth ast 1)))
        ((= tag "t-var") (list "TVar" (nth ast 1)))
        ((= tag "t-fun")
          (list "TArr" (hk-ast-type (nth ast 1)) (hk-ast-type (nth ast 2))))
        ((= tag "t-app")
          (list "TApp" (hk-ast-type (nth ast 1)) (hk-ast-type (nth ast 2))))
        ((= tag "t-list")
          (list "TApp" (list "TCon" "[]") (hk-ast-type (nth ast 1))))
        ((= tag "t-tuple") (list "TTuple" (map hk-ast-type (nth ast 1))))
        (:else (raise (str "unknown type node: " (first ast))))))))

;; ─── Convenience ─────────────────────────────────────────────────────────────
;; hk-infer-type : Haskell expression source → inferred type string

(define
  hk-collect-tvars
  (fn
    (t acc)
    (cond
      ((= (first t) "TVar")
        (if
          (some (fn (v) (= v (nth t 1))) acc)
          acc
          (begin (append! acc (nth t 1)) acc)))
      ((= (first t) "TArr")
        (hk-collect-tvars (nth t 2) (hk-collect-tvars (nth t 1) acc)))
      ((= (first t) "TApp")
        (hk-collect-tvars (nth t 2) (hk-collect-tvars (nth t 1) acc)))
      ((= (first t) "TTuple")
        (reduce (fn (a elem) (hk-collect-tvars elem a)) acc (nth t 1)))
      (:else acc))))

(define
  hk-check-sig
  (fn
    (declared-ast inferred-type)
    (let
      ((declared (hk-ast-type declared-ast)))
      (let
        ((tvars (hk-collect-tvars declared (list))))
        (let
          ((scheme (if (empty? tvars) declared (list "TScheme" tvars declared))))
          (let
            ((inst (hk-instantiate scheme)))
            (hk-unify inst inferred-type)))))))

(define
  hk-infer-prog
  (fn
    (prog env)
    (let
      ((decls (cond ((and (list? prog) (= (first prog) "program")) (nth prog 1)) ((and (list? prog) (= (first prog) "module")) (nth prog 3)) (:else (list))))
        (results (list))
        (sigs (dict)))
      (for-each
        (fn
          (d)
          (when
            (= (first d) "type-sig")
            (let
              ((names (nth d 1)) (type-ast (nth d 2)))
              (for-each (fn (n) (dict-set! sigs n type-ast)) names))))
        decls)
      (for-each
        (fn
          (d)
          (let
            ((r (hk-infer-decl env d)))
            (when
              (not (nil? r))
              (let
                ((checked (if (and (= (first r) "ok") (has-key? sigs (nth r 1))) (guard (e (true (list "err" (str "in '" (nth r 1) "': declared type mismatch: " e)))) (begin (hk-check-sig (get sigs (nth r 1)) (nth r 3)) r)) r)))
                (append! results checked)
                (when
                  (= (first checked) "ok")
                  (dict-set! env (nth checked 1) (nth checked 3)))))))
        decls)
      results)))

(define
  hk-infer-type
  (fn
    (src)
    (hk-reset-fresh)
    (let
      ((ast (hk-core-expr src)) (env (hk-type-env0)))
      (let
        ((r (hk-w env ast)))
        (hk-type->str (hk-subst-apply (first r) (nth r 1)))))))
