<commit_before>(ns braid.core.client.ui.views.thread
  (:require
   [clojure.set :refer [difference]]
   [clojure.string :as string]
   [cljsjs.resize-observer-polyfill]
   [re-frame.core :refer [dispatch subscribe]]
   [reagent.core :as r]
   [braid.core.client.helpers :as helpers]
   [braid.core.client.routes :as routes]
   [braid.core.client.ui.views.card-border :refer [card-border-view]]
   [braid.core.client.ui.views.message :refer [message-view]]
   [braid.core.client.ui.views.new-message :refer [new-message-view]]
   [braid.core.client.ui.views.mentions :refer [user-mention-view tag-mention-view]]
   [braid.core.hooks :as hooks])
  (:import
   (goog.events KeyCodes)))

(def max-file-size (* 10 1024 1024))

(defn tag-option-view [tag thread-id close-list!]
  [:div.tag-option
   {:on-click (fn []
                (close-list!)
                (dispatch [:add-tag-to-thread {:thread-id thread-id
                                               :tag-id (tag :id)}]))}
   [:div.rect {:style {:background (helpers/->color (tag :id))}}]
   [:span {:style {:color (helpers/->color (tag :id))}}
    "#" (tag :name)]])

(defn add-tag-list-view [thread close-list!]
  (let [group-tags (subscribe [:open-group-tags])]
    (fn [thread close-list!]
      (let [thread-tag-ids (set (thread :tag-ids))
            tags (->> @group-tags
                      (remove (fn [tag]
                                (contains? thread-tag-ids (tag :id))))
                      (sort-by :name))]
        [:div.tag-list
         (if (seq tags)
           (doall
             (for [tag tags]
               ^{:key (tag :id)}
               [tag-option-view tag (thread :id) close-list!]))
           [:div.name "All tags used already."])]))))

(defn add-tag-button-view [thread]
  (let [show-list? (r/atom false)
        close-list! (fn []
                      (reset! show-list? false))]
    (fn [thread]
      [:div.add
       [:span.pill {:on-click (fn [] (swap! show-list? not))}
        (if @show-list? "×" "+")]
       (when @show-list?
         [add-tag-list-view thread close-list!])])))

(defn thread-tags-view [thread]
  [:div.tags
   (doall
     (for [user-id (thread :mentioned-ids)]
       ^{:key user-id}
       [user-mention-view user-id]))
   (doall
     (for [tag-id (thread :tag-ids)]
       ^{:key tag-id}
       [tag-mention-view tag-id]))
   [add-tag-button-view thread]])

(defn messages-view [thread-id]
  ; Closing over thread-id, but the only time a thread's id changes is the new
  ; thread box, which doesn't have messages anyway
  (let [messages (subscribe [:messages-for-thread thread-id])

        last-open-at (subscribe [:thread-last-open-at thread-id])

        unseen? (fn [message] (> (:created-at message) @last-open-at))

        messages-node (atom nil)
        ref-cb (fn [node] (reset! messages-node node))

        at-bottom? (atom true)
        first-scroll? (atom true)

        old-last-msg (atom nil)

        check-at-bottom!
        (fn []
          (let [node @messages-node]
            (reset! at-bottom?
                    (> 25
                       (- (.-scrollHeight node)
                          (.-scrollTop node)
                          (.-clientHeight node))))))

        scroll-to-bottom!
        (fn [component]
          (when-let [node @messages-node]
            (when @at-bottom?
              (set! (.-scrollTop node)
                    (- (.-scrollHeight node)
                       (.-clientHeight node))))))]

    (r/create-class
      {:display-name "thread"

       :component-did-mount
       (fn [c]
         (reset! at-bottom? true)
         (scroll-to-bottom! c)
         (reset! old-last-msg (:id (last @messages)))

         (let [resize-observer
               (js/ResizeObserver. (fn [entries]
                                     (scroll-to-bottom! c)))]
           (.. resize-observer (observe @messages-node))))

       :component-did-update
       (fn [c _]
         (let [last-msg (last @messages)]
           (when (and (not= @old-last-msg (:id last-msg))
                      (= (:user-id last-msg) @(subscribe [:user-id])))
             (reset! at-bottom? true))
           (reset! old-last-msg (:id last-msg)))
         (scroll-to-bottom! c))

       :reagent-render
       (fn [thread-id]
         [:div.messages
          {:ref ref-cb
           :on-scroll (fn [_]
                        (if @first-scroll?
                          (reset! first-scroll? false)
                          (check-at-bottom!)))}
          (let [sorted-messages
                (->> @messages
                     (sort-by :created-at)
                     (cons nil)
                     (partition 2 1)
                     (map (fn [[prev-message message]]
                            (let [new-date?
                                  (or (nil? prev-message)
                                      (not= (helpers/format-date "yyyyMMdd" (:created-at message))
                                            (helpers/format-date "yyyyMMdd" (:created-at prev-message))))]
                            (assoc message
                              :unseen?
                              (unseen? message)
                              :first-unseen?
                              (and
                                (unseen? message)
                                (not (unseen? prev-message)))
                              :show-date-divider?
                              new-date?
                              :collapse?
                              (and
                                (not new-date?)
                                (= (:user-id message)
                                   (:user-id prev-message))
                                (> (* 2 60 1000) ; 2 minutes
                                   (- (:created-at message)
                                      (or (:created-at prev-message) 0)))
                                ; TODO should instead check if there was an embed triggered
                                (not (helpers/contains-urls? (prev-message :content)))))))))]
            (doall
              (for [message sorted-messages]
                 ^{:key (message :id)}
                [:<>
                 (when (message :show-date-divider?)
                   [:div.divider
                    [:div.date (helpers/format-date "yyyy-MM-dd" (message :created-at))]])
                 [message-view (assoc message :thread-id thread-id)]])))])})))

(def header-item-dataspec
  {:priority number?
   :view fn?})

(defonce thread-header-items
  (hooks/register! (atom []) [header-item-dataspec]))

(def thread-control-dataspec
  {:priority number?
   :view fn?})

(defonce thread-controls
  (hooks/register!
    (atom [{:view
            (fn [thread]
              [:div.control.mute
               {:title "Mute"<commit_msg>Fix react key error by working around reagent bug<commit_after>(ns braid.core.client.ui.views.thread
  (:require
   [clojure.set :refer [difference]]
   [clojure.string :as string]
   [cljsjs.resize-observer-polyfill]
   [re-frame.core :refer [dispatch subscribe]]
   [reagent.core :as r]
   [braid.core.client.helpers :as helpers]
   [braid.core.client.routes :as routes]
   [braid.core.client.ui.views.card-border :refer [card-border-view]]
   [braid.core.client.ui.views.message :refer [message-view]]
   [braid.core.client.ui.views.new-message :refer [new-message-view]]
   [braid.core.client.ui.views.mentions :refer [user-mention-view tag-mention-view]]
   [braid.core.hooks :as hooks])
  (:import
   (goog.events KeyCodes)))

(def max-file-size (* 10 1024 1024))

(defn tag-option-view [tag thread-id close-list!]
  [:div.tag-option
   {:on-click (fn []
                (close-list!)
                (dispatch [:add-tag-to-thread {:thread-id thread-id
                                               :tag-id (tag :id)}]))}
   [:div.rect {:style {:background (helpers/->color (tag :id))}}]
   [:span {:style {:color (helpers/->color (tag :id))}}
    "#" (tag :name)]])

(defn add-tag-list-view [thread close-list!]
  (let [group-tags (subscribe [:open-group-tags])]
    (fn [thread close-list!]
      (let [thread-tag-ids (set (thread :tag-ids))
            tags (->> @group-tags
                      (remove (fn [tag]
                                (contains? thread-tag-ids (tag :id))))
                      (sort-by :name))]
        [:div.tag-list
         (if (seq tags)
           (doall
             (for [tag tags]
               ^{:key (tag :id)}
               [tag-option-view tag (thread :id) close-list!]))
           [:div.name "All tags used already."])]))))

(defn add-tag-button-view [thread]
  (let [show-list? (r/atom false)
        close-list! (fn []
                      (reset! show-list? false))]
    (fn [thread]
      [:div.add
       [:span.pill {:on-click (fn [] (swap! show-list? not))}
        (if @show-list? "×" "+")]
       (when @show-list?
         [add-tag-list-view thread close-list!])])))

(defn thread-tags-view [thread]
  [:div.tags
   (doall
     (for [user-id (thread :mentioned-ids)]
       ^{:key user-id}
       [user-mention-view user-id]))
   (doall
     (for [tag-id (thread :tag-ids)]
       ^{:key tag-id}
       [tag-mention-view tag-id]))
   [add-tag-button-view thread]])

(defn messages-view [thread-id]
  ; Closing over thread-id, but the only time a thread's id changes is the new
  ; thread box, which doesn't have messages anyway
  (let [messages (subscribe [:messages-for-thread thread-id])

        last-open-at (subscribe [:thread-last-open-at thread-id])

        unseen? (fn [message] (> (:created-at message) @last-open-at))

        messages-node (atom nil)
        ref-cb (fn [node] (reset! messages-node node))

        at-bottom? (atom true)
        first-scroll? (atom true)

        old-last-msg (atom nil)

        check-at-bottom!
        (fn []
          (let [node @messages-node]
            (reset! at-bottom?
                    (> 25
                       (- (.-scrollHeight node)
                          (.-scrollTop node)
                          (.-clientHeight node))))))

        scroll-to-bottom!
        (fn [component]
          (when-let [node @messages-node]
            (when @at-bottom?
              (set! (.-scrollTop node)
                    (- (.-scrollHeight node)
                       (.-clientHeight node))))))]

    (r/create-class
      {:display-name "thread"

       :component-did-mount
       (fn [c]
         (reset! at-bottom? true)
         (scroll-to-bottom! c)
         (reset! old-last-msg (:id (last @messages)))

         (let [resize-observer
               (js/ResizeObserver. (fn [entries]
                                     (scroll-to-bottom! c)))]
           (.. resize-observer (observe @messages-node))))

       :component-did-update
       (fn [c _]
         (let [last-msg (last @messages)]
           (when (and (not= @old-last-msg (:id last-msg))
                      (= (:user-id last-msg) @(subscribe [:user-id])))
             (reset! at-bottom? true))
           (reset! old-last-msg (:id last-msg)))
         (scroll-to-bottom! c))

       :reagent-render
       (fn [thread-id]
         [:div.messages
          {:ref ref-cb
           :on-scroll (fn [_]
                        (if @first-scroll?
                          (reset! first-scroll? false)
                          (check-at-bottom!)))}
          (let [sorted-messages
                (->> @messages
                     (sort-by :created-at)
                     (cons nil)
                     (partition 2 1)
                     (map (fn [[prev-message message]]
                            (let [new-date?
                                  (or (nil? prev-message)
                                      (not= (helpers/format-date "yyyyMMdd" (:created-at message))
                                            (helpers/format-date "yyyyMMdd" (:created-at prev-message))))]
                            (assoc message
                              :unseen?
                              (unseen? message)
                              :first-unseen?
                              (and
                                (unseen? message)
                                (not (unseen? prev-message)))
                              :show-date-divider?
                              new-date?
                              :collapse?
                              (and
                                (not new-date?)
                                (= (:user-id message)
                                   (:user-id prev-message))
                                (> (* 2 60 1000) ; 2 minutes
                                   (- (:created-at message)
                                      (or (:created-at prev-message) 0)))
                                ; TODO should instead check if there was an embed triggered
                                (not (helpers/contains-urls? (prev-message :content)))))))))]
            (doall
              (for [message sorted-messages]
                ;; [BUG] Reagent 0.8.1 has a bug where metadata doesn't get
                ;; applied to fragments properly, so we need to set
                ;; the key in this way instead
                [:<> {:key (message :id)}
                 (when (message :show-date-divider?)
                   [:div.divider
                    [:div.date (helpers/format-date "yyyy-MM-dd" (message :created-at))]])
                 [message-view (assoc message :thread-id thread-id)]])))])})))

(def header-item-dataspec
  {:priority number?
   :view fn?})

(defonce thread-header-items
  (hooks/register! (atom []) [header-item-dataspec]))

(def thread-control-dataspec
  {:priority number?
   :view fn?})

(defonce thread-controls
  (hooks/register!
    (atom [{:view
            (fn [thread]
              [:div.control.mute
               {:title "Mute"
{'fraction_non_alphanumeric': 0.1461355271703832, 'fraction_numerical': 0.0035361189290611245, 'mean_word_length': 1.372945205479452, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '401815', 'n_tokens_mistral': 4225, 'n_tokens_neox': 4022, 'n_words': 938}