;;; syd-file.el -*- lexical-binding: t; -*-

(require 'syd-prelude)
(require 'syd-buffers)
(require 'cl-lib)
(eval-when-compile (require 'cl-lib))
(eval-when-compile (require 'tramp))

(syd-define-stub
 syd/copy-this-file
 :desc "Copy current file.  See `doom/copy-this-file'."
 :interactive t)

(defun syd-files--update-refs (&rest files)
  "Ensure FILES are updated in `recentf', `magit' and `save-place'."
  (let (toplevels)
    (dolist (file files)
      (when (featurep 'vc)
        (vc-file-clearprops file)
        (when-let (buffer (get-file-buffer file))
          (with-current-buffer buffer
            (vc-refresh-state))))
      (when (featurep 'magit)
        (when-let (default-directory
                   (magit-toplevel (file-name-directory file)))
          (cl-pushnew default-directory toplevels)))
      (unless (file-readable-p file)
        (when (bound-and-true-p recentf-mode)
          (recentf-remove-if-non-kept file))
    (dolist (default-directory toplevels)
      (magit-refresh))
    (when (bound-and-true-p save-place-mode)
      (save-place-forget-unreadable-files))))))

(defun syd/delete-this-file (&optional path force-p)
  "Delete PATH, kill its buffers and expunge it from vc/magit cache.

If PATH is not specified, default to the current buffer's file.

If FORCE-P, delete without confirmation."
  (interactive (list (buffer-file-name (buffer-base-buffer))
                     current-prefix-arg))
  (let* ((path (or path (buffer-file-name (buffer-base-buffer))))
         (short-path (and path (abbreviate-file-name path))))
    (unless path
      (user-error "Buffer is not visiting any file"))
    (unless (file-exists-p path)
      (error "File doesn't exist: %s" path))
    (unless (or force-p (y-or-n-p (format "Really delete %S?" short-path)))
      (user-error "Aborted"))
    (let ((buf (current-buffer)))
      (unwind-protect
          (progn (delete-file path t) t))
      (if (file-exists-p path)
          (error "Failed to delete %S" short-path)
        ;; Ensures that windows displaying this buffer will be switched to
        ;; real buffers (`doom-real-buffer-p')
        (syd/kill-this-buffer-in-all-windows buf t)
        (syd-files--update-refs path)
        (message "Deleted %S" short-path)))))

(syd-define-stub
 syd/move-this-file
 :desc "Move current file.  See `doom/move-this-file'."
 :interactive t)

(defun syd-find-file-in (root)
  (interactive (list (read-directory-name
                      "Find file in: " default-directory nil t)))
  ;; HACK: To avoid reimplementation, we pretend `root' is a project and
  ;; delegate the work to project.el.
  (syd-with-project-root root
    (project-find-file)))

(syd-define-stub
 syd/yank-buffer-path
 :desc "Yank buffer path."
 :interactive t)

(defun syd/find-file-in-emacs-user-directory ()
  (interactive)
  (unless (file-directory-p user-emacs-directory)
    (user-error "`user-emacs-directory' doesn't exist! (%s)"
                (abbreviate-file-name user-emacs-directory)))
  (let ((default-directory user-emacs-directory))
    (call-interactively #'find-file)))

(defun syd-switch-to-emacs-user-directory ()
  "Switch project to `user-emacs-directory' via `project-switch-project'."
  (interactive)
  (require 'syd-project)
  (if (file-directory-p user-emacs-directory)
      (syd-with-project-root user-emacs-directory
        (project-switch-project user-emacs-directory))
    (user-error "`user-emacs-directory' (%s) does not exist or is not a directory!"
                (abbreviate-file-name user-emacs-directory))))

(syd-define-stub
 syd/open-this-file-as-root
 :desc "Open current file as root.  See `doom/sudo-this-file'."
 :interactive t)

(syd-define-stub
 syd/find-file-as-root
 :desc "Open current file as root.  See `doom/sudo-this-file'."
 :interactive t)

(defun syd--make-syncthing-merge-finalise-hook (file-name conflict-file-name)
  (lambda ()
    (let ((merge-result-file (read-file-name
                              (format "Write merge to (default: %s):"
                                      file-name)
                              nil file-name))
          (delete-conflict-p (yes-or-no-p (format "Delete conflict file? (%s)"
                                                  conflict-file-name)))))
    (when merge-result-file
      (with-current-buffer ediff-buffer-C
        (set-visited-file-name merge-result-file)
        (save-buffer))
      (kill-buffer ediff-buffer-C))
    (when delete-conflict-p
      (kill-buffer (find-buffer-visiting conflict-file-name))
      (delete-file conflict-file-name))))

(defun syd--read-syncthing-conflict-file (&optional directory)
  (let ((conflict-files (directory-files-recursively
                         (or directory default-directory)
                         (rx ".sync-conflict-"))))
    (completing-read "Conflict file: " conflict-files nil t)))

(defun syd--syncthing-conflict-file-base-name (conflict-file)
  (replace-regexp-in-string (rx ".sync-conflict-" (* (not ?.)))
                            ""
                            conflict-file))

(defun syd-resolve-syncthing-conflict (conflict-file)
  (interactive (list (syd--read-syncthing-conflict-file)))
  (require 'ediff)
  (let* ((base-file (syd--syncthing-conflict-file-base-name conflict-file))
         (ediff-after-quit-hook-internal
          ;; Override Ediff's "save and quit" hook with our own.
          (cons (syd--make-syncthing-merge-finalise-hook base-file conflict-file)
                (remq #'ediff-write-merge-buffer-and-maybe-kill
                      (ensure-list ediff-quit-merge-hook)))))
    (ediff-merge-files
     base-file
     conflict-file)))

;;;###autoload
(defun syd-split-tramp-file-name (file-name)
  "Split FILE-NAME into (TRAMP-PREFIX . LOCAL-NAME). Returns (nil . FILE-NAME)
if FILE-NAME has no TRAMP prefix."
  (if (tramp-tramp-file-p file-name)
      (let* ((dissected (tramp-dissect-file-name file-name t))
             (localname (tramp-file-name-localname dissected)))
        (setf (tramp-file-name-localname dissected) nil)
        (cons (tramp-make-tramp-file-name dissected)
              localname))
    (cons nil file-name)))

(provide 'syd-file)
