;; lib/prolog/hs-bridge.sx — Prolog ↔ Hyperscript bridge
;;
;; Two complementary integration styles:
;;
;; 1. Hook style — for `prolog(db, "goal(args)")` call syntax in Hyperscript:
;;      (pl-install-hs-hook!)   ;; call once at startup
;;    Requires lib/hyperscript/plugins/prolog.sx (provides hs-set-prolog-hook!)
;;
;; 2. Factory style — for named conditions like `when allowed(user, action)`:
;;      (define allowed (pl-hs-predicate/2 pl-db "allowed"))
;;    No parser/compiler changes needed: Hyperscript compiles
;;    `allowed(user, action)` to `(allowed user action)` — a plain SX call.
;;
;; Requires tokenizer.sx, parser.sx, runtime.sx, query.sx loaded first.

;; --- Hook style ---

(define
  pl-install-hs-hook!
  (fn
    ()
    (hs-set-prolog-hook!
      (fn (db goal) (not (= nil (pl-query-one db goal)))))))

;; --- Factory style ---

;; Test whether a ground Prolog goal succeeds against db.
;; Returns true/false (not a solution dict).
(define
  pl-hs-query
  (fn (db goal-str) (not (nil? (pl-query-one db goal-str)))))

;; Build a Prolog goal string from a predicate name and arg list.
;; SX values: strings/keywords pass through; numbers are stringified via str.
(define
  pl-hs-build-goal
  (fn
    (pred-name args)
    (str pred-name "(" (join ", " (map (fn (a) (str a)) args)) ")")))

;; Return a 1-arg SX function that succeeds iff pred(a) holds in db.
(define
  pl-hs-predicate/1
  (fn
    (db pred-name)
    (fn (a) (pl-hs-query db (pl-hs-build-goal pred-name (list a))))))

;; Return a 2-arg SX function that succeeds iff pred(a, b) holds in db.
(define
  pl-hs-predicate/2
  (fn
    (db pred-name)
    (fn (a b) (pl-hs-query db (pl-hs-build-goal pred-name (list a b))))))

;; Return a 3-arg SX function that succeeds iff pred(a, b, c) holds in db.
(define
  pl-hs-predicate/3
  (fn
    (db pred-name)
    (fn (a b c) (pl-hs-query db (pl-hs-build-goal pred-name (list a b c))))))

;; Install every predicate in install-list as a named SX function backed by db.
;; install-list: list of (name arity) pairs.
;; Returns a dict {name → fn} for the caller to destructure.
(define
  pl-hs-install
  (fn
    (db install-list)
    (reduce
      (fn
        (acc entry)
        (let
          ((pred-name (first entry)) (arity (nth entry 1)))
          (dict-set!
            acc
            pred-name
            (cond
              ((= arity 1) (pl-hs-predicate/1 db pred-name))
              ((= arity 2) (pl-hs-predicate/2 db pred-name))
              ((= arity 3) (pl-hs-predicate/3 db pred-name))
              (true (fn (a b) false))))
          acc))
      {}
      install-list)))
