(ns devtools.async
  (:require-macros [devtools.oops :refer [oset ocall]])
  (:require [goog.async.nextTick :as next-tick]
            [goog.labs.userAgent.browser :as ua]
            [devtools.context :as context]))

(defn ^:dynamic available? []
  (exists? js/Promise))

(def ^:dynamic fixed-chrome-version-for-async "65.0.3321")

(defn ^:dynamic needed? []
  (not (and (ua/isChrome) (ua/isVersionOrHigher fixed-chrome-version-for-async))))

(defn ^:dynamic get-not-needed-message []
  (str "cljs-devtools: the :async feature is no longer needed since Chrome " fixed-chrome-version-for-async ", "
       "see https://github.com/binaryage/cljs-devtools/issues/20"))

(def ^:dynamic *installed* false)
(def ^:dynamic *original-set-immediate* nil)

; see http://stackoverflow.com/a/30741722/84283
(defn rethrow-outside-promise [e]
  (js/setTimeout #(throw e) 0))

(defn promise-based-set-immediate [callback]
  (-> (ocall js/Promise "resolve")
      (ocall "then" callback)
      (ocall "catch" rethrow-outside-promise))
  nil)

(defn install-async-set-immediate! []
  (set! *original-set-immediate* next-tick/setImmediate_)
  (set! next-tick/setImmediate_ promise-based-set-immediate))

(defn uninstall-async-set-immediate! []
  (set! next-tick/setImmediate_ *original-set-immediate*))

; -- installation -----------------------------------------------------------------------------------------------------------

(defn installed? []
  *installed*)

(defn install! []
  (when-not *installed*
    (set! *installed* true)
    (oset js/Error ["stackTraceLimit"] js/Infinity)
    (install-async-set-immediate!)
    (when-not (needed?)
      (.info (context/get-console) (get-not-needed-message)))
    true))

(defn uninstall! []
  (when *installed*
    (set! *installed* false)
    (uninstall-async-set-immediate!)))