 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (def ^{:no-doc true}
-  promise-monad
+  promise-context
   (reify
-    cats/Functor
-    (fmap [mn f mv]
+    mp/ContextClass
+    (-get-level [_] mc/+level-default+)
+
+    mp/Functor
+    (-fmap [mn f mv]
       (then mv f))
 
-    cats/Applicative
-    (pure [_ v]
+    mp/Applicative
+    (-pure [_ v]
       (promise v))
 
-    (fapply [_ pf pv]
-      (spread (all [pf pv])
-              (fn [f v]
-                (f v))))
-
-    cats/Monad
-    (mreturn [_ v]
+    (-fapply [_ pf pv]
+      (then (all [pf pv])
+            (fn [[f v]]
+              (f v))))
+
+    mp/Semigroup
+    (-mappend [_ mv mv']
+      (p/-then (m/sequence [mv mv'])
+               (fn [[mvv mvv']]
+                 (let [ctx (mp/-get-context mvv)]
+                   (mp/-mappend ctx mvv mvv')))))
+
+    mp/Monad
+    (-mreturn [_ v]
       (promise v))
 
-    (mbind [mn mv f]
-      (let [ctx mctx/*context*]
-        (then mv (fn [v]
-                    (m/with-monad ctx
-                      (f v))))))))
-
+    (-mbind [mn mv f]
+      (then mv f))))
+

(defn promise?
  "Returns true if `p` is a primise instance."
  [p]
  (satisfies? proto/IPromise p))

(defn resolved?
  "Returns true if promise `p` is already fulfilled."
  [p]
  (proto/-resolved? p))

(defn fulfilled?
  "Convenience alias for the `resolved?` predicate."
  [p]
  (proto/-resolved? p))

(defn rejected?
  "Returns true if promise `p` is already rejected."
  [p]
  (proto/-rejected? p))

(defn pending?
  "Returns true if promise `p` is stil pending."
  [p]
  (proto/-pending? p))

(defn done?
  "Returns true if promise `p` is already done."
  [p]
  (not (proto/-pending? p)))

;; Chain operations

(defn then
  "A chain helper for promises."
  [p callback]
  (proto/-then p callback))

(defn finally
  "A chain helper that associate handler to the promise
  that will be called regardless if it is resolved or
  rejected."
  [p callback]
  (proto/-finally p callback))

(defn catch
  "Catch all promise chain helper."
  ([p callback]
   (proto/-catch p callback))
  ([p type callback]
   (proto/-catch p type callback)))

(defn error
  "Catch operational errors promise chain helper."
  [p callback]
  (.error p callback))

;; Helpers


(ns promesa.core
  (:refer-clojure :exclude [delay spread some])
  (:require [cats.core :as m]
            [cats.context :as mc]
            [cats.protocols :as mp]
            [promesa.protocols :as p]
            [org.bluebird]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(declare promise-context)

(extend-type js/Promise
  mp/Context
  (-get-context [_] promise-context)

  mp/Extract
  (-extract [it]
    (if (.isRejected it)
      (.cause it)
      (.value it)))

  p/IPromise
  (-then [it cb]
    (.then it cb))

