;; lib/minikanren/relations.sx — Phase 4 standard relations.
;;
;; Programs use native SX lists as data. Relations decompose lists via the
;; tagged cons-cell shape `(:cons h t)` because SX has no improper pairs;
;; the unifier treats `(:cons h t)` and the native list `(h . t)` as
;; equivalent, and `mk-walk*` flattens cons cells back to flat lists for
;; reification.

;; --- pair / list shape relations ---

(define nullo (fn (l) (== l (list))))

(define pairo (fn (p) (fresh (a d) (== p (mk-cons a d)))))

(define caro (fn (p a) (fresh (d) (== p (mk-cons a d)))))

(define cdro (fn (p d) (fresh (a) (== p (mk-cons a d)))))

(define conso (fn (a d p) (== p (mk-cons a d))))

(define firsto caro)
(define resto cdro)

(define
  listo
  (fn (l) (conde ((nullo l)) ((fresh (a d) (conso a d l) (listo d))))))

;; --- appendo: the canary ---
;;
;; (appendo l s ls) — `ls` is the concatenation of `l` and `s`.
;; Runs forwards (l, s known → ls), backwards (ls known → all (l, s) pairs),
;; and bidirectionally (mix of bound + unbound).

(define
  appendo
  (fn
    (l s ls)
    (conde
      ((nullo l) (== s ls))
      ((fresh (a d res) (conso a d l) (conso a res ls) (appendo d s res))))))

;; --- membero ---
;; (membero x l) — x appears (at least once) in l.

(define
  appendo3
  (fn
    (l1 l2 l3 result)
    (fresh (l12) (appendo l1 l2 l12) (appendo l12 l3 result))))

(define
  partitiono
  (fn
    (pred l yes no)
    (conde
      ((nullo l) (nullo yes) (nullo no))
      ((fresh (a d y-rest n-rest) (conso a d l) (conde ((pred a) (conso a y-rest yes) (== no n-rest) (partitiono pred d y-rest n-rest)) ((nafc (pred a)) (== yes y-rest) (conso a n-rest no) (partitiono pred d y-rest n-rest))))))))

(define
  foldr-o
  (fn
    (rel l acc result)
    (conde
      ((nullo l) (== result acc))
      ((fresh (a d r-rest) (conso a d l) (foldr-o rel d acc r-rest) (rel a r-rest result))))))

(define
  foldl-o
  (fn
    (rel l acc result)
    (conde
      ((nullo l) (== result acc))
      ((fresh (a d new-acc) (conso a d l) (rel acc a new-acc) (foldl-o rel d new-acc result))))))

(define
  flat-mapo
  (fn
    (rel l result)
    (conde
      ((nullo l) (nullo result))
      ((fresh (a d a-result rest-result) (conso a d l) (rel a a-result) (flat-mapo rel d rest-result) (appendo a-result rest-result result))))))

(define
  nub-o
  (fn
    (l result)
    (conde
      ((nullo l) (nullo result))
      ((fresh (a d r-rest) (conso a d l) (conde ((membero a d) (nub-o d result)) ((nafc (membero a d)) (conso a r-rest result) (nub-o d r-rest))))))))


(define
  take-while-o
  (fn
    (pred l result)
    (conde
      ((nullo l) (nullo result))
      ((fresh (a d r-rest) (conso a d l) (conde ((pred a) (conso a r-rest result) (take-while-o pred d r-rest)) ((nafc (pred a)) (== result (list)))))))))

(define
  drop-while-o
  (fn
    (pred l result)
    (conde
      ((nullo l) (nullo result))
      ((fresh (a d) (conso a d l) (conde ((pred a) (drop-while-o pred d result)) ((nafc (pred a)) (== result l))))))))

(define
  membero
  (fn
    (x l)
    (conde
      ((fresh (d) (conso x d l)))
      ((fresh (a d) (conso a d l) (membero x d))))))

(define
  not-membero
  (fn
    (x l)
    (conde
      ((nullo l))
      ((fresh (a d) (conso a d l) (nafc (== a x)) (not-membero x d))))))

(define
  subseto
  (fn
    (l1 l2)
    (conde
      ((nullo l1))
      ((fresh (a d) (conso a d l1) (membero a l2) (subseto d l2))))))

(define
  reverseo
  (fn
    (l r)
    (conde
      ((nullo l) (nullo r))
      ((fresh (a d res-rev) (conso a d l) (reverseo d res-rev) (appendo res-rev (list a) r))))))

(define
  rev-acco
  (fn
    (l acc result)
    (conde
      ((nullo l) (== result acc))
      ((fresh (a d acc-prime) (conso a d l) (conso a acc acc-prime) (rev-acco d acc-prime result))))))

(define rev-2o (fn (l result) (rev-acco l (list) result)))

(define palindromeo (fn (l) (fresh (rev) (reverseo l rev) (== l rev))))

(define prefixo (fn (p l) (fresh (rest) (appendo p rest l))))

(define suffixo (fn (s l) (fresh (front) (appendo front s l))))

(define
  subo
  (fn
    (s l)
    (fresh
      (front-and-s back front)
      (appendo front-and-s back l)
      (appendo front s front-and-s))))

(define
  selecto
  (fn
    (x rest l)
    (conde
      ((conso x rest l))
      ((fresh (a d r) (conso a d l) (conso a r rest) (selecto x r d))))))

(define
  lengtho
  (fn
    (l n)
    (conde
      ((nullo l) (== n :z))
      ((fresh (a d n-1) (conso a d l) (== n (list :s n-1)) (lengtho d n-1))))))

(define
  inserto
  (fn
    (a l p)
    (conde
      ((conso a l p))
      ((fresh (h t pt) (conso h t l) (conso h pt p) (inserto a t pt))))))

(define
  permuteo
  (fn
    (l p)
    (conde
      ((nullo l) (nullo p))
      ((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p))))))

(define
  flatteno
  (fn
    (tree flat)
    (conde
      ((nullo tree) (nullo flat))
      ((pairo tree)
        (fresh
          (h t hf tf)
          (conso h t tree)
          (flatteno h hf)
          (flatteno t tf)
          (appendo hf tf flat)))
      ((nafc (nullo tree)) (nafc (pairo tree)) (== flat (list tree))))))

(define
  rembero
  (fn
    (x l out)
    (conde
      ((nullo l) (nullo out))
      ((fresh (a d) (conso a d l) (== a x) (== out d)))
      ((fresh (a d res) (conso a d l) (nafc (== a x)) (conso a res out) (rembero x d res))))))

(define
  removeo-allo
  (fn
    (x l result)
    (conde
      ((nullo l) (nullo result))
      ((fresh (a d) (conso a d l) (== a x) (removeo-allo x d result)))
      ((fresh (a d r-rest) (conso a d l) (nafc (== a x)) (conso a r-rest result) (removeo-allo x d r-rest))))))

(define
  assoco
  (fn
    (key pairs val)
    (fresh
      (rest)
      (conde
        ((conso (list key val) rest pairs))
        ((fresh (other) (conso other rest pairs) (assoco key rest val)))))))

(define
  nth-o
  (fn
    (n l elem)
    (conde
      ((== n :z) (fresh (d) (conso elem d l)))
      ((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (nth-o n-1 d elem))))))

(define
  samelengtho
  (fn
    (l1 l2)
    (conde
      ((nullo l1) (nullo l2))
      ((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (samelengtho d d-prime))))))

(define
  mapo
  (fn
    (rel l1 l2)
    (conde
      ((nullo l1) (nullo l2))
      ((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (rel a a-prime) (mapo rel d d-prime))))))

(define
  iterate-no
  (fn
    (rel n x result)
    (conde
      ((== n :z) (== result x))
      ((fresh (n-1 mid) (== n (list :s n-1)) (rel x mid) (iterate-no rel n-1 mid result))))))

(define
  pairlisto
  (fn
    (l1 l2 pairs)
    (conde
      ((nullo l1) (nullo l2) (nullo pairs))
      ((fresh (a1 d1 a2 d2 d-pairs) (conso a1 d1 l1) (conso a2 d2 l2) (conso (list a1 a2) d-pairs pairs) (pairlisto d1 d2 d-pairs))))))

(define
  zip-with-o
  (fn
    (rel l1 l2 result)
    (conde
      ((nullo l1) (nullo l2) (nullo result))
      ((fresh (a1 d1 a2 d2 a-result d-result) (conso a1 d1 l1) (conso a2 d2 l2) (rel a1 a2 a-result) (conso a-result d-result result) (zip-with-o rel d1 d2 d-result))))))

(define
  swap-firsto
  (fn
    (l result)
    (fresh
      (a b rest mid-l mid-r)
      (conso a mid-l l)
      (conso b rest mid-l)
      (conso b mid-r result)
      (conso a rest mid-r))))

(define
  everyo
  (fn
    (rel l)
    (conde
      ((nullo l))
      ((fresh (a d) (conso a d l) (rel a) (everyo rel d))))))

(define
  someo
  (fn
    (rel l)
    (conde
      ((fresh (a d) (conso a d l) (rel a)))
      ((fresh (a d) (conso a d l) (someo rel d))))))

(define
  lasto
  (fn
    (l x)
    (conde
      ((conso x (list) l))
      ((fresh (a d) (conso a d l) (lasto d x))))))

(define
  init-o
  (fn
    (l init)
    (conde
      ((fresh (x) (conso x (list) l) (== init (list))))
      ((fresh (a d d-init) (conso a d l) (conso a d-init init) (init-o d d-init))))))

(define
  tako
  (fn
    (n l prefix)
    (conde
      ((== n :z) (== prefix (list)))
      ((fresh (n-1 a d p-rest) (== n (list :s n-1)) (conso a d l) (conso a p-rest prefix) (tako n-1 d p-rest))))))

(define
  dropo
  (fn
    (n l suffix)
    (conde
      ((== n :z) (== suffix l))
      ((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (dropo n-1 d suffix))))))

(define
  repeato
  (fn
    (x n result)
    (conde
      ((== n :z) (== result (list)))
      ((fresh (n-1 r-rest) (== n (list :s n-1)) (conso x r-rest result) (repeato x n-1 r-rest))))))

(define
  concato
  (fn
    (lists result)
    (conde
      ((nullo lists) (nullo result))
      ((fresh (h t r-rest) (conso h t lists) (appendo h r-rest result) (concato t r-rest))))))
