 (ns lt.plugins.cljrefactor.form-refactor
   (:require [lt.plugins.cljrefactor.parser :as p]
             [lt.plugins.cljrefactor.util :as u]
+            [rewrite-clj.zip :as z]
+            [rewrite-clj.zip.whitespace :as ws]
+            [rewrite-clj.node :as nd]
+            [rewrite-clj.zip.utils :as zu]
+            [clojure.zip :as zz]
             [lt.object :as object]
             [lt.objs.editor.pool :as pool]
             [lt.objs.editor :as editor]
             [lt.objs.command :as cmd]
-            [lt.plugins.paredit :as pe]
-            [lt.plugins.clojure :as cp]
-            [clojure.zip :as z]
             [clojure.string :as s])
   (:require-macros [lt.macros :refer [behavior]]))
 
 
-(defn top [zipnode]
-  (loop [n zipnode]
-    (if-not (z/up n)
-      n
-      (recur (z/up n)))))
 
-(defn if-node? [loc]
-  (when (and (seq loc) (list? (z/node loc)))
-    (-> loc z/down z/node  ((fn [s] (some #{s} ['if 'if-not]))))))
+(defn-  nodes-by-dir
+  ([zloc f] (nodes-by-dir zloc f constantly))
+  ([zloc f p?]
+   (->> zloc
+        (iterate f)
+        (take-while identity)
+        (take-while p?)
+        (map z/node))))
 
 
-(defn swap-if-nodes [loc]
-  (let [move (-> loc z/down z/right z/right z/node)]
-    (-> loc z/down z/right z/right z/remove top (z/append-child move))))
+(defn- an-if? [zloc]
+  (some #{"if" "if-not"} [(z/string zloc)]))
 
-(defn cycle-if [form-str]
-  (let [root (p/str->seq-zip form-str)
-        if-node (if-node? root)]
-    (when if-node
-      (-> root
-          swap-if-nodes
-          z/down
-          (z/replace (if (= if-node 'if) 'if-not 'if))
-          p/zip->str))))
+
+(defn- find-if [zloc]
+  (when zloc
+     (if-let [an-if (-> zloc z/leftmost an-if?)]
+       [an-if (z/leftmost zloc)]
+       (recur (z/up zloc)))))
+
+
+(defn- right-lb-or-nl [zloc]
+  (->> (-> zloc
+           zz/right
+           (nodes-by-dir zz/right ws/whitespace-or-comment?))
+       (filter #(or (nd/linebreak? %) (nd/comment? %)))))
+
+(defn shift-left [zloc]
+  (let [preserves-right (right-lb-or-nl zloc)
+        maybe-trim-ws (fn [l]
+                        (if (and (z/rightmost? l)
+                                 (not (->> l right-lb-or-nl (filter nd/comment?) seq)))
+                          (zu/remove-right-while l ws/whitespace?)
+                          l))
+        maybe-add-lb (fn [l]
+                       (if (and (not (seq preserves-right))
+                                (-> zloc z/left right-lb-or-nl seq))
+                         (z/insert-left l (nd/newlines 1))
+                         l))]
+    (-> zloc
+        (zu/remove-right-while ws/whitespace-or-comment?)
+        zu/remove-and-move-left
+        z/left
+        maybe-trim-ws
+        (z/insert-left (z/node zloc))
+        ((partial reduce z/insert-left) preserves-right)
+        maybe-add-lb)))
+
+
+(defn cycle-if [zloc]
+  (when-let [[an-if cand] (find-if zloc)]
+    (-> cand
+        z/rightmost
+        shift-left
+        z/leftmost
+        (z/replace (symbol (if (= an-if "if") "if-not" "if"))))))
 
 
 (defn cycle-col [form-str]

       (= (.substr form-str 0 2) "#{") (wrap-in "(" ")" 2)
       :else nil))))


(defn find-node [start candidate-str]
  (loop [cur start]
    (cond
     (= (-> cur z/node str) candidate-str) cur
     (not (z/end? cur)) (recur (z/next cur))
    :else nil)))



(defn move-up [form-str candidate-str]
  (let [root (p/str->seq-zip form-str)
        loc? (when (seq root) (find-node root candidate-str))]
    (when (and loc? (z/node (z/remove loc?)))
      (z/root (z/insert-left (z/remove loc?) (z/node loc?))))))

;;(move-up "(+ 1 (\"__jalla__\" \"dill\"))" "__jalla__")

;;(move-up "(\"__jalla__\")" "__jalla__")


;; DOH no negative lookbehind in JS
;;"(?<!if|\bif-not\b)\s\(" "\n("
(defn inject-nl[form-str]
  (let [idx-a (.indexOf form-str "if (")
        idx-b (.indexOf form-str "if-not (")]
    (cond
     (= -1 idx-a idx-b) (s/replace form-str #"\s\(" "\n(")
     (> idx-a idx-b) (str (.substr form-str 0 (+ idx-a 4))
                          (inject-nl (.substr form-str (+ idx-a 4))))
     :else (str (.substr form-str 0 (+ idx-b 8))
                (inject-nl (.substr form-str (+ idx-b 8)))))))


;; Silly simplistic...
(defn format-form [form-str]
  (-> form-str
      inject-nl))


(defn replace-cmd [ed replace-fn & {:keys [fmt]}]

              :exec (fn []
                      (when-let [ed (pool/last-active)]
                        (object/raise ed :refactor.cycle-col!)))})
