(ns leiningen.update-in
  "Perform arbitrary transformations on your project map."
  (:refer-clojure :exclude [update-in])
  (:require [leiningen.core.main :as main]
            [leiningen.core.project :as project]
            [leiningen.core.utils :as utils]
            [clojure.core :as clj]))

(defn ^:internal parse-args [key-path f args]
  (let [[f-args [_ & task+args]] (split-with #(not= "--" %) args)]
    [(mapv keyword (rest (.split key-path ":")))
     (utils/require-resolve (read-string f))
     (mapv read-string f-args)
     task+args]))

(defn ^:internal update-project [project keys-vec f args]
  (let [f #(if (seq keys-vec)
             (apply clj/update-in % keys-vec f args)
             (apply f % args))]
    (-> (vary-meta (f project) clj/update-in [:without-profiles] f)
        (project/load-plugins)
        (project/activate-middleware))))

(defn ^:higher-order ^:no-project-needed update-in
  "Perform arbitrary transformations on your project map.

Acts a lot like calling `clojure.core/update-in` on your project map
and then invoking a task on it, but with a few differences. Instead of
a vector of keys for reaching into nested maps, just mash keywords
together like \":repl-options:port\". A single \":\" refers to the map
root. Provide the arguments to f (which must be a resolvable var)
followed by \"--\", and then the task name and arguments to the task:

    $ lein update-in :dependencies conj \"[slamhound \\\"1.1.3\\\"]\" -- repl"
  [project key-path f & args]
  (let [[keys-vec f f-args task+args] (parse-args key-path f args)]
    (main/resolve-and-apply (update-project project keys-vec f f-args)
                            task+args)))
