;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns app.util.shell
  "Shell & FS utilities."
  (:require
   ["child_process" :as proc]
   ["fs" :as fs]
   ["path" :as path]
   [app.common.exceptions :as ex]
   [app.common.logging :as l]
   [app.common.uuid :as uuid]
   [app.config :as cf]
   [cuerdas.core :as str]
   [promesa.core :as p]))

(l/set-level! :trace)

(def tempfile-minage (* 1000 60 60 1)) ;; 1h

(def tmpdir
  (let [path (cf/get :tempdir)]
    (l/inf :hint "tmptdir setup" :path path)
    (when-not (fs/existsSync path)
      (fs/mkdirSync path #js {:recursive true}))
    path))

(defn- schedule-deletion!
  [path]
  (letfn [(remote-tempfile []
            (when (fs/existsSync path)
              (l/trace :hint "permanently remove tempfile" :path path)
              (fs/rmSync path #js {:recursive true})))]
    (l/trace :hint "schedule tempfile deletion"
             :path path
             :scheduled-at (.. (js/Date. (+ (js/Date.now) tempfile-minage)) toString))
    (js/setTimeout remote-tempfile tempfile-minage)))

(defn tempfile
  [& {:keys [prefix suffix]
      :or {prefix "penpot."
           suffix ".tmp"}}]
  (loop [i 0]
    (if (< i 1000)
      (let [path (path/join tmpdir (str/concat prefix (uuid/next) "-" i  suffix))]
        (if (fs/existsSync path)
          (recur (inc i))
          (do
            (schedule-deletion! path)
            path)))
      (ex/raise :type :internal
                :code :unable-to-locate-temporal-file
                :hint "unable to find a tempfile candidate"))))

(defn move!
  [origin-path dest-path]
  (.rename fs/promises origin-path dest-path))

(defn stat
  [path]
  (-> (.stat fs/promises path)
      (p/then (fn [data]
                {:created-at (inst-ms (.-ctime ^js data))
                 :size (.-size data)}))
      (p/catch (constantly nil))))

(defn rmdir!
  [path]
  (.rm fs/promises path #js {:recursive true}))

(defn write-file!
  [fpath content]
  (.writeFile fs/promises fpath content))

(defn read-file
  [fpath]
  (.readFile fs/promises fpath))

(defn run-cmd!
  [cmd]
  (p/create
   (fn [resolve reject]
     (l/trace :fn :run-cmd :cmd cmd)
     (proc/exec cmd #js {:encoding "buffer"}
                (fn [error stdout _stderr]
                  ;; (l/trace :fn :run-cmd :stdout stdout)
                  (if error
                    (reject error)
                    (resolve stdout)))))))

