;; lib/dream/demos/todo.sx — CRUD todo list with forms + CSRF (todo.ml).
;; An in-memory store holds items; add/toggle/delete go through POST forms guarded
;; by the CSRF middleware. User text is HTML-escaped on render (dream-escape).
;; Wires session -> csrf -> router.

(define
  dream-todo-store
  (fn () (let ((items (list)) (next-id 0)) {:all (fn () items) :add (fn (text) (begin (set! next-id (+ next-id 1)) (set! items (concat items (list {:id next-id :text text :done false}))) next-id)) :delete (fn (id) (set! items (filter (fn (it) (not (= (get it :id) id))) items))) :toggle (fn (id) (set! items (map (fn (it) (if (= (get it :id) id) (assoc it :done (not (get it :done))) it)) items)))})))

(define
  dr/todo-render
  (fn
    (store req)
    (str
      "<ul>"
      (reduce
        (fn
          (acc it)
          (str
            acc
            "<li>"
            (if (get it :done) "[x] " "[ ] ")
            (dream-escape (get it :text))
            "</li>"))
        ""
        ((get store :all)))
      "</ul>"
      "<form method=\"post\" action=\"/add\">"
      (dream-csrf-tag req)
      "<input name=\"text\"><button>Add</button></form>")))

(define
  dream-todo-index
  (fn (store) (fn (req) (dream-html (dr/todo-render store req)))))

(define
  dream-todo-add
  (fn
    (store)
    (fn
      (req)
      (let
        ((r (dream-form req)))
        (if
          (dream-ok? r)
          (begin
            ((get store :add) (get (dream-ok-value r) "text"))
            (dream-redirect "/"))
          (dream-html-status
            403
            (str "Rejected: " (dream-err-reason r))))))))

(define
  dream-todo-toggle
  (fn
    (store)
    (fn
      (req)
      (let
        ((r (dream-form req)))
        (if
          (dream-ok? r)
          (begin
            ((get store :toggle) (parse-int (dream-param req "id")))
            (dream-redirect "/"))
          (dream-html-status 403 "Rejected"))))))

(define
  dream-todo-delete
  (fn
    (store)
    (fn
      (req)
      (let
        ((r (dream-form req)))
        (if
          (dream-ok? r)
          (begin
            ((get store :delete) (parse-int (dream-param req "id")))
            (dream-redirect "/"))
          (dream-html-status 403 "Rejected"))))))

(define
  dream-todo-app-with
  (fn
    (store backend secret)
    ((dream-sessions backend)
      ((dream-csrf secret)
        (dream-router
          (list
            (dream-get "/" (dream-todo-index store))
            (dream-post "/add" (dream-todo-add store))
            (dream-post "/toggle/:id" (dream-todo-toggle store))
            (dream-post "/delete/:id" (dream-todo-delete store))))))))

;; entry: (dream-run (dream-todo-app-with (dream-todo-store) (dream-memory-sessions) "change-me"))
