;;; ~/.doom.d/config.el -*- lexical-binding: t; -*-

(setq doom-theme 'gruvbox-dark-soft)

;; Set to 2 spaces
(setq default-tab-width 2)
(setq tab-width 2)
(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)
(setq js2-mode-hook
      '(lambda () (progn
                    (set-variable 'js2-basic-offset 2))))
(setq css-mode-hook
      '(lambda () (progn
                    (set-variable 'css-indent-offset 2))))
(setq typescript-mode-hook
      '(lambda () (progn
                    (set-variable 'typescript-indent-level 2))))

;; Maximize the window upon startup
(add-to-list 'default-frame-alist '(fullscreen . maximized))

(setq doom-font (font-spec :family "Cozette" :size 11))

(when (memq window-system '(mac ns x))
  (exec-path-from-shell-initialize))

;; Make Monky communicate via cmdserver
(setq monky-process-type 'cmdserver)

;; Map SPC-g-h to monky-status
(map! :map magit-mode-map :leader "g h" 'monky-status)

;; Enable Mercurial support for git-gutter
(setq git-gutter:handled-backends '(git hg))

;; Enable editorconfig
(editorconfig-mode 1)

;; mu4e config
(set-email-account! "posteo"
                    '((mu4e-sent-folder . "/Sent")
                      (mu4e-drafts-folder . "/Drafts")
                      (mu4e-trash-folder . "/Trash")
                      (mu4e-refile-folder . "/INBOX")
                      (smtpmail-smtp-user . "mokou@posteo.net")
                      (user-mail-address . "mokou@posteo.de"))
                    t)
(map! :map global-map :leader "M" 'mu4e)

;; Org configuration
(after! org
  (setq-default org-directory "~/code/personal/org")
  (setq-default org-default-notes-file "~/code/personal/org/capture.org")
  (setq-default org-todo-keywords
                (quote ((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d)")
                        (sequence "WAITING(w@/!)" "HOLD(h@/!)" "|" "CANCELLED(c@/!)" "PHONE" "MEETING"))))
  (setq-default org-todo-keyword-faces
                (quote (("TODO" :foreground "red" :weight bold)
                        ("NEXT" :foreground "blue" :weight bold)
                        ("DONE" :foreground "forest green" :weight bold)
                        ("WAITING" :foreground "orange" :weight bold)
                        ("HOLD" :foreground "magenta" :weight bold)
                        ("CANCELLED" :foreground "forest green" :weight bold)
                        ("MEETING" :foreground "forest green" :weight bold)
                        ("PHONE" :foreground "forest green" :weight bold))))
  (setq org-capture-templates
        (quote (("t" "todo" entry (file "~/code/personal/org/capture.org")
                 "* TODO %?\n%U\n" :clock-in t :clock-resume t)
                ("r" "respond" entry (file "~/code/personal/org/capture.org")
                 "* STRT Respond to %:from on %:subject\nSCHEDULED: %t\n%U\n%a\n" :clock-in t :clock-resume t :immediate-finish t)
                ("n" "note" entry (file "~/code/personal/org/capture.org")
                 "* %? :NOTE:\n%U\n%a\n" :clock-in t :clock-resume t)
                ("w" "org-protocol" entry (file "~/code/personal/org/capture.org")
                 "* TODO Review %c\n%U\n" :immediate-finish t)
                ("m" "meeting" entry (file "~/code/personal/org/capture.org")
                 "* PROJ Meeting with %? :MEETING:\n%U" :clock-in t :clock-resume t))))
  (setq org-refile-targets (quote ((nil :maxlevel . 9)
                                   (org-agenda-files :maxlevel . 9))))
  (add-to-list
   'ivy-completing-read-handlers-alist
   '(org-capture-refile . completing-read-default))
  (org-clock-persistence-insinuate)
  (setq org-clock-in-switch-to-state 'cf/clock-in-to-next)
  (setq org-drawers (quote ("PROPERTIES" "LOGBOOK")))
  (setq org-clock-into-drawer t)
  (setq org-clock-out-remove-zero-time-clocks t)
  (setq org-clock-out-when-done t)
  (setq org-clock-persist t)
  (setq org-clock-auto-clock-resolution (quote when-no-clock-is-running))
  (setq org-clock-report-include-clocking-task t)
  (setq org-agenda-dim-blocked-tasks nil)
  (setq org-agenda-compact-blocks t)
  (setq org-agenda-custom-commands
        (quote (("N" "Notes" tags "NOTE"
                 ((org-agenda-overriding-header "Notes")
                  (org-tags-match-list-sublevels t)))
                (" " "Agenda"
                 ((agenda "" nil)
                  (tags "REFILE"
                        ((org-agenda-overriding-header "Tasks to Refile")
                         (org-tags-match-list-sublevels nil)))
                  (tags-todo "-CANCELLED/!"
                             ((org-agenda-overriding-header "Stuck Projects")
                              (org-agenda-skip-function 'cf/skip-non-stuck-projects)
                              (org-agenda-sorting-strategy
                               '(category-keep))))
                  (tags-todo "-HOLD-CANCELLED/!"
                             ((org-agenda-overriding-header "Projects")
                              (org-agenda-skip-function 'cf/skip-non-projects)
                              (org-tags-match-list-sublevels 'indented)
                              (org-agenda-sorting-strategy
                               '(category-keep))))
                  (tags-todo "-CANCELLED/!NEXT"
                             ((org-agenda-overriding-header (concat "Project Next Tasks"
                                                                    (if cf/hide-scheduled-and-waiting-next-tasks
                                                                        ""
                                                                      " (including WAITING and SCHEDULED tasks)")))
                              (org-agenda-skip-function 'cf/skip-projects-and-habits-and-single-tasks)
                              (org-tags-match-list-sublevels t)
                              (org-agenda-todo-ignore-scheduled cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-deadlines cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-with-date cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-sorting-strategy
                               '(todo-state-down effort-up category-keep))))
                  (tags-todo "-REFILE-CANCELLED-WAITING-HOLD/!"
                             ((org-agenda-overriding-header (concat "Project Subtasks"
                                                                    (if cf/hide-scheduled-and-waiting-next-tasks
                                                                        ""
                                                                      " (including WAITING and SCHEDULED tasks)")))
                              (org-agenda-skip-function 'cf/skip-non-project-tasks)
                              (org-agenda-todo-ignore-scheduled cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-deadlines cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-with-date cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-sorting-strategy
                               '(category-keep))))
                  (tags-todo "-REFILE-CANCELLED-WAITING-HOLD/!"
                             ((org-agenda-overriding-header (concat "Standalone Tasks"
                                                                    (if cf/hide-scheduled-and-waiting-next-tasks
                                                                        ""
                                                                      " (including WAITING and SCHEDULED tasks)")))
                              (org-agenda-skip-function 'cf/skip-project-tasks)
                              (org-agenda-todo-ignore-scheduled cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-deadlines cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-with-date cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-sorting-strategy
                               '(category-keep))))
                  (tags-todo "-CANCELLED+WAITING|HOLD/!"
                             ((org-agenda-overriding-header (concat "Waiting and Postponed Tasks"
                                                                    (if cf/hide-scheduled-and-waiting-next-tasks
                                                                        ""
                                                                      " (including WAITING and SCHEDULED tasks)")))
                              (org-agenda-skip-function 'cf/skip-non-tasks)
                              (org-tags-match-list-sublevels nil)
                              (org-agenda-todo-ignore-scheduled cf/hide-scheduled-and-waiting-next-tasks)
                              (org-agenda-todo-ignore-deadlines cf/hide-scheduled-and-waiting-next-tasks)))
                  (tags "-REFILE/"
                        ((org-agenda-overriding-header "Tasks to Archive")
                         (org-agenda-skip-function 'cf/skip-non-archivable-tasks)
                         (org-tags-match-list-sublevels nil))))
                 nil)))))


(map! :map global-map :leader "a" 'org-agenda)

(defun cf/clock-in-to-next (kw)
  (when (not (and (boundp 'org-capture-mode) org-capture-mode))
    (cond
     ((and (member (org-get-todo-state) (list "TODO"))
           (cf/is-task-p))
      "NEXT")
     ((and (member (org-get-todo-state) (list "NEXT"))
           (cf/is-project-p))
      "TODO"))))

(defun cf/find-project-task ()
  (save-restriction
    (widen)
    (let ((parent-task (save-excursion (org-back-to-heading 'invisible-ok) (point))))
      (while (org-up-heading-safe)
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq parent-task (point))))
      (goto-char parent-task)
      parent-task)))

(defun cf/is-project-p ()
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task has-subtask))))

(defun cf/is-task-p ()
  (save-restriction
    (widen)
    (let ((has-subtask)
          (subtree-end (save-excursion (org-end-of-subtree t)))
          (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
      (save-excursion
        (forward-line 1)
        (while (and (not has-subtask)
                    (< (point) subtree-end)
                    (re-search-forward "^\*+ " subtree-end t))
          (when (member (org-get-todo-state) org-todo-keywords-1)
            (setq has-subtask t))))
      (and is-a-task (not has-subtask)))))

(defun cf/is-project-subtree-p ()
  (let ((task (save-excursion (org-back-to-heading 'invisible-ok)
                              (point))))
    (save-excursion
      (cf/find-project-task)
      (if (equal (point) task)
          nil
        t))))

(defun cf/is-subproject-p ()
  (let ((is-subproject)
        (is-a-task (member (nth 2 (org-heading-components)) org-todo-keywords-1)))
    (save-excursion
      (while (and (not is-subproject) (org-up-heading-safe))
        (when (member (nth 2 (org-heading-components)) org-todo-keywords-1)
          (setq is-subproject t))))
    (and is-a-task is-subproject)))

(defun cf/list-sublevels-for-projects-indented ()
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels 'indented)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defun cf/list-sublevels-for-projects ()
  (if (marker-buffer org-agenda-restrict-begin)
      (setq org-tags-match-list-sublevels t)
    (setq org-tags-match-list-sublevels nil))
  nil)

(defvar cf/hide-scheduled-and-waiting-next-tasks t)


(defun cf/toggle-next-task-display ()
  (interactive)
  (setq cf/hide-scheduled-and-waiting-next-tasks (not cf/hide-scheduled-and-waiting-next-tasks))
  (when  (equal major-mode 'org-agenda-mode)
    (org-agenda-redo))
  (message "%s WAITING and SCHEDULED NEXT Tasks" (if cf/hide-scheduled-and-waiting-next-tasks "Hide" "Show")))


(defun cf/skip-stuck-projects ()
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (cf/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                nil
              next-headline)) ; a stuck project, has subtasks but no next task
        nil))))

(defun cf/skip-non-stuck-projects ()
  ;; (cf/list-sublevels-for-projects-indented)
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (if (cf/is-project-p)
          (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
                 (has-next ))
            (save-excursion
              (forward-line 1)
              (while (and (not has-next) (< (point) subtree-end) (re-search-forward "^\\*+ NEXT " subtree-end t))
                (unless (member "WAITING" (org-get-tags-at))
                  (setq has-next t))))
            (if has-next
                next-headline
              nil)) ; a stuck project, has subtasks but no next task
        next-headline))))

(defun cf/skip-non-projects ()
  ;; (cf/list-sublevels-for-projects-indented)
  (if (save-excursion (cf/skip-non-stuck-projects))
      (save-restriction
        (widen)
        (let ((subtree-end (save-excursion (org-end-of-subtree t))))
          (cond
           ((cf/is-project-p)
            nil)
           ((and (cf/is-project-subtree-p) (not (cf/is-task-p)))
            nil)
           (t
            subtree-end))))
    (save-excursion (org-end-of-subtree t))))

(defun cf/skip-non-tasks ()
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((cf/is-task-p)
        nil)
       (t
        next-headline)))))

(defun cf/skip-project-trees-and-habits ()
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((cf/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun cf/skip-projects-and-habits-and-single-tasks ()
  (save-restriction
    (widen)
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((org-is-habit-p)
        next-headline)
       ((and cf/hide-scheduled-and-waiting-next-tasks
             (member "WAITING" (org-get-tags-at)))
        next-headline)
       ((cf/is-project-p)
        next-headline)
       ((and (cf/is-task-p) (not (cf/is-project-subtree-p)))
        next-headline)
       (t
        nil)))))

(defun cf/skip-project-tasks-maybe ()
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max))))
           (limit-to-project (marker-buffer org-agenda-restrict-begin)))
      (cond
       ((cf/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (not limit-to-project)
             (cf/is-project-subtree-p))
        subtree-end)
       ((and limit-to-project
             (cf/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       (t
        nil)))))

(defun cf/skip-project-tasks ()
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((cf/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       ((cf/is-project-subtree-p)
        subtree-end)
       (t
        nil)))))


(defun cf/skip-non-project-tasks ()
  (save-restriction
    (widen)
    (let* ((subtree-end (save-excursion (org-end-of-subtree t)))
           (next-headline (save-excursion (or (outline-next-heading) (point-max)))))
      (cond
       ((cf/is-project-p)
        next-headline)
       ((org-is-habit-p)
        subtree-end)
       ((and (cf/is-project-subtree-p)
             (member (org-get-todo-state) (list "NEXT")))
        subtree-end)
       ((not (cf/is-project-subtree-p))
        subtree-end)
       (t
        nil)))))


(defun cf/skip-projects-and-habits ()
  (save-restriction
    (widen)
    (let ((subtree-end (save-excursion (org-end-of-subtree t))))
      (cond
       ((cf/is-project-p)
        subtree-end)
       ((org-is-habit-p)
        subtree-end)
       (t
        nil)))))

(defun cf/skip-non-subprojects ()
  (let ((next-headline (save-excursion (outline-next-heading))))
    (if (cf/is-subproject-p)
        nil
      next-headline)))

(defun cf/skip-non-archivable-tasks ()
  (save-restriction
    (widen)
    ;; Consider only tasks with done todo headings as archivable candidates
    (let ((next-headline (save-excursion (or (outline-next-heading) (point-max))))
          (subtree-end (save-excursion (org-end-of-subtree t))))
      (if (member (org-get-todo-state) org-todo-keywords-1)
          (if (member (org-get-todo-state) org-done-keywords)
              (let* ((daynr (string-to-number (format-time-string "%d" (current-time))))
                     (a-month-ago (* 60 60 24 (+ daynr 1)))
                     (last-month (format-time-string "%Y-%m-" (time-subtract (current-time) (seconds-to-time a-month-ago))))
                     (this-month (format-time-string "%Y-%m-" (current-time)))
                     (subtree-is-current (save-excursion
                                           (forward-line 1)
                                           (and (< (point) subtree-end)
                                                (re-search-forward (concat last-month "\\|" this-month) subtree-end t)))))
                (if subtree-is-current
                    subtree-end ; Has a date in this month or last month, skip it
                  nil))  ; available to archive
            (or subtree-end (point-max)))
        next-headline))))