;; lib/mod/policy.sx — moderation rules → Prolog clauses.
;;
;; A rule is {:name :action :when}. :when is a list of condition forms; each
;; compiles to a Prolog goal. The conditions in a :when list are ANDed (joined by
;; ", "); :not negates and :any (a list of sub-conditions) disjoins — so the
;; condition language is a small boolean algebra over the leaf predicates.
;; Rule order is precedence: the engine queries with pl-query-one, so the first
;; clause that proves wins. The final default rule has an empty body (true) so
;; every report yields at least :keep — "no rule matched" is a real result, not a
;; query failure.
;;
;; cond->goal takes an id-term so the same condition can be compiled with the
;; head variable "Id" (for clause bodies) or a concrete report id (for proof-tree
;; goal-by-goal re-querying in the engine).
;;
;; Precedence (top wins): exoneration evidence (appeal override) > confirmed-abuse
;; evidence (human review) > spam/abuse classification > repeated-report count >
;; default keep.

(define mod/mk-rule (fn (name action conds) {:when conds :name name :action action}))

(define mod/rule-name (fn (r) (get r :name)))
(define mod/rule-action (fn (r) (get r :action)))
(define mod/rule-when (fn (r) (get r :when)))

(define
  mod/default-rules
  (list
    (mod/mk-rule
      "exonerated-keep"
      :keep (list (list :evidence "exonerated")))
    (mod/mk-rule
      "reviewer-remove"
      :remove (list (list :evidence "confirmed-abuse")))
    (mod/mk-rule "spam-hide" :hide (list (list :classification "spam")))
    (mod/mk-rule
      "abuse-remove"
      :remove (list (list :classification "abuse")))
    (mod/mk-rule
      "repeated-escalate"
      :escalate (list (list :count-at-least 3)))
    (mod/mk-rule "default-keep" :keep (list))))

;; ── condition → Prolog goal ──
;;
;; (:classification "spam")  → classification(Id, spam)
;; (:evidence "kind")        → evidence(Id, 'kind', _)
;; (:attr "verified")        → attr(Id, verified)
;; (:not <cond>)             → not(<cond>)                        (negation)
;; (:any (list c1 c2 ...))   → (g1 ; g2 ; ...)                    (disjunction)
;; (:count-at-least 3)       → report(Id, B, S), report_count(S, N), N >= 3
;; (:score-at-least 5)       → aggregate_all(sum(W), signal(Id, _, W), T), T >= 5
;; (:reporters-at-least 2)   → report(Id, _, Sr), setof(Br, report(_, Br, Sr), Bsr),
;;                             length(Bsr, Nr), Nr >= 2   (quorum engine)
;; (:burst-at-least 3)       → report(Id, _, Sb), burst_count(Sb, Nb), Nb >= 3
;;                             (temporal engine)

(define
  mod/cond->goal
  (fn
    (c idterm)
    (let
      ((tag (first c)))
      (cond
        ((= tag :classification)
          (str "classification(" idterm ", " (nth c 1) ")"))
        ((= tag :evidence)
          (str
            "evidence("
            idterm
            ", "
            (mod/pl-quote (nth c 1))
            ", _)"))
        ((= tag :attr) (str "attr(" idterm ", " (nth c 1) ")"))
        ((= tag :not)
          (str "not(" (mod/cond->goal (nth c 1) idterm) ")"))
        ((= tag :any)
          (str
            "("
            (mod/join-with
              " ; "
              (map
                (fn (sub) (mod/cond->goal sub idterm))
                (nth c 1)))
            ")"))
        ((= tag :count-at-least)
          (str
            "report("
            idterm
            ", B, S), report_count(S, N), N >= "
            (nth c 1)))
        ((= tag :score-at-least)
          (str
            "aggregate_all(sum(W), signal("
            idterm
            ", _, W), T), T >= "
            (nth c 1)))
        ((= tag :reporters-at-least)
          (str
            "report("
            idterm
            ", _, Sr), setof(Br, report(_, Br, Sr), Bsr), "
            "length(Bsr, Nr), Nr >= "
            (nth c 1)))
        ((= tag :burst-at-least)
          (str
            "report("
            idterm
            ", _, Sb), burst_count(Sb, Nb), Nb >= "
            (nth c 1)))
        (true "true")))))

(define
  mod/conds->body
  (fn
    (conds idterm)
    (if
      (empty? conds)
      "true"
      (mod/join-with ", " (map (fn (c) (mod/cond->goal c idterm)) conds)))))

(define
  mod/rule->clause
  (fn
    (r)
    (str
      "policy_action(Id, "
      (mod/rule-action r)
      ", '"
      (mod/rule-name r)
      "') :- "
      (mod/conds->body (mod/rule-when r) "Id")
      ".")))

(define
  mod/rules->program
  (fn (rules) (mod/join-with "\n" (map mod/rule->clause rules))))
