;; lib/mod/schema.sx — report representation + Prolog fact generation.
;;
;; A report is a dict {:id :by :about :reason :evidence :attrs :signals :at}.
;;   :evidence — accumulated {:kind :val} entries (human review, scanners)
;;   :attrs    — attribute names ("verified") for negation-as-failure conditions
;;   :signals  — weighted {:kind :weight} entries for aggregate scoring rules
;;   :at       — integer timestamp/tick (deterministic; supplied, not clock-read)
;; The engine derives keyword classifications from the reason text and projects
;; the report, its classifications, evidence, attributes, and signals into Prolog
;; facts that policy clauses match against.

(define mod/mk-report (fn (id by about reason) {:attrs (list) :id id :signals (list) :by by :evidence (list) :about about :at 0 :reason reason}))

(define mod/report-id (fn (r) (get r :id)))
(define mod/report-by (fn (r) (get r :by)))
(define mod/report-about (fn (r) (get r :about)))
(define mod/report-reason (fn (r) (get r :reason)))

(define
  mod/report-evidence
  (fn (r) (let ((e (get r :evidence))) (if (nil? e) (list) e))))

(define
  mod/report-attrs
  (fn (r) (let ((a (get r :attrs))) (if (nil? a) (list) a))))

(define
  mod/report-signals
  (fn (r) (let ((s (get r :signals))) (if (nil? s) (list) s))))

(define
  mod/report-at
  (fn (r) (let ((t (get r :at))) (if (nil? t) 0 t))))

(define mod/mk-evidence (fn (kind val) {:val val :kind kind}))
(define mod/evidence-kind (fn (e) (get e :kind)))
(define mod/evidence-val (fn (e) (get e :val)))

(define mod/mk-signal (fn (kind weight) {:kind kind :weight weight}))
(define mod/signal-kind (fn (s) (get s :kind)))
(define mod/signal-weight (fn (s) (get s :weight)))

(define mod/report* (fn (r evs attrs sigs at) {:attrs attrs :id (mod/report-id r) :signals sigs :by (mod/report-by r) :evidence evs :about (mod/report-about r) :at at :reason (mod/report-reason r)}))

(define
  mod/with-evidence
  (fn
    (r evs)
    (mod/report*
      r
      evs
      (mod/report-attrs r)
      (mod/report-signals r)
      (mod/report-at r))))

(define
  mod/with-attrs
  (fn
    (r attrs)
    (mod/report*
      r
      (mod/report-evidence r)
      attrs
      (mod/report-signals r)
      (mod/report-at r))))

(define
  mod/with-signals
  (fn
    (r sigs)
    (mod/report*
      r
      (mod/report-evidence r)
      (mod/report-attrs r)
      sigs
      (mod/report-at r))))

(define
  mod/with-at
  (fn
    (r at)
    (mod/report*
      r
      (mod/report-evidence r)
      (mod/report-attrs r)
      (mod/report-signals r)
      at)))

(define
  mod/attach-evidence
  (fn
    (r e)
    (mod/with-evidence r (append (mod/report-evidence r) (list e)))))

(define
  mod/attach-attr
  (fn (r a) (mod/with-attrs r (append (mod/report-attrs r) (list a)))))

(define
  mod/attach-signal
  (fn (r s) (mod/with-signals r (append (mod/report-signals r) (list s)))))

;; ── substring search (the prolog-loaded env lacks includes?; slice/len do work) ──

(define
  mod/contains-at?
  (fn
    (hay needle hl nl pos)
    (if
      (< hl (+ pos nl))
      false
      (if
        (= (slice hay pos (+ pos nl)) needle)
        true
        (mod/contains-at? hay needle hl nl (+ pos 1))))))

(define
  mod/str-contains?
  (fn
    (hay needle)
    (let
      ((hl (len hay)) (nl (len needle)))
      (if
        (= nl 0)
        true
        (mod/contains-at? hay needle hl nl 0)))))

;; ── evidence derivation (keyword classification) ──

(define
  mod/spam-keywords
  (list "spam" "buy now" "click here" "free money" "viagra" "limited offer"))

(define
  mod/abuse-keywords
  (list "abuse" "harassment" "threat" "slur" "hate speech"))

(define
  mod/any?
  (fn (pred coll) (reduce (fn (acc x) (if acc acc (pred x))) false coll)))

(define
  mod/reason-matches?
  (fn
    (reason kws)
    (let
      ((low (downcase reason)))
      (mod/any? (fn (k) (mod/str-contains? low k)) kws))))

(define
  mod/classify-keywords
  (fn
    (r)
    (let
      ((reason (mod/report-reason r)) (kinds (list)))
      (begin
        (when
          (mod/reason-matches? reason mod/spam-keywords)
          (append! kinds "spam"))
        (when
          (mod/reason-matches? reason mod/abuse-keywords)
          (append! kinds "abuse"))
        kinds))))

(define
  mod/report-count
  (fn
    (about reports)
    (reduce
      (fn
        (acc r)
        (if (= (mod/report-about r) about) (+ acc 1) acc))
      0
      reports)))

;; ── Prolog fact projection ──

(define
  mod/join-with
  (fn
    (sep items)
    (reduce (fn (acc x) (if (= acc "") x (str acc sep x))) "" items)))

(define mod/pl-quote (fn (s) (str "'" s "'")))

(define
  mod/classification-facts
  (fn
    (id kinds)
    (mod/join-with
      "\n"
      (map (fn (k) (str "classification(" id ", " k ").")) kinds))))

(define
  mod/evidence-facts
  (fn
    (id evs)
    (mod/join-with
      "\n"
      (map
        (fn
          (e)
          (str
            "evidence("
            id
            ", "
            (mod/pl-quote (mod/evidence-kind e))
            ", "
            (mod/pl-quote (str (mod/evidence-val e)))
            ")."))
        evs))))

(define
  mod/attr-facts
  (fn
    (id attrs)
    (mod/join-with "\n" (map (fn (a) (str "attr(" id ", " a ").")) attrs))))

(define
  mod/signal-facts
  (fn
    (id sigs)
    (mod/join-with
      "\n"
      (map
        (fn
          (s)
          (str
            "signal("
            id
            ", "
            (mod/pl-quote (mod/signal-kind s))
            ", "
            (mod/signal-weight s)
            ")."))
        sigs))))

(define
  mod/report-facts
  (fn
    (r count)
    (let
      ((id (mod/report-id r))
        (by (mod/pl-quote (mod/report-by r)))
        (about (mod/pl-quote (mod/report-about r))))
      (let
        ((cls (mod/classification-facts id (mod/classify-keywords r)))
          (evs (mod/evidence-facts id (mod/report-evidence r)))
          (ats (mod/attr-facts id (mod/report-attrs r)))
          (sgs (mod/signal-facts id (mod/report-signals r))))
        (mod/join-with
          "\n"
          (list
            (str "report(" id ", " by ", " about ").")
            (str "report_count(" about ", " count ").")
            cls
            evs
            ats
            sgs))))))
