                  (map (fn [id] [:hide-thread {:thread-id id :local-only? false}])))
            (helpers/get-open-threads state))}))
 
-(reg-event-db
+(reg-event-fx
   :invite
-  (fn [state [_ data]]
+  (fn [cofx [_ data]]
     (let [invite (schema/make-invitation data)]
-      ; TODO
-      (sync/chsk-send! [:braid.server/invite-to-group invite])
-      state)))
-
-(reg-event-db
+      {:websocket-send (list [:braid.server/invite-to-group invite])})))
+
+(reg-event-fx
   :generate-link
-  (fn [state [_ {:keys [group-id expires complete]}]]
-    (sync/chsk-send!
-      ; TODO
-      [:braid.server/generate-invite-link {:group-id group-id :expires expires}]
-      5000
-      (fn [reply]
-        ; indicate error if it fails?
-        (when-let [link (:link reply)]
-          (complete link))))
-    state))
-
-(reg-event-db
+  (fn [cofx [_ {:keys [group-id expires complete]}]]
+    {:websocket-send
+     (list
+       [:braid.server/generate-invite-link {:group-id group-id :expires expires}]
+       5000
+       (fn [reply]
+         ; indicate error if it fails?
+         (when-let [link (:link reply)]
+           (complete link))))}))
+
+(reg-event-fx
   :accept-invite
-  (fn [state [_ invite]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/invitation-accept invite])
-    (helpers/remove-invite state invite)))
-
-(reg-event-db
+  (fn [{state :db :as cofx} [_ invite]]
+    {:websocket-send (list [:braid.server/invitation-accept invite])
+     :db (helpers/remove-invite state invite)}))
+
+(reg-event-fx
   :decline-invite
-  (fn [state [_ invite]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/invitation-decline invite])
-    (helpers/remove-invite state invite)))
-
-(reg-event-db
+  (fn [{state :db :as cofx} [_ invite]]
+    {:websocket-send (list [:braid.server/invitation-decline invite])
+     :db (helpers/remove-invite state invite)}))
+
+(reg-event-fx
   :make-admin
-  (fn [state [_ {:keys [group-id user-id local-only?] :as args}]]
-    ; TODO
-    (when-not local-only?
-      (sync/chsk-send! [:braid.server/make-user-admin args]))
-    (helpers/make-user-admin state user-id group-id)))
-
-(reg-event-db
+  (fn [{state :db :as cofx} [_ {:keys [group-id user-id local-only?] :as args}]]
+    (merge
+      {:db (helpers/make-user-admin state user-id group-id)}
+      (when-not local-only?
+        {:websocket-send (list [:braid.server/make-user-admin args])}))))
+
+(reg-event-fx
   :remove-from-group
-  (fn [state [ _ {:keys [group-id user-id] :as args}]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/remove-from-group args])
-    state))
-
-(reg-event-db
+  (fn [cofx [ _ {:keys [group-id user-id] :as args}]]
+    {:websocket-send (list [:braid.server/remove-from-group args])}))
+
+(reg-event-fx
   :set-group-intro
-  (fn [state [_ {:keys [group-id intro local-only?] :as args}]]
-    ; TODO
-    (when-not local-only?
-      (sync/chsk-send! [:braid.server/set-group-intro args]))
-    (helpers/set-group-intro state group-id intro)))
-
-(reg-event-db
+  (fn [{state :db :as cofx} [_ {:keys [group-id intro local-only?] :as args}]]
+    (merge
+      {:db (helpers/set-group-intro state group-id intro)}
+      (when-not local-only?
+        {:websocket-send (list [:braid.server/set-group-intro args])}))))
+
+(reg-event-fx
   :set-group-avatar
-  (fn [state [_ {:keys [group-id avatar local-only?] :as args}]]
-    ; TODO
-    (when-not local-only?
-      (sync/chsk-send! [:braid.server/set-group-avatar args]))
-    (helpers/set-group-avatar state group-id avatar)))
-
-(reg-event-db
+  (fn [{state :db :as cofx} [_ {:keys [group-id avatar local-only?] :as args}]]
+    (merge
+      {:db (helpers/set-group-avatar state group-id avatar)}
+      (when-not local-only?
+        {:websocket-send (list [:braid.server/set-group-avatar args])}))))
+
+(reg-event-fx
   :make-group-public!
-  (fn [state [_ group-id]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/set-group-publicity [group-id true]])
-    state))
-
-(reg-event-db
+  (fn [cofx [_ group-id]]
+    {:websocket-send (list [:braid.server/set-group-publicity [group-id true]])}))
+
+(reg-event-fx
   :make-group-private!
-  (fn [state [_ group-id]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/set-group-publicity [group-id false]])
-    state))
-
-(reg-event-db
+  (fn [cofx [_ group-id]]
+    {:websocket-send (list [:braid.server/set-group-publicity [group-id false]])}))
+
+(reg-event-fx
   :new-bot
-  (fn [state [_ {:keys [bot on-complete]}]]
+  (fn [cofx [_ {:keys [bot on-complete]}]]
     (let [bot (schema/make-bot bot)]
-      ; TODO
-      (sync/chsk-send!
-        [:braid.server/create-bot bot]
-        5000
-        (fn [reply]
-          (when (nil? (:braid/ok reply))
-            (dispatch [:display-error
-                       [(str "bot-" (bot :id) (rand))
-                        (get reply :braid/error
-                          "Something when wrong creating bot")]]))
-          (on-complete (:braid/ok reply)))))
-    state))
-
-(reg-event-db
+      {:websocket-send
+       (list
+         [:braid.server/create-bot bot]
+         5000
+         (fn [reply]
+           (when (nil? (:braid/ok reply))
+             (dispatch [:display-error
+                        [(str "bot-" (bot :id) (rand))
+                         (get reply :braid/error
+                           "Something when wrong creating bot")]]))
+           (on-complete (:braid/ok reply))))})))
+
+(reg-event-fx
   :get-bot-info
-  (fn [state [_ {:keys [bot-id on-complete]}]]
-    ; TODO
-    (sync/chsk-send!
-      [:braid.server/get-bot-info bot-id]
-      2000
-      (fn [reply]
-        (when-let [bot (:braid/ok reply)]
-          (on-complete bot))))
-    state))
+  (fn [cofx [_ {:keys [bot-id on-complete]}]]
+    {:websocket-send
+     (list
+       [:braid.server/get-bot-info bot-id]
+       2000
+       (fn [reply]
+         (when-let [bot (:braid/ok reply)]
+           (on-complete bot))))}))
 
 (reg-event-fx
   :create-upload
   (fn [{state :db :as cofx} [_ {:keys [url thread-id group-id]}]]
-    ; TODO
-    (sync/chsk-send! [:braid.server/create-upload
-                      (schema/make-upload {:url url :thread-id thread-id})])
-    {:dispatch [:new-message {:content url :thread-id thread-id :group-id group-id}]}))
-
-(reg-event-db
+    {:websocket-send (list [:braid.server/create-upload
+                            (schema/make-upload {:url url :thread-id thread-id})])
+     :dispatch [:new-message {:content url :thread-id thread-id :group-id group-id}]}))
+
+(reg-event-fx
   :get-group-uploads
-  (fn [state [_ {:keys [group-id on-success on-error]}]]
-    ; TODO
-    (sync/chsk-send!
-      [:braid.server/uploads-in-group group-id]
-      5000
-      (fn [reply]
-        (if-let [uploads (:braid/ok reply)]
-          (on-success uploads)
-          (on-error (get reply :braid/error "Couldn't get uploads in group")))))
-    state))
+  (fn [cofx [_ {:keys [group-id on-success on-error]}]]
+    {:websocket-send
+     (list
+       [:braid.server/uploads-in-group group-id]
+       5000
+       (fn [reply]
+         (if-let [uploads (:braid/ok reply)]
+           (on-success uploads)
+           (on-error (get reply :braid/error "Couldn't get uploads in group")))))}))
 
 (reg-event-db
   :check-auth

  (fn [state [_ query]]
    (helpers/set-search-query state query)))

(reg-event-db
  :search-history
  (fn [state [_ [query group-id]]]
    (if query
      ; TODO
      (do (sync/chsk-send!
            [:braid.server/search [query group-id]]
            15000
            (fn [reply]
              (dispatch [:set-page-loading false])
              (if (:thread-ids reply)
                (dispatch [:set-search-results [query reply]])
                (dispatch [:set-page-error true]))))
          (helpers/set-page-error state false))
      state)))

(reg-event-db
  :add-threads
  (fn [state [_ threads]]
    (helpers/add-threads state threads)))

(reg-event-db
  :load-recent-threads
  (fn [state [_ {:keys [group-id on-error on-complete]}]]
    ; TODO
    (sync/chsk-send!
      [:braid.server/load-recent-threads group-id]
      5000
      (fn [reply]
        (if-let [threads (:braid/ok reply)]
          (dispatch [:add-threads threads])
          (cond
            (= reply :chsk/timeout) (on-error "Timed out")
            (:braid/error reply) (on-error (:braid/error reply))
            :else (on-error "Something went wrong")))
        (on-complete (some? (:braid/ok reply)))))
    state))

(reg-event-db
  :load-threads
  (fn [state [_ {:keys [thread-ids on-complete]}]]
    ; TODO
    (sync/chsk-send!
      [:braid.server/load-threads thread-ids]
      5000
      (fn [reply]
        (when-let [threads (:threads reply)]
          (dispatch [:add-threads threads]))
        (when on-complete
          (on-complete))))
    state))

(reg-event-db
  :set-channel-results

  (fn [state [_ err-key]]
    (helpers/clear-error state err-key)))

(reg-event-fx
  :resend-message
  (fn [{state :db :as cofx} [_ message]]
    {:websocket-send (list
                       [:braid.server/new-message message]
                       2000
                       (fn [reply]
                         (when (not= :braid/ok reply)
                           (dispatch [:display-error
                                      [(str :failed-to-send (message :id))
                                       "Message failed to send!"]])
                           (dispatch [:set-message-failed message]))))

     :db (-> state
             (helpers/clear-error (str :failed-to-send (message :id)))
             (helpers/clear-message-failed message))}))

(reg-event-fx
  :hide-thread
  (fn [{state :db :as cofx} [_ {:keys [thread-id local-only?]}]]
    (merge
      {:db (helpers/hide-thread state thread-id)}
      (when-not local-only?
        {:websocket-send (list [:braid.server/hide-thread thread-id])}))))

(reg-event-fx
  :reopen-thread
  (fn [{state :db :as cofx} [_ thread-id]]
    {:websocket-send (list [:braid.server/show-thread thread-id])
     :db (helpers/show-thread state thread-id)}))

(reg-event-fx
  :unsub-thread
  (fn [cofx [_ data]]
    {:websocket-send (list [:braid.server/unsub-thread (data :thread-id)])
     :dispatch [:hide-thread {:thread-id (data :thread-id) :local-only? true}]}))

(reg-event-fx
  :create-tag
  (fn [{state :db :as cofx} [_ {:keys [tag local-only?]}]]
    (let [tag (merge (schema/make-tag) tag)]
      (merge
        {:db (-> state
                 (helpers/add-tag tag)
                 (helpers/subscribe-to-tag (tag :id)))}
        (when-not local-only?
          {:websocket-send
           (list
             [:braid.server/create-tag tag]
             1000
             (fn [reply]
               (if-let [msg (:error reply)]
                 (do
                   (dispatch [:remove-tag {:tag-id (tag :id) :local-only? true}])
                   (dispatch [:display-error [(str :bad-tag (tag :id)) msg]])))))})))))

(reg-event-fx
  :unsubscribe-from-tag
  (fn [{state :db :as cofx} [_ tag-id]]
    {:websocket-send (list [:braid.server/unsubscribe-from-tag tag-id])
     :db (helpers/unsubscribe-from-tag state tag-id)}))

(reg-event-fx
  :subscribe-to-tag
  (fn [{state :db :as cofx} [_ {:keys [tag-id local-only?]}]]
    (merge
      {:db (helpers/subscribe-to-tag state tag-id)}
      (when-not local-only?
        {:websocket-send (list [:braid.server/subscribe-to-tag tag-id])}))))

(reg-event-fx
  :set-tag-description
  (fn [{state :db :as cofx} [_ {:keys [tag-id description local-only?]}]]
    (merge
      {:db (helpers/set-tag-description state tag-id description)}
      (when-not local-only?
        {:websocket-send
         (list
           [:braid.server/set-tag-description {:tag-id tag-id
                                               :description description}])}))))

(reg-event-fx
  :retract-tag
  (fn [{state :db :as cofx} [_ {:keys [tag-id local-only?]}]]
    (merge
      {:db (helpers/remove-tag state tag-id)}
      (when-not local-only?
        {:websocket-send (list [:braid.server/retract-tag tag-id])}))))

(reg-event-db
  :remove-group
  (fn [state [_ group-id]]
    (helpers/remove-group state group-id)))

(reg-event-fx
  :create-group
  (fn [{state :db :as cofx} [_ group]]
    (let [group (schema/make-group group)]
      {:websocket-send
       (list
         [:braid.server/create-group group]
         1000
         (fn [reply]
           (when-let [msg (reply :error)]
             (.error js/console msg)
             (dispatch [:display-error [(str :bad-group (group :id)) msg]])
             (dispatch [:remove-group (group :id)]))))
       :db (-> state
               (helpers/add-group group)
               (helpers/become-group-admin (:id group)))})))

(reg-event-db
  :update-user-nickname
  (fn [state [_ {:keys [nickname user-id]}]]
    (helpers/update-user-nickname state user-id nickname)))

(reg-event-fx
  :set-user-nickname
  (fn [{state :db :as cofx} [_ {:keys [nickname on-error]}]]
    {:websocket-send
     (list
       [:braid.server/set-nickname {:nickname nickname}]
       1000
       (fn [reply]
         (if-let [msg (reply :error)]
           (on-error msg)
           (dispatch [:update-user-nickname
                      {:nickname nickname
                       :user-id (helpers/current-user-id state)}]))))}))

(reg-event-fx
  :set-user-avatar
  (fn [{state :db :as cofx} [_ avatar-url]]
    {:websocket-send (list [:braid.server/set-user-avatar avatar-url])
     :db (helpers/update-user-avatar
           state (helpers/current-user-id state) avatar-url)}))

(reg-event-db
  :update-user-avatar
  (fn [state [_ {:keys [user-id avatar-url]}]]
    (helpers/update-user-avatar state user-id avatar-url)))

(reg-event-fx
  :set-password
  (fn [cofx [_ [password on-success on-error]]]
    {:websocket-send
     (list
       [:braid.server/set-password {:password password}]
       3000
       (fn [reply]
         (cond
           (reply :error) (on-error reply)
           (= reply :chsk/timeout)
           (on-error
             {:error "Couldn't connect to server, please try again"})
           true (on-success))))}))

(reg-event-fx
  :add-notification-rule
  (fn [{state :db :as cofx} [_ rule]]
    (let [current-rules (get (helpers/get-user-preferences state)
                          :notification-rules [])]
      {:dispatch [:set-preference [:notification-rules (conj current-rules rule)]]})))

(reg-event-fx
  :remove-notification-rule
  (fn [{state :db :as cofx} [_ rule]]
    (let [new-rules (into [] (remove (partial = rule))
                          (get (helpers/get-user-preferences state)
                            :notification-rules []))]
      {:dispatch [:set-preference [:notification-rules new-rules]]})))

(reg-event-db
