(defpackage :dialogs
  (:use :cl :qml)
  (:export
   #:query-dialog
   #:debug-dialog
   #:get-file-name
   #:exited
   #:help
   #:push-dialog
   #:pop-dialog
   #:*file-name*))

(in-package :dialogs)

(defvar *file-name* nil)
(defvar *callback*  nil)

(defun push-dialog (name)
  "Pushes dialog NAME onto the StackView."
  #+ios
  (disable-clipboard-menu nil)
  (qjs |pushDialog| ui:*main* (string-downcase name)))

(defun pop-dialog ()
  "Pops the currently shown dialog, returning T if there was a dialog to pop."
  #+ios
  (disable-clipboard-menu)
  (prog1
      (> (q< |depth| ui:*main*) 1)
    (qjs |popDialog| ui:*main*)))

(defun wait-while-transition ()
  ;; needed for evtl. recursive calls
  (x:while (q< |busy| ui:*main*)
    (qsleep 0.1)))

(defun query-dialog (query)
  (qrun*
   (unless (x:empty-string query)
     (q> |text| ui:*query-text* (string-trim '(#\Newline) query)))
   (q! |clear| ui:*query-input*)
   (wait-while-transition)
   (push-dialog :query)
   (q! |forceActiveFocus| ui:*query-input*)
   (q! |showKeyboard| ui:*main* t) ; needed on recursive calls
   (qsingle-shot 500 (lambda () (q! |positionViewAtEnd| ui:*output*)))
   (wait-for-closed)
   (qlater (lambda () (editor:ensure-focus :show)))
   (q< |text| ui:*query-input*)))

(defun append-debug-output (text color bold)
  (qjs |appendOutput| ui:*debug-model*
       (list :text  text
             :color color
             :bold  bold)))

(defun debug-dialog (messages)
  (qrun*
   (q! |clear| ui:*debug-model*)
   (q> |text| ui:*debug-input* ":q")
   (dolist (text/color messages)
     (let* ((text (string-trim '(#\Newline) (car text/color)))
            (color (cdr text/color))
            (bold (not (string= "black" color)))) ; boolean
       (append-debug-output text color bold)))
   (wait-while-transition)
   (push-dialog :debug)
   (q! |forceActiveFocus| ui:*debug-input*)
   (qsingle-shot 500 (lambda () (q! |positionViewAtEnd| ui:*debug-text*)))
   (wait-for-closed)
   (qlater (lambda () (editor:ensure-focus :show)))
   (q< |text| ui:*debug-input*)))

(let (waiting)
  (defun wait-for-closed ()
    (setf waiting t)
    ;; busy waiting is safer than suspending a thread, especially on mobile
    (x:while waiting
      (qsleep 0.1))
    (pop-dialog))
  (defun exited () ; called from QML
    (unless waiting
      (pop-dialog))
    (setf waiting nil)))

;; file browser

(let ((1st t))
  (defun get-file-name (&optional callback focus)
    #+android
    (ensure-permissions :write-external-storage)
    (qjs |showKeyboard| ui:*main* nil)
    (when 1st
      (setf 1st nil)
      (set-file-browser-path #+ios ":data" #-ios ":home"))
    (setf *callback* callback)
    ;; force update
    (dolist (folder (list "" (q< |folder| ui:*folder-model*)))
      (q> |folder| ui:*folder-model* folder))
    (q> |editMode| ui:*file-browser* nil)
    (push-dialog :file)
    (when focus
      (qsingle-shot 500 (lambda () (q! |forceActiveFocus| ui:*path*))))))

(defun directory-p (path)
  (unless (equal "" path)
    (not (or (pathname-name path)
             (pathname-type path)))))

(defun set-file-name (file-name) ; called from QML
  (let ((name (remove-if (lambda (ch) (find ch "*?\\")) file-name)))
    (if (directory-p name)
        (set-file-browser-path name)
        (progn
          (setf *file-name* name)
          (when *callback*
            (funcall *callback*))
          ;; QLATER: prevent crash (internal threads are a bitch)
          (qlater 'pop-dialog)))))

(defun rename-file* (from to) ; called from QML
  (ensure-directories-exist (merge-pathnames to from)) ; for moving files around
  (rename-file from to))

(defun location (name)
  (cond ((string= ":data" name)
         #+(or android ios)
         (progn
           #+android "/sdcard/Documents/"
           #+ios     (namestring (truename (merge-pathnames "../Documents/"))))
         #-(or android ios)
         (namestring (merge-pathnames "Documents/" (user-homedir-pathname))))
        ((string= ":home" name)
         (namestring *default-pathname-defaults*))))

(defun set-file-browser-path (path) ; called from QML
  (let ((url (x:cc #+win32 "file:/"
                   #-win32 "file://"
                   (if (x:starts-with ":" path)
                       (location path)
                       path))))
    (unless (x:ends-with "/" url)
      (setf url (x:cc url "/")))
    (q> |folder| ui:*folder-model* url)))

(defun help ()
  (qrun*
   (push-dialog :help)))

