 (enable-console-print!)
 
 ;; ---------------------------------------------------------------------
+;; View helpers
+
+(defprotocol IInspect
+  (-inspect [this]))
+
+;; ---------------------------------------------------------------------
 ;; Utilities
 
 (defn- empty? [x]
-  (cond
-   (object? x) (object/isEmpty x)
-   :else (clojure.core/empty? x)))
+  (if (object? x)
+    (object/isEmpty x)
+    (clojure.core/empty? x)))
 
 (defn- record? [x]
   (satisfies? IRecord x))
 
-(defn- regex? [x]
-  (instance? js/RegExp x))
-
-(defn- record-name [r]
-  (first (string/split (pr-str r) "{")))
+(defn record-name [r]
+  (let [s (pr-str r)]
+    (subs s 0 (.indexOf s "{"))))
+
+(defn record-opener [r]
+  (str (record-name r) "{"))
 
 ;; ---------------------------------------------------------------------
 ;; View helpers
 
 (declare view)
-
-(defn- literal [class x]
-  (dom/span #js {:className class
-                 :key x}
-            (pr-str x)))
-
-(defn- collection [cursor coll class opener closer]
-  (om/build view
-            {:value coll}
-            {:opts {:opener opener
-                    :class class
-                    :closer closer}}))
 
 ;; TODO: Build a view for functions. 
 

                               :outline "none"
                               :fontWeight "bold"
                               :padding "0"
                               :opacity (if disable?
                                          "0.5"
                                          "1.0")}}
              (if (om/get-state owner :open?)
                "-"
                "+")))

(defn- view [data owner {:keys [class opener closer] :as opts}]
  (let [value (:value data)
        value? (empty? value)
        open? #(om/get-state owner :open?)]
    (reify
      om/IInitState
      (init-state [_]
        {:open? (and (not (false? (:open? opts)))
                     (not value?))})

      om/IRender
      (render [_]
        (dom/div #js {:className class}
                 (toggle-button owner {:disable? value?})
                 
                 (dom/span #js {:className "opener"
                                :style #js {:display "inline-block"}}
                           opener)

                 (dom/ul #js {:className "values"
                              :style #js {:display (if (open?)
                                                     "block"
                                                     "none")
                                          :listStyleType "none"
                                          :margin "0"}}
                         (let [f (cond
                                  (map? value) map->dom
                                  (object? value) object->dom
                                  :else coll->dom)]
                           (f data value)))

                 (dom/span #js {:className "ellipsis"
                                :style #js {:display (cond
                                                      (open?) "none"
                                                      value? "none"
                                                      :else "inline")}}
                           "…")

                 (dom/span #js {:className "closer"
                                :style #js {:display (cond
                                                      (not value?) "inline-block"
                                                      (open?) "block"
                                                      :else "inline-block")}}
                           closer))))))

(defn inspector
  ([data owner]
     (inspector data owner {:opts {:class "inspector"}}))
  ([data owner opts]
     (let [{:keys [class] :or {class "inspector"}} opts]
       (reify
         om/IRender
         (render [_]
           (dom/div #js {:className "inspector"
                         :style #js {:fontFamily "monospace"
                                     :whiteSpace "pre-wrap"
                                     :width "100%"
                                     :overflowX "scroll"}}
                    (inspect data (om/value data))))))))

  (defn function-name [f]
    (first (string/split (pr-str f) "{"))))

(defn- literal [class x]
  (dom/span #js {:className class :key x}
    (pr-str x)))

(defn coll-view [data opener closer class]
  (om/build view data
    {:opts {:opener opener :closer closer :class class}}))

(defn inspect* [x]
  (cond
    (satisfies? IInspect x)
    (-inspect x)
    (fn? x)
    (literal "function" x)
    :else
    (literal "literal" x)))

(defn- associative->dom
  [data {:keys [entry-class key-class val-class]}]
  (into-array
    (for [[k v] data]
      (dom/li #js {:key (str [k v])}
        (dom/div #js {:className (str "entry " entry-class)
                      :style #js {:position "relative"}}
          (dom/span #js {:className (str "key " key-class) 
                         :style #js {:display "inline-block"
                                     :verticalAlign "top"}}
            (inspect* k))
          (dom/span #js {:style #js {:display "inline-block"
                                     :width "1em"}})
          (dom/span #js {:className (str "val " val-class)
                         :style #js {:display "inline-block"
                                     :verticalAlign "top"}}
            (inspect* v)))))))

(defn- sequential->dom [data]
  (into-array
    (for [[i x :as pair] (map-indexed vector data)]
      (dom/li #js {:className "entry"
                   :key (str pair)}
        (inspect* x)))))

(defn- toggle-button [owner {:keys [disable?]}]
  (dom/button #js {:className "toggle-button"
                   :disabled disable?
                   :onClick
                   (fn [_]
                     (om/update-state! owner :open? not))
                   :style #js {:display "inline-block"
                               :verticalAlign "top"
                               :border "none"
