(in-package #:lispsearch)

;;;; Small, pure helpers. See the M0 spike notes in lispsearch-plan.md §2 for the
;;;; confirmed name-encoding rules.

(defun decode-name (registry-name)
  "Decode an OCI repo key into the ASDF/install name: _slash_ -> /, _plus_ -> +.
e.g. \"cl_plus_ssl\" -> \"cl+ssl\", \"clog_slash_tools\" -> \"clog/tools\"."
  (str:replace-all "_slash_" "/"
                   (str:replace-all "_plus_" "+" registry-name)))

(defun encode-name (system-name)
  "Inverse of DECODE-NAME: build an OCI repo key from an ASDF name."
  (str:replace-all "+" "_plus_"
                   (str:replace-all "/" "_slash_" system-name)))

(defun split-lines (string)
  "Trimmed, non-empty lines of STRING."
  (remove-if (lambda (s) (zerop (length s)))
             (mapcar (lambda (s) (string-trim '(#\Space #\Tab #\Return) s))
                     (uiop:split-string string :separator '(#\Newline)))))

(defun iso-now ()
  "Current UTC time as an ISO-8601 string, e.g. 2026-06-05T14:33:09Z."
  (multiple-value-bind (sec min hr day mon yr)
      (decode-universal-time (get-universal-time) 0)
    (format nil "~4,'0D-~2,'0D-~2,'0DT~2,'0D:~2,'0D:~2,'0DZ"
            yr mon day hr min sec)))

(sb-ext:defglobal *temp-dir-counter* (list 0)
  "Process-global counter (a cons so its CAR is an atomic-incf place) for unique
temp-dir names. Bumped atomically so parallel enrichment workers never collide —
a shared `random` would not be thread-safe.")

(defmacro with-temp-dir ((var) &body body)
  "Bind VAR to a freshly created temporary directory pathname; remove it on exit.
Thread-safe: the unique suffix comes from an atomic counter, not `random`."
  (let ((dir (gensym "DIR")))
    `(let ((,dir (ensure-directories-exist
                  (uiop:ensure-directory-pathname
                   (merge-pathnames
                    (format nil "lispsearch-~36R-~36R/"
                            (get-internal-real-time)
                            (sb-ext:atomic-incf (car *temp-dir-counter*)))
                    (uiop:temporary-directory))))))
       (let ((,var ,dir))
         (unwind-protect (progn ,@body)
           (ignore-errors (uiop:delete-directory-tree ,dir :validate t)))))))
