 (declare canonicalize)
 
 
-(defn append! [container el]
+(defn append!
+  "Make element last child of container.
+   Returns live DOM node.
+   > *container* CSS selector or live DOM node
+   > *el* hiccup vector"
+  [container el]
   (let [el (if (dom-element? el)
              el
              (build-dom-elem el))]
     (gdom/appendChild (select container) el)
     el))
 
-(defn prepend! [container el]
+(defn prepend!
+  "Make element first child of container.
+   Returns live DOM node.
+   > *container* CSS selector or live DOM node
+   > *el* hiccup vector"
+  [container el]
   (let [el (if (dom-element? el)
              el
              (build-dom-elem el))]
     (gdom/insertChildAt (select container) el 0)
     el))
 
-(defn remove! [el]
+(defn remove!
+  "Remove element from DOM and return it.
+   > *el* CSS selector or live DOM node"
+  [el]
   (gdom/removeNode (select el)))
 
-(defn replace! [old new]
-  (let [new (if (dom-element? new)
-              new
-              (build-dom-elem new))]
+(defn replace!
+  "Replace live DOM node with a new one.
+   > *old* CSS selector or live DOM node
+   > *new* CSS selector, live DOM node, or hiccup vector"
+  [old new]
+  (let [new (condp = (node-type new)
+              :dom new
+              :hiccup (build-dom-elem new)
+              :selector (select new))]
     (gdom/replaceNode new (select old))))
 
 (defn style
+  "Get or set inline element style.
+
+   `(style el)`                map of inline element styles
+
+   `(style el :keyword)`       value of style :keyword
+
+   `(style el {:keyword val})` sets inline style according to map, returns element
+
+   `(style el :keyword val)`   sets single style, returns element"
   ([el] (throw (js/Error. "TODO: return map of element styles")))
   ([el x] (match [x]
                  [(k :when keyword?)] (gstyle/getComputedStyle el (name k))
-                 [(m :when map?)] (doseq [[k v] m] (style el k v))))
+                 [(m :when map?)] (doseq [[k v] m] (style el k v)))
+     el)
   ([el k v] (gstyle/setStyle el (name k)
                              (match [v]
                                     [s :when string?] s
                                     [n :when number?]
                                     (if (#{:height :width :top :left :bottom :right} (keyword k))
                                       (str n "px")
-                                      n)))))
+                                      n)))
+     el))
 
 (defn attr
+  "Get or set element attributes.
+
+   `(attr el)`                map of element attributes
+
+   `(attr el :keyword)`       value of attr :keyword
+
+   `(attr el {:keyword val})` sets element attributes according to map, returns element
+
+   `(attr el :keyword val)`   sets single attr, returns element"
   ([el] (let [attrs (.-attributes el)]
           (into {} (for [i (range (.-length attrs))]
                      [(keyword  (.-name (aget attrs i)))

   (dom-element? node) :dom      ;;It's an actual DOM node
   ))

(defmulti select node-type)
(defmethod select :selector
  ([selector] (.querySelector js/document selector))
  ([selector container] (.querySelector (select container) selector)))
(defmethod select :dom [node] node)

(defmulti select-all node-type)
(defmethod select-all :selector
  ([selector] (.querySelectorAll js/document selector))
  ([selector container] (.querySelectorAll (select container) selector)))
(defmethod select-all :dom [nodes] nodes)



(defn children [node]
  (filter #(= 1 (.-nodeType %))
          (.-childNodes (select node))))

(defn parent [node]
  (.-parentNode (select node)))



(defn add-class! [el class] (classed! el class true))
(defn remove-class! [el class] (classed! el class false))


;;Call this fn with a fn that should be executed on the next browser animation frame.
(def request-animation-frame
  (or (.-requestAnimationFrame js/window)
      (.-webkitRequestAnimationFrame js/window)
      #(timeout 10 (%))))

(defn merge-dom!
  "Recursively walk a live DOM node, unifying its attributes and children with those of hiccup vector `el`.
   Boolean kwarg `:defer-attr` to set attributes on next animation frame, defaults to `false`. "
  [$node el & {:keys [defer-attr]
               :or {defer-attr false}}]
  (let [el (canonicalize el)]
    (when (not= (.toLowerCase (.-nodeName $node))
                (.toLowerCase (name (:tag el))))
      (throw "Cannot merge el into node of a different type"))

    (if defer-attr
      (request-animation-frame #(attr $node (:attr el)) $node)
      (attr $node (:attr el)))

    (when-let [txt (first (filter string? (:children el)))]
      (text $node txt))
    (iter {for [dom-child el-child] in (map vector (children $node)
                                            (remove string? (:children el)))}
          (merge-dom! dom-child el-child :defer-attr defer-attr))
    $node))

(defn canonicalize
  "Convert hiccup vectors into maps suitable for rendering.
   Hiccup vectors will be converted to maps of {:tag :attr :children}.
   Strings will be passed through and numbers coerced to strings.
   Based on Pinot's html/normalize-element."
  [x]
  (match [x]
