;;; jao-afio.el --- workspaces in just one frame  -*- lexical-binding: t; -*-

;; Copyright (C) 2020, 2021, 2022, 2024, 2025, 2026  jao

;; Author: jao <mail@jao.io>
;; Keywords: frames

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; initialisation
(require 'multisession)
(require 'cl-lib)
(require 'jao-doc-session)

(defvar jao-afio-use-frames (not window-system))
(defvar jao-afio-frame-parameters nil)
(defvar jao-afio-persist-configurations nil)

(defvar jao-open-doc-fun 'find-file)
(defvar jao-afio-mail-function 'gnus)
(defvar jao-afio-use-w3m nil)
(defvar jao-afio-auto-toggle nil)
(defvar jao-afio-switch-hook nil)

(defvar jao-afio--configs '(?c ?w ?g ?p ?s ?t))
(defvar jao-afio--previous-config (car jao-afio--configs))
(defvar jao-afio--config-names
  '((?c . "main") (?s . "scratch") (?g . "mail")
    (?p . "docs") (?w . "web") (?t . "chats")))

(define-multisession-variable jao-afio-configurations nil)

(defmacro jao-afio-add-frame-parameters (name &rest params)
  `(add-to-list 'jao-afio-frame-parameters
                '(,(if name (format "%s" name) 'def) ,params)))

(defun jao-afio--current-wc ()
  (window-state-get (frame-root-window) t))

(defun jao-afio-save-configuration ()
  (interactive)
  (when-let* ((c (jao-afio--current-config)))
    (setf (alist-get c (multisession-value jao-afio-configurations))
          (jao-afio--current-wc))
    (message "Configuration of %s saved" (jao-afio-frame-name c))))

(defun jao-afio-restore-configuration (c)
  (when-let* ((_ jao-afio-persist-configurations)
              (c (alist-get c (multisession-value jao-afio-configurations))))
    (window-state-put c)
    t))

(defun jao-afio--current-config (&optional c f)
  (when c (modify-frame-parameters f `((afio . ,c))))
  (frame-parameter f 'afio))

(defun jao-afio--set-up-frame (cfg)
  (when jao-afio-use-frames
    (let ((fn (jao-afio-frame-name cfg)))
      (set-frame-name fn)
      (when-let* ((params (cadr (or (assoc fn jao-afio-frame-parameters)
                                    (assoc 'def jao-afio-frame-parameters)))))
        (modify-frame-parameters nil params)))))

(defun jao-afio--init (&optional f)
  (interactive)
  (jao-afio--current-config ?c)
  (if jao-afio-use-frames
      (jao-afio--set-up-frame ?c)
    (window-configuration-to-register ?c)))

(defun jao-afio--check-frame ()
  (unless (jao-afio--current-config)
    (jao-afio--init (window-frame (get-buffer-window (current-buffer))))))

(defvar jao-afio-side-buffers '("*eldoc*"))

;;; utilities
(defun jao-afio-trisect (&optional force)
  (interactive)
  (let ((fw (frame-width))
        (display-buffer-alist nil))
    (cond ((or force (>= fw 240))
           (let ((b (current-buffer)))
             (delete-other-windows)
             (switch-to-buffer (other-buffer b))
             (split-window-horizontally)
             (switch-to-buffer (other-buffer b))
             (split-window-horizontally)
             (switch-to-buffer b)
             (balance-windows)
             (when-let* ((b (seq-keep #'get-buffer jao-afio-side-buffers)))
               (other-window -1)
               (switch-to-buffer (car b))
               (other-window 1))))
          ((> fw 162)
           (delete-other-windows)
           (split-window-horizontally)
           (switch-to-buffer (other-buffer))))))

;;; session openers
;;;###autoload
(defun jao-afio-open-pdf-session (&optional docs)
  (interactive)
  (dolist (doc (or docs (jao-doc-session)))
    (when (and (stringp doc) (file-exists-p doc))
      (if (jao-pdf-is-pdf-file doc) (jao-open-doc doc) (find-file doc))
      (other-window 1)))
  (other-window 1))

(defun jao-afio-open-doc ()
  (interactive)
  (delete-other-windows)
  (split-window-right 80)
  (let ((docs (cl-remove-if-not 'jao-doc-session-is-doc (buffer-list))))
    (if (car docs)
        (progn (switch-to-buffer (car docs))
               (switch-to-buffer-other-window (or (cadr docs) (car docs))))
      (when-let* ((docs (jao-doc-session)))
        (when (y-or-n-p (format "Load saved session? (%d docs)" (length docs)))
          (jao-afio-open-pdf-session docs))))))

(declare-function w3m "w3m")
(declare-function notmuch "notmuch")
(declare-function jao-eww-session-eww-buffers "jao-eww-session")
(declare-function jao-eww-session-load "jao-eww-session")

(defun jao-afio--open-eww-session ()
  (if-let* ((b (jao-eww-session-eww-buffers)))
      (switch-to-buffer (car b))
    (jao-eww-session-load)))

;;;###autoload
(defun jao-afio-open-www ()
  (interactive)
  (require 'jao-eww-session)
  (if (< (frame-width) 160)
      (if jao-afio-use-w3m (w3m) (jao-afio--open-eww-session))
    (if jao-afio-use-w3m
        (progn (delete-other-windows)
               (split-window-right)
               (w3m))
      (jao-afio-trisect)
      (jao-afio--open-eww-session)
      (let ((b (current-buffer)))
        (other-window 1)
        (switch-to-buffer (car (jao-eww-session-eww-buffers b)))
        (other-window 1)
        (switch-to-buffer (car (jao-eww-session-eww-buffers b)))
        (other-window 1)))))

;;;###autoload
(defun jao-afio-open-gnus ()
  (interactive)
  (jao-org-agenda)
  (calendar)
  (find-file (expand-file-name "inbox.org" org-directory))
  (delete-other-windows)
  (gnus)
  (jao-gnus--set-summary-line))

(defun jao-afio--mail-sidebar ()
  (other-window 1)
  (delete-other-windows-vertically)
  (find-file (expand-file-name "inbox.org" org-directory))
  (set-window-dedicated-p nil t)
  (split-window-below (/ (window-height) 3))
  (other-window 1)
  (jao-org-agenda)
  (set-window-dedicated-p nil t)
  (split-window-below -8)
  (other-window 1)
  (switch-to-buffer "*Calendar*")
  (set-window-dedicated-p nil t)
  (other-window 1))

(defun jao-afio--open-mail (fun)
  (unless (get-buffer "*Calendar*") (calendar))
  (delete-other-windows)
  (if (< (frame-width) 160)
      (funcall fun)
    (split-window-horizontally -80)
    (funcall fun)
    ;; (set-window-dedicated-p nil t)
    (jao-afio--mail-sidebar)))

;;;###autoload
(defun jao-afio-open-mail ()
  (interactive)
  (cond ((eq 'gnus jao-afio-mail-function) (jao-afio-open-gnus))
        ((eq 'notmuch jao-afio-mail-function) (jao-afio--open-mail 'notmuch))
        (t (jao-afio-trisect))))

;;;###autoload
(defun jao-afio-reset ()
  (interactive)
  (let ((c (jao-afio--current-config)))
    (unless (jao-afio-restore-configuration c)
      (delete-other-windows)
      (cl-case c
        (?w (jao-afio-open-www))
        (?g (jao-afio-open-mail))
        (?p (jao-afio-open-doc))
        (t (jao-afio-trisect)))
      (when jao-afio-persist-configurations (jao-afio-save-configuration)))))

;;; go to frame
(defsubst jao-afio--find-frame (c)
  (seq-find (lambda (f) (eq (jao-afio--current-config nil f) c)) (frame-list)))

(defun jao-afio-frame-name (&optional c)
  (alist-get (or c (jao-afio--current-config)) jao-afio--config-names))

(defun jao-afio-frame-no (&optional c)
  (alist-get (or c (jao-afio--current-config))
             '((?s . 0) (?c . 1) (?g . 2) (?w . 3) (?p . 4) (?t . 5))))

(defun jao-afio--goto-frame (next &optional reset)
  (jao-afio--check-frame)
  (let ((current (jao-afio--current-config)))
    (if (and jao-afio-auto-toggle
             (eq next current)
             (not reset)
             (not (eq current jao-afio--previous-config)))
        (jao-afio--goto-frame jao-afio--previous-config)
      (when (or reset (not (eq next current)))
        (if jao-afio-use-frames
            (let ((f (jao-afio--find-frame next)))
              (select-frame-set-input-focus (or f (make-frame)))
              (when (setq reset (or reset (not f)))
                (jao-afio--set-up-frame next)))
          (window-configuration-to-register (jao-afio--current-config))
          (when (and (not reset) (get-register next))
            (ignore-errors (jump-to-register next)))
          (setq reset (or reset (not (get-register next)))))
        (jao-afio--current-config next)
        (unless (eq current next) (setq jao-afio--previous-config current))
        (when reset (jao-afio-reset)))
      (run-hooks 'jao-afio-switch-hook))))

(defun jao-afio-goto-main (&optional reset)
  (interactive "P")
  (jao-afio--goto-frame ?c reset))

(defun jao-afio-goto-mail (&optional reset)
  (interactive "P")
  (jao-afio--goto-frame ?g reset))

(defun jao-afio-goto-docs (&optional reset)
  (interactive "P")
  (jao-afio--goto-frame ?p reset))

(defun jao-afio-goto-www (&optional reset)
  (interactive "P")
  (jao-afio--goto-frame ?w reset))

(defun jao-afio-toggle ()
  (interactive)
  (jao-afio--goto-frame jao-afio--previous-config))

(defun jao-afio-goto-scratch (&optional one-win)
  (interactive "P")
  (jao-afio--goto-frame ?s one-win)
  (when one-win (delete-other-windows)))

(defun jao-afio-goto-chats (&optional reset)
  (interactive "P")
  (jao-afio--goto-frame ?t reset))

;;;###autoload
(defun jao-afio-goto-nth (n)
  (cl-case n
    ((-1) (jao-afio-goto-scratch t))
    ((0) (jao-afio-goto-scratch))
    ((1) (jao-afio-goto-main))
    ((2) (jao-afio-goto-mail))
    ((3) (jao-afio-goto-www))
    ((4) (jao-afio-goto-docs))
    ((5) (jao-afio-goto-chats))))

;;;###autoload
(defun jao-afio-pop-to-buffer (n buff)
  (interactive "NFrame number: \nBBuffer: ")
  (jao-afio-goto-nth n)
  (pop-to-buffer buff))

;;; setup
;;;###autoload
(defun jao-afio-setup (&optional use-frames)
  (setq jao-afio-use-frames use-frames)
  (jao-afio--init))

(provide 'jao-afio)
;;; jao-afio.el ends here
