(ns ajet.chat.web.components
  "Hiccup UI components for the chat interface.

   All components return Hiccup data structures (vectors).
   Components are used both in full page renders (layout.clj)
   and as SSE fragments (sse.clj).

   Color palette: Catppuccin Mocha dark theme."
  (:require [hiccup2.core :as h]
            [ajet.chat.shared.markdown :as markdown]
            [ajet.chat.shared.mentions :as mentions]
            [clojure.string :as str]))

(declare channel-item dm-item)

;;; ---------------------------------------------------------------------------
;;; Helpers
;;; ---------------------------------------------------------------------------

(defn- format-timestamp
  "Format an ISO timestamp or inst for display."
  [ts]
  (when ts
    (let [s (str ts)]
      (if (> (count s) 16)
        (subs s 11 16)
        s))))

(defn- user-initials
  "Get initials from a display name or username."
  [name-str]
  (when name-str
    (let [parts (str/split (str/trim name-str) #"\s+")]
      (str/upper-case
        (apply str (map #(first %) (take 2 parts)))))))

(defn- avatar-color
  "Deterministic color from a string (for initials avatars)."
  [id-str]
  (let [colors ["#89b4fa" "#a6e3a1" "#f38ba8" "#fab387" "#cba6f7"
                "#f9e2af" "#94e2d5" "#74c7ec" "#f5c2e7" "#b4befe"]
        idx    (mod (Math/abs (.hashCode (str id-str))) (count colors))]
    (nth colors idx)))

(defn- community-initial
  "Get display initial(s) for a community."
  [community]
  (let [n (:name community "?")]
    (str/upper-case (subs n 0 (min 2 (count n))))))

;;; ---------------------------------------------------------------------------
;;; Community Strip (Pane 1)
;;; ---------------------------------------------------------------------------

(defn community-strip
  "Vertical strip of community icons, similar to Discord's server list."
  [{:keys [communities active-id unread-count]}]
  (list
    ;; Home / DMs button
    [:div {:class "w-12 h-12 rounded-2xl bg-surface0 flex items-center justify-center cursor-pointer hover:rounded-xl hover:bg-blue transition-all duration-200 mb-2 relative"
           "data-on:click" "$navTarget = 'dms'; $navCommunity = ''; $navChannel = ''; @post('/web/navigate')"}
     [:svg {:class "w-6 h-6 text-text" :viewBox "0 0 24 24" :fill "currentColor"}
      [:path {:d "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z"}]]
     (when (and unread-count (pos? unread-count))
       [:span {:id "notification-badge"
               :class "absolute -top-1 -right-1 bg-red text-base text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center"}
        (if (> unread-count 99) "99+" (str unread-count))])]

    ;; Separator
    [:div {:class "w-8 h-0.5 bg-surface1 rounded-full mb-2"}]

    ;; Community icons
    (for [comm communities]
      (let [cid    (str (:id comm))
            active (= cid active-id)]
        [:div {:key   cid
               :class (str "w-12 h-12 rounded-2xl flex items-center justify-center cursor-pointer "
                           "transition-all duration-200 text-sm font-bold "
                           (if active
                             "rounded-xl bg-blue text-base"
                             "bg-surface0 text-subtext0 hover:rounded-xl hover:bg-blue hover:text-base"))
               :title (:name comm)
               "data-on:click" (str "$navCommunity = '" cid "'; $navChannel = ''; $navTarget = ''; @post('/web/navigate')")}
         (community-initial comm)]))

    ;; Add community button
    [:div {:class "w-12 h-12 rounded-2xl bg-surface0 flex items-center justify-center cursor-pointer hover:rounded-xl hover:bg-green transition-all duration-200 mt-2"
           :title "Create Community"
           "data-on:click" "window.location.href='/setup'"}
     [:span {:class "text-green text-2xl font-light"} "+"]]))

;;; ---------------------------------------------------------------------------
;;; Sidebar (Pane 2)
;;; ---------------------------------------------------------------------------

(defn sidebar
  "Channel list with categories and user info at bottom."
  [{:keys [community channels channel categories user]}]
  (let [active-channel-id (when channel (str (:id channel)))
        categorized       (group-by :category-id channels)
        uncategorized     (get categorized nil [])
        sorted-categories (sort-by :position categories)]
    (list
      ;; Community name header
      [:div {:class "h-12 px-4 flex items-center border-b border-surface1 flex-shrink-0"}
       [:h2 {:class "font-semibold text-text truncate text-sm"}
        (or (:name community) "ajet chat")]
       [:button {:class "ml-auto text-overlay0 hover:text-text"
                 :title "Community Settings"}
        [:svg {:class "w-4 h-4" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
         [:circle {:cx "12" :cy "12" :r "3"}]
         [:path {:d "M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"}]]]]

      ;; Channel list
      [:div {:id "channel-list" :class "flex-1 overflow-y-auto px-2 py-2"}

       ;; Uncategorized channels
       (when (seq uncategorized)
         (for [ch uncategorized]
           (channel-item ch active-channel-id)))

       ;; Categorized channels
       (for [cat sorted-categories]
         (let [cat-channels (get categorized (str (:id cat)) [])]
           (when (seq cat-channels)
             [:div {:key (str (:id cat)) :class "mb-2"}
              [:div {:class "flex items-center px-1 py-1 text-xs font-semibold text-overlay0 uppercase tracking-wide cursor-pointer hover:text-subtext0"}
               [:svg {:class "w-3 h-3 mr-1" :viewBox "0 0 24 24" :fill "currentColor"}
                [:path {:d "M7 10l5 5 5-5z"}]]
               (:name cat)]
              (for [ch cat-channels]
                (channel-item ch active-channel-id))])))]

      ;; User info bar at bottom
      [:div {:id "user-bar"
             :class "h-14 px-3 flex items-center bg-mantle border-t border-surface1 flex-shrink-0"}
       [:div {:class "w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0"
              :style (str "background-color: " (avatar-color (:id user)))}
        (user-initials (or (:display-name user) (:username user)))]
       [:div {:class "ml-2 min-w-0"}
        [:div {:class "text-sm font-medium text-text truncate"}
         (or (:display-name user) (:username user))]
        [:div {:class "text-xs text-overlay0 truncate"}
         (or (:status-text user) "Online")]]])))

(defn dm-sidebar
  "DM list sidebar — shown when the Home/DM icon is active in the community strip."
  [{:keys [dms active-dm user]}]
  (let [active-dm-id (when active-dm (str (:id active-dm)))]
    (list
      ;; Header
      [:div {:class "h-12 px-4 flex items-center border-b border-surface1 flex-shrink-0"}
       [:h2 {:class "font-semibold text-text truncate text-sm"} "Direct Messages"]
       [:button {:class "ml-auto text-overlay0 hover:text-text"
                 :title "New DM"}
        [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
         [:path {:d "M12 20h9M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"}]]]]

      ;; DM list
      [:div {:id "dm-list" :class "flex-1 overflow-y-auto px-2 py-2"}
       (if (seq dms)
         (for [dm dms]
           (dm-item dm active-dm-id))
         [:div {:class "text-sm text-overlay0 text-center py-8"}
          "No conversations yet"])]

      ;; User info bar at bottom
      [:div {:id "user-bar"
             :class "h-14 px-3 flex items-center bg-mantle border-t border-surface1 flex-shrink-0"}
       [:div {:class "w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0"
              :style (str "background-color: " (avatar-color (:id user)))}
        (user-initials (or (:display-name user) (:username user)))]
       [:div {:class "ml-2 min-w-0"}
        [:div {:class "text-sm font-medium text-text truncate"}
         (or (:display-name user) (:username user))]
        [:div {:class "text-xs text-overlay0 truncate"}
         (or (:status-text user) "Online")]]])))

(defn- channel-item
  "Single channel entry in the sidebar."
  [ch active-channel-id]
  (let [cid     (str (:id ch))
        active  (= cid active-channel-id)
        ch-type (or (:type ch) "text")
        prefix  (if (= ch-type "voice") "\uD83D\uDD0A" "#")]
    [:div {:key   cid
           :id    (str "sidebar-channel-" cid)
           :class (str "flex items-center px-2 py-1 rounded cursor-pointer text-sm "
                       (if active
                         "bg-surface0 text-text"
                         "text-overlay1 hover:text-subtext1 hover:bg-hover"))
           "data-on:click" (str "$navChannel = '" cid "'; $navCommunity = ''; $navTarget = ''; @post('/web/navigate')")}
     [:span {:class "mr-1.5 text-overlay0 text-xs"} prefix]
     [:span {:class "truncate"} (:name ch)]
     ;; Unread badge placeholder
     [:span {:id    (str "unread-badge-" cid)
             :class "ml-auto hidden bg-red text-base text-xs font-bold rounded-full px-1.5 min-w-[1.25rem] text-center"}]]))

(defn- dm-item
  "Single DM entry in the sidebar."
  [dm active-channel-id]
  (let [dm-id  (str (:id dm))
        active (= dm-id active-channel-id)
        name   (or (:display-name dm) (:name dm) "Direct Message")]
    [:div {:key   dm-id
           :id    (str "sidebar-dm-" dm-id)
           :class (str "flex items-center px-2 py-1 rounded cursor-pointer text-sm "
                       (if active
                         "bg-surface0 text-text"
                         "text-overlay1 hover:text-subtext1 hover:bg-hover"))
           "data-on:click" (str "$navChannel = '" dm-id "'; $navCommunity = ''; $navTarget = ''; @post('/web/navigate')")}
     [:div {:class "w-6 h-6 rounded-full flex items-center justify-center text-xs font-bold mr-2 flex-shrink-0"
            :style (str "background-color: " (avatar-color dm-id))}
      (user-initials name)]
     [:span {:class "truncate"} name]
     ;; Online status dot
     [:span {:id    (str "presence-" dm-id)
             :class "ml-auto w-2 h-2 rounded-full bg-green hidden"}]]))

;;; ---------------------------------------------------------------------------
;;; Channel Header
;;; ---------------------------------------------------------------------------

(defn channel-header
  "Header bar above the message list showing channel name, topic, and actions."
  [channel]
  (let [ch-name (or (:name channel) "general")
        topic   (:topic channel)]
    [:div {:class "h-12 px-4 flex items-center border-b border-surface1 flex-shrink-0"}
     [:span {:class "text-overlay0 mr-1"} "#"]
     [:span {:class "font-semibold text-text"} ch-name]
     (when topic
       [:span {:class "ml-3 text-sm text-overlay0 truncate"} topic])
     [:div {:class "ml-auto flex items-center gap-3"}
      ;; Search button
      [:button {:class "text-overlay0 hover:text-text"
                :title "Search"
                "data-on:click" "$searchOpen = !$searchOpen"}
       [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
        [:circle {:cx "11" :cy "11" :r "8"}]
        [:line {:x1 "21" :y1 "21" :x2 "16.65" :y2 "16.65"}]]]
      ;; Members / settings
      [:button {:class "text-overlay0 hover:text-text"
                :title "Channel Settings"}
       [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
        [:path {:d "M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4-4v2"}]
        [:circle {:cx "9" :cy "7" :r "4"}]
        [:path {:d "M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"}]]]]]))

;;; ---------------------------------------------------------------------------
;;; Messages
;;; ---------------------------------------------------------------------------

(defn message-component
  "Single message with avatar, username, timestamp, rendered markdown body,
   reactions, and thread indicator."
  [msg current-user]
  (let [msg-id      (str (:id msg))
        user-id     (str (:user-id msg))
        username    (or (:display-name msg) (:username msg) "unknown")
        timestamp   (format-timestamp (or (:created-at msg) (:created_at msg)))
        body        (:body-md msg (:body_md msg ""))
        edited      (or (:edited-at msg) (:edited_at msg))
        reactions   (or (:reactions msg) [])
        thread-count (:thread-count msg (:thread_count msg 0))
        is-own      (= user-id (str (:id current-user)))
        ;; Render markdown to HTML, resolving mentions with a simple fallback
        rendered-body (-> body
                         (mentions/render (fn [type id]
                                            (case type
                                              :user  (or (:username (get (:mentions-cache msg) id)) id)
                                              :channel (or (:name (get (:channels-cache msg) id)) id)
                                              nil)))
                         markdown/->html)]
    [:div {:id    (str "msg-" msg-id)
           :class "group flex px-4 py-1 hover:bg-hover rounded transition-colors relative"
           :data-msg-id msg-id}
     ;; Avatar
     [:div {:class "w-10 h-10 rounded-full flex-shrink-0 mr-3 flex items-center justify-center text-sm font-bold mt-0.5"
            :style (str "background-color: " (avatar-color user-id))}
      (user-initials username)]

     ;; Content
     [:div {:class "flex-1 min-w-0"}
      ;; Name + timestamp row
      [:div {:class "flex items-baseline gap-2"}
       [:span {:class "font-medium text-sm text-text"} username]
       [:span {:class "text-xs text-overlay0"} timestamp]
       (when edited
         [:span {:class "text-xs text-overlay0 italic"} "(edited)"])]

      ;; Body
      [:div {:class "text-sm text-subtext1 mt-0.5 break-words message-body"}
       (h/raw rendered-body)]

      ;; Attachments
      (when-let [attachments (seq (:attachments msg))]
        [:div {:class "mt-2 flex flex-wrap gap-2"}
         (for [att attachments]
           [:img {:key (str (:id att))
                  :src (or (:url att) (:storage-key att))
                  :alt (:filename att)
                  :class "max-w-xs max-h-60 rounded-lg border border-surface1"}])])

      ;; Reactions bar
      (when (seq reactions)
        [:div {:id    (str "reactions-" msg-id)
               :class "flex flex-wrap gap-1 mt-1"}
         (for [r reactions]
           [:button {:key   (str (:emoji r) "-" (count (:users r)))
                     :class "flex items-center gap-1 px-2 py-0.5 rounded-full text-xs bg-surface0 border border-surface1 hover:border-blue transition-colors"
                     "data-on:click" (str "@post('/web/reactions', {headers: {'X-Message-Id': '" msg-id "', 'X-Emoji': '" (:emoji r) "'}})")}
            [:span (:emoji r)]
            [:span {:class "text-subtext0"} (count (:users r))]])])

      ;; Thread indicator
      (when (and thread-count (pos? thread-count))
        [:button {:class "flex items-center gap-1 text-xs text-blue hover:underline mt-1"
                  "data-on:click" (str "$threadOpen = true; $threadMessageId = '" msg-id "'")}
         [:span (str thread-count " " (if (= 1 thread-count) "reply" "replies"))]])]

     ;; Hover action toolbar
     [:div {:class "absolute right-2 -top-3 hidden group-hover:flex bg-surface0 rounded border border-surface1 shadow-lg"}
      ;; Add reaction
      [:button {:class "p-1.5 hover:bg-hover rounded-l text-overlay0 hover:text-text"
                :title "Add Reaction"
                "data-on:click" (str "$emojiOpen = !$emojiOpen; $threadMessageId = '" msg-id "'")}
       "\uD83D\uDE00"]
      ;; Reply in thread
      [:button {:class "p-1.5 hover:bg-hover text-overlay0 hover:text-text"
                :title "Reply in Thread"
                "data-on:click" (str "$threadOpen = true; $threadMessageId = '" msg-id "'")}
       "\uD83D\uDCAC"]
      ;; Edit (own messages only)
      (when is-own
        [:button {:class "p-1.5 hover:bg-hover text-overlay0 hover:text-text"
                  :title "Edit"
                  "data-on:click" (str "let el = document.querySelector('#msg-" msg-id " .message-body');"
                                      "el.contentEditable = 'true'; el.focus();")}
         "\u270F\uFE0F"])
      ;; Delete (own messages only)
      (when is-own
        [:button {:class "p-1.5 hover:bg-hover rounded-r text-overlay0 hover:text-red"
                  :title "Delete"
                  "data-on:click" (str "if(confirm('Delete this message?')) @post('/web/messages/" msg-id "/delete')")}
         "\uD83D\uDDD1\uFE0F"])]]))

(defn message-list
  "Scrollable list of messages with a 'Load older' trigger at top."
  [messages current-user]
  (list
    ;; Load older sentinel — only show when a full page was returned (may have more)
    [:div {:id "load-older-sentinel" :class "flex justify-center py-2"}
     (if (>= (count messages) 50)
       [:button {:class "text-xs text-overlay0 hover:text-subtext0 px-3 py-1 rounded bg-surface0 hover:bg-hover transition-colors"
                 "data-on:click" "@post('/web/messages/older')"}
        "Load older messages"]
       [:span {:class "text-xs text-overlay0"} "Beginning of conversation"])]

    ;; Messages
    [:div {:id "messages-container"}
     (for [msg messages]
       (message-component msg current-user))]

    ;; Typing indicator
    [:div {:id "typing-indicator" :class "px-4 py-1 text-xs text-overlay0 h-6"}]))

;;; ---------------------------------------------------------------------------
;;; Message Input
;;; ---------------------------------------------------------------------------

(defn message-input
  "Message composition area with textarea and send button."
  [channel]
  (let [channel-id (when channel (str (:id channel)))
        ch-name    (or (:name channel) "general")]
    [:div {:class "bg-surface0 rounded-lg border border-surface1"}
     [:div {:class "flex items-end"}
      ;; Upload button
      [:button {:class "p-3 text-overlay0 hover:text-text flex-shrink-0"
                :title "Upload Image"
                "data-on:click" "document.getElementById('file-upload-input').click()"}
       [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
        [:path {:d "M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"}]]]
      ;; Hidden file input
      [:input {:type "file"
               :id "file-upload-input"
               :class "hidden"
               :accept "image/jpeg,image/png,image/gif,image/webp"
               "data-on:change" (str "@post('/web/upload', {headers: {'X-Channel-Id': '" (or channel-id "") "'}})")}]
      ;; Textarea
      [:textarea {:id "message-textarea"
                  :class "flex-1 bg-transparent text-text placeholder-overlay0 px-1 py-3 resize-none outline-none text-sm max-h-40"
                  :placeholder (str "Message #" ch-name)
                  :rows "1"
                  :data-bind "messageText"
                  "data-on:keydown" (str "if(evt.key === 'Enter' && !evt.shiftKey) {"
                                        " evt.preventDefault();"
                                        " if($messageText.trim()) {"
                                        "  @post('/web/messages');"
                                        "  $messageText = '';"
                                        " }"
                                        "}")
                  "data-on:input" (str "@post('/web/typing', {headers: {'X-Channel-Id': '" (or channel-id "") "'}})")}]
      ;; Send button
      [:button {:class "p-3 text-blue hover:text-text flex-shrink-0"
                :title "Send"
                "data-on:click" (str "if($messageText.trim()) {"
                                    " @post('/web/messages');"
                                    " $messageText = '';"
                                    "}")}
       [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "currentColor"}
        [:path {:d "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"}]]]]]))

;;; ---------------------------------------------------------------------------
;;; Thread Panel
;;; ---------------------------------------------------------------------------

(defn thread-panel
  "Thread view with root message and replies."
  [root-msg replies current-user]
  [:div {:class "flex flex-col h-full"}
   ;; Thread header
   [:div {:class "h-12 px-4 flex items-center border-b border-surface1 flex-shrink-0"}
    [:span {:class "font-semibold text-text text-sm"} "Thread"]
    [:button {:class "ml-auto text-overlay0 hover:text-text"
              "data-on:click" "$threadOpen = false"}
     [:svg {:class "w-5 h-5" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
      [:line {:x1 "18" :y1 "6" :x2 "6" :y2 "18"}]
      [:line {:x1 "6" :y1 "6" :x2 "18" :y2 "18"}]]]]

   ;; Root message
   (when root-msg
     [:div {:class "border-b border-surface1 px-2 py-2"}
      (message-component root-msg current-user)])

   ;; Replies
   [:div {:class "flex-1 overflow-y-auto px-2 py-2"}
    (for [reply replies]
      (message-component reply current-user))]

   ;; Thread reply input
   [:div {:class "px-3 pb-3"}
    [:div {:class "bg-surface0 rounded-lg border border-surface1 flex items-end"}
     [:textarea {:class "flex-1 bg-transparent text-text placeholder-overlay0 px-3 py-2 resize-none outline-none text-sm max-h-24"
                 :placeholder "Reply in thread..."
                 :rows "1"
                 :data-bind "threadReply"
                 "data-signals:threadReply" ""
                 "data-on:keydown" (str "if(evt.key === 'Enter' && !evt.shiftKey) {"
                                       " evt.preventDefault();"
                                       " if($threadReply.trim()) {"
                                       "  @post('/web/messages', {headers: {'X-Parent-Id': $threadMessageId}});"
                                       " }"
                                       "}")}]
     [:button {:class "p-2 text-blue hover:text-text flex-shrink-0"
               "data-on:click" (str "if($threadReply.trim()) {"
                                   " @post('/web/messages', {headers: {'X-Parent-Id': $threadMessageId}});"
                                   "}")}
      [:svg {:class "w-4 h-4" :viewBox "0 0 24 24" :fill "currentColor"}
       [:path {:d "M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"}]]]]]])

;;; ---------------------------------------------------------------------------
;;; Notification Toast
;;; ---------------------------------------------------------------------------

(defn notification-toast
  "Toast popup for notifications."
  [{:keys [id title body type]}]
  (let [color (case type
                :mention "#f38ba8"
                :dm      "#89b4fa"
                :thread  "#cba6f7"
                "#89b4fa")]
    [:div {:id    (str "toast-" (or id (random-uuid)))
           :class "toast-enter bg-surface0 border border-surface1 rounded-lg shadow-xl p-3 max-w-sm min-w-[280px]"
           :style (str "border-left: 3px solid " color)
           "data-on:click" "this.remove()"}
     [:div {:class "flex items-start gap-2"}
      [:div {:class "flex-1 min-w-0"}
       [:div {:class "text-sm font-medium text-text truncate"} (or title "Notification")]
       [:div {:class "text-xs text-subtext0 mt-0.5 truncate"} (or body "")]]
      [:button {:class "text-overlay0 hover:text-text flex-shrink-0 text-sm"
                "data-on:click" "this.parentElement.parentElement.remove()"}
       "\u2715"]]]))

;;; ---------------------------------------------------------------------------
;;; Emoji Picker
;;; ---------------------------------------------------------------------------

(def ^:private emoji-grid
  "Common emoji organized for quick picking."
  ["\uD83D\uDE04" "\uD83D\uDE02" "\uD83D\uDE0D" "\uD83E\uDD14" "\uD83D\uDC4D"
   "\uD83D\uDC4E" "\u2764\uFE0F" "\uD83D\uDD25" "\uD83D\uDE80" "\uD83C\uDF89"
   "\uD83D\uDCAF" "\u2705" "\u274C" "\uD83D\uDC40" "\uD83D\uDC4B"
   "\uD83D\uDCAA" "\uD83D\uDE4F" "\uD83D\uDC4F" "\u2728" "\u2B50"
   "\uD83D\uDE09" "\uD83D\uDE0E" "\uD83D\uDE2D" "\uD83D\uDCA9" "\uD83C\uDF7A"
   "\u2615" "\uD83C\uDF55" "\uD83D\uDC1B" "\uD83D\uDD27" "\uD83D\uDCA1"])

(defn emoji-picker
  "Server-rendered emoji grid for adding reactions."
  []
  [:div {:class "bg-surface0 border border-surface1 rounded-lg shadow-xl p-3 w-72"}
   [:div {:class "text-xs font-semibold text-overlay0 uppercase mb-2"} "Pick an Emoji"]
   [:div {:class "grid grid-cols-6 gap-1"}
    (for [emoji emoji-grid]
      [:button {:key   emoji
                :class "emoji-btn text-xl w-10 h-10 flex items-center justify-center rounded hover:bg-hover"
                "data-on:click" (str "@post('/web/reactions', {headers: {'X-Message-Id': $threadMessageId, 'X-Emoji': '" emoji "'}});"
                                    " $emojiOpen = false")}
       emoji])]
   [:button {:class "mt-2 w-full text-xs text-overlay0 hover:text-subtext0 py-1"
             "data-on:click" "$emojiOpen = false"}
    "Close"]])

;;; ---------------------------------------------------------------------------
;;; Search Modal
;;; ---------------------------------------------------------------------------

(defn search-modal
  "Full-screen search overlay with input and results."
  []
  [:div {:class "flex items-start justify-center pt-20"}
   ;; Backdrop
   [:div {:class "absolute inset-0 bg-black bg-opacity-50"
          "data-on:click" "$searchOpen = false"}]
   ;; Modal
   [:div {:class "relative bg-mantle border border-surface1 rounded-xl shadow-2xl w-full max-w-2xl z-10"}
    ;; Search input
    [:div {:class "flex items-center border-b border-surface1 px-4"}
     [:svg {:class "w-5 h-5 text-overlay0 mr-3" :viewBox "0 0 24 24" :fill "none" :stroke "currentColor" :stroke-width "2"}
      [:circle {:cx "11" :cy "11" :r "8"}]
      [:line {:x1 "21" :y1 "21" :x2 "16.65" :y2 "16.65"}]]
     [:input {:type "text"
              :class "flex-1 bg-transparent text-text placeholder-overlay0 py-3 outline-none text-sm"
              :placeholder "Search messages..."
              :data-bind "searchQuery"
              "data-on:keydown" "if(evt.key === 'Enter') @post('/web/search')"
              :autofocus true}]
     [:button {:class "text-overlay0 hover:text-text ml-2"
               "data-on:click" "$searchOpen = false"}
      [:span {:class "text-xs border border-surface1 rounded px-1.5 py-0.5"} "ESC"]]]
    ;; Search results container
    [:div {:id "search-results" :class "max-h-96 overflow-y-auto p-4"}
     [:div {:class "text-sm text-overlay0 text-center py-8"} "Type a query and press Enter to search."]]]])

(defn search-results
  "Rendered search results to replace #search-results content."
  [results]
  (if (seq results)
    [:div
     [:div {:class "text-xs text-overlay0 mb-3"} (str (count results) " result(s) found")]
     (for [msg results]
       (let [msg-id   (str (:id msg))
             username (or (:display-name msg) (:username msg) "unknown")
             body     (or (:body-md msg) (:body_md msg) "")
             ts       (format-timestamp (or (:created-at msg) (:created_at msg)))]
         [:div {:key   msg-id
                :class "flex items-start gap-3 p-2 rounded hover:bg-hover cursor-pointer"
                "data-on:click" (str "$searchOpen = false;"
                                    " document.getElementById('msg-" msg-id "')?.scrollIntoView({behavior: 'smooth'})")}
          [:div {:class "w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0"
                 :style (str "background-color: " (avatar-color (:user-id msg)))}
           (user-initials username)]
          [:div {:class "flex-1 min-w-0"}
           [:div {:class "flex items-baseline gap-2"}
            [:span {:class "text-sm font-medium text-text"} username]
            [:span {:class "text-xs text-overlay0"} ts]]
           [:div {:class "text-sm text-subtext0 truncate"} body]]]))]
    [:div {:class "text-sm text-overlay0 text-center py-8"} "No results found."]))
