;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns app.main.ui.components.dropdown-menu
  (:require
   [app.common.data :as d]
   [app.config :as cfg]
   [app.util.dom :as dom]
   [app.util.globals :as globals]
   [app.util.keyboard :as kbd]
   [app.util.object :as obj]
   [goog.events :as events]
   [goog.object :as gobj]
   [rumext.v2 :as mf])
  (:import goog.events.EventType))

(mf/defc dropdown-menu-item*
  {::mf/wrap-props false}
  [props]
  (let [props (-> (obj/clone props)
                  (obj/set! "role" "menuitem"))]
    [:> :li props]))

(mf/defc dropdown-menu'
  {::mf/wrap-props false}
  [props]
  (let [children   (gobj/get props "children")
        on-close   (gobj/get props "on-close")
        ref        (gobj/get props "container")
        ids        (gobj/get props "ids")
        list-class (gobj/get props "list-class")
        ids        (filter some? ids)
        on-click
        (fn [event]
          (let [target (dom/get-target event)

                ;; MacOS ctrl+click sends two events: context-menu and click.
                ;; In order to not have two handlings we ignore ctrl+click for this platform
                mac-ctrl-click? (and (cfg/check-platform? :macos) (kbd/ctrl? event))]
            (when (and (not mac-ctrl-click?)
                       (not (.-data-no-close ^js target)))
              (if ref
                (let [parent (mf/ref-val ref)]
                  (when-not (or (not parent) (.contains parent target))
                    (on-close)))
                (on-close)))))

        on-keyup
        (fn [event]
          (when (kbd/esc? event)
            (on-close)))

        on-key-down
        (fn [event]
          (let [first-id (dom/get-element (first ids))
                first-element (dom/get-element first-id)
                len (count ids)]

            (when (kbd/home? event)
              (when first-element
                (dom/focus! first-element)))

            (when (kbd/up-arrow? event)
              (let [actual-selected (dom/get-active)
                    actual-id (dom/get-attribute actual-selected "id")
                    actual-index (d/index-of ids actual-id)
                    previous-id (if (= 0 actual-index)
                                  (last ids)
                                  (get ids (- actual-index 1) (last ids)))]
                (dom/focus! (dom/get-element previous-id))))

            (when (kbd/down-arrow? event)
              (let [actual-selected (dom/get-active)
                    actual-id (dom/get-attribute actual-selected "id")
                    actual-index (d/index-of ids actual-id)
                    next-id (if (= (- len 1) actual-index)
                              (first ids)
                              (get ids (+ 1 actual-index) (first ids)))
                    node-item (dom/get-element next-id)]
                (dom/focus! node-item)))

            (when (kbd/tab? event)
              (on-close))))]

    (mf/with-effect []
      (let [keys [(events/listen globals/document EventType.CLICK on-click)
                  (events/listen globals/document EventType.CONTEXTMENU on-click)
                  (events/listen globals/document EventType.KEYUP on-keyup)
                  (events/listen globals/document EventType.KEYDOWN on-key-down)]]
        #(doseq [key keys]
           (events/unlistenByKey key))))

    [:ul {:class list-class :role "menu"} children]))

(mf/defc dropdown-menu
  {::mf/wrap-props false}
  [props]
  (assert (fn? (gobj/get props "on-close")) "missing `on-close` prop")
  (assert (boolean? (gobj/get props "show")) "missing `show` prop")

  (let [ids (obj/get props "ids")
        ids (d/nilv ids (->> (obj/get props "children")
                             (keep #(obj/get-in % ["props" "id"]))))]
    (when (gobj/get props "show")
      (mf/element
       dropdown-menu'
       (mf/spread-props props {:ids ids})))))
