;; 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.hooks.resize
  (:require
   [app.common.data.macros :as dm]
   [app.common.geom.point :as gpt]
   [app.common.logging :as log]
   [app.common.math :as mth]
   [app.main.ui.context :as ctx]
   [app.main.ui.hooks :as hooks]
   [app.util.dom :as dom]
   [app.util.storage :refer [storage]]
   [rumext.v2 :as mf]))

(log/set-level! :warn)

(def last-resize-type nil)

(defn set-resize-type! [type]
  (set! last-resize-type type))

(defn use-resize-hook
  ([key initial min-val max-val axis negate? resize-type]
   (use-resize-hook key initial min-val max-val axis negate? resize-type nil))

  ([key initial min-val max-val axis negate? resize-type on-change-size]
   (let [current-file-id (mf/use-ctx ctx/current-file-id)
         size-state (mf/use-state (or (get-in @storage [::saved-resize current-file-id key]) initial))
         parent-ref (mf/use-ref nil)

         dragging-ref (mf/use-ref false)
         start-size-ref (mf/use-ref nil)
         start-ref (mf/use-ref nil)

         on-pointer-down
         (mf/use-callback
          (mf/deps @size-state)
          (fn [event]
            (dom/capture-pointer event)
            (mf/set-ref-val! start-size-ref @size-state)
            (mf/set-ref-val! dragging-ref true)
            (mf/set-ref-val! start-ref (dom/get-client-position event))
            (set! last-resize-type resize-type)))

         on-lost-pointer-capture
         (mf/use-callback
          (fn [event]
            (dom/release-pointer event)
            (mf/set-ref-val! start-size-ref nil)
            (mf/set-ref-val! dragging-ref false)
            (mf/set-ref-val! start-ref nil)
            (set! last-resize-type nil)))

         on-pointer-move
         (mf/use-callback
          (mf/deps min-val max-val negate?)
          (fn [event]
            (when (mf/ref-val dragging-ref)
              (let [start (mf/ref-val start-ref)
                    pos (dom/get-client-position event)
                    delta (-> (gpt/to-vec start pos)
                              (cond-> negate? gpt/negate)
                              (get axis))
                    start-size (mf/ref-val start-size-ref)
                    new-size (-> (+ start-size delta) (max min-val) (min max-val))]
                (reset! size-state new-size)
                (swap! storage assoc-in [::saved-resize current-file-id key] new-size)
                (when on-change-size (on-change-size new-size))))))

         set-size
         (mf/use-callback
          (mf/deps on-change-size)
          (fn [new-size]
            (let [new-size (mth/clamp new-size min-val max-val)]
              (reset! size-state new-size)
              (swap! storage assoc-in [::saved-resize current-file-id key] new-size)
              (when on-change-size (on-change-size new-size)))))]

     (mf/use-effect
      (fn []
        (when on-change-size (on-change-size @size-state))))

     {:on-pointer-down on-pointer-down
      :on-lost-pointer-capture on-lost-pointer-capture
      :on-pointer-move on-pointer-move
      :parent-ref parent-ref
      :set-size set-size
      :size @size-state})))

(defn use-resize-observer
  [callback]

  (dm/assert!
   "expected a valid callback"
   (fn? callback))

  (let [prev-val-ref (mf/use-ref nil)
        observer-ref (mf/use-ref nil)
        callback     (hooks/use-ref-callback callback)

        ;; We use the ref as a callback when the dom node is ready (or change)
        node-ref
        (mf/use-fn
         (fn [^js node]
           (when (some? node)
             (let [^js observer (mf/ref-val observer-ref)
                   ^js prev-val (mf/ref-val prev-val-ref)]

               (when (and (not= prev-val node) (some? observer))
                 (log/debug :action "disconnect" :js/prev-val prev-val :js/node node)
                 (.disconnect observer)
                 (mf/set-ref-val! observer-ref nil))

               (when (and (not= prev-val node) (some? node))
                 (let [^js observer (js/ResizeObserver.
                                     #(callback last-resize-type (dom/get-client-size node)))]
                   (mf/set-ref-val! observer-ref observer)
                   (log/debug :action "observe"  :js/node node :js/observer observer)
                   (.observe observer node)
                   (callback last-resize-type (dom/get-client-size node)))))

             (mf/set-ref-val! prev-val-ref node))))]

    (mf/with-layout-effect []
      ;; On dismount we need to disconnect the current observer
      (fn []
        (when-let [observer (mf/ref-val observer-ref)]
          (log/debug :action "disconnect")
          (.disconnect ^js observer))))

    node-ref))
