;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause

;; Some compilers (e.g. SBCL) fail to reload the system with `defpackage' when
;; exports are spread around.  `uiop:define-package' does not have this problem.

#+sb-package-locks
(serapeum:eval-always
  (when (find-package :nyxt)
    (sb-ext:unlock-package :nyxt)))

(uiop:define-package :nyxt
  (:use :cl)
  (:use-reexport :nyxt/utilities :nyxt/types)
  #+nyxt-debug-make-instance
  (:shadow #:make-instance)
  (:export #:use-nyxt-package-nicknames)
  (:documentation "The core package of Nyxt, the infinitely extensible browser.

This package should not be modified by the users.

It's recommended to use the `nyxt-user' package instead to create new functions,
modes, commands, etc."))
#+sb-package-locks
(sb-ext:lock-package :nyxt)

(in-package :nyxt)
(defvar *imports* '((:alexandria #:compose #:curry #:mappend #:rcurry)
                    (:trivia #:match #:multiple-value-match #:lambda-match #:guard)
                    (:nkeymaps #:define-key #:define-keyscheme-map)
                    (:nclasses #:define-generic)
                    (:serapeum #:export-always #:->))
  "Default list of symbol imports used by `nyxt:define-package'.")

(loop :for (package . symbols) in *imports*
      :do (import (mapcar (lambda (symbol) (intern (symbol-name symbol) package))
                          symbols)
                  :nyxt))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (loop :for (nickname package) in
        '((:alex :alexandria-2)
          (:sera :serapeum)
          (:time :local-time)
          (:types :trivial-types)
          (:lpara :lparallel)
          (:hooks :nhooks)
          (:files :nfiles)
          (:j :njson/aliases)
          (:keymaps :nkeymaps)
          (:sym :nsymbols))
        :do (trivial-package-local-nicknames:add-package-local-nickname nickname package :nyxt)))

(defmacro nyxt::use-nyxt-package-nicknames (&optional (package *package*))
  "Define package nicknames in PACKAGE for Nyxt-used libraries.
Effectively makes programming in PACKAGE same as programming in `:nyxt'."
  `(eval-when (:compile-toplevel :load-toplevel :execute)
     (let ((*package* (find-package ,package)))
       (dolist (pkgs (trivial-package-local-nicknames:package-local-nicknames :nyxt))
         (trivial-package-local-nicknames:add-package-local-nickname (first pkgs) (package-name (rest pkgs))
                                                                     (find-package ,package))))))

(defmacro without-package-locks (&body body)
  "Ignore package locks for the duration of the BODY.
Same as `progn' on implementations that don't have package locks."
  #+sb-package-locks
  `(sb-ext:without-package-locks
     ,@body)
  #+(and ecl package-locks)
  `(ext:without-package-locks
     ,@body)
  #-(or sb-package-locks package-locks)
  `(progn ,@body))

(serapeum:export-always 'define-class :nyxt)
(defmacro define-class (name supers slots &rest options)
  "`nclasses:define-class' with automatic types and always-dashed predicates."
  `(nclasses:define-class ,name ,supers ,slots
     ,@(append
        '((:automatic-types-p t)
          (:accessor-name-package :slot-name)
          (:predicate-name-transformer 'nclasses:always-dashed-predicate-name-transformer))
       options)))

(serapeum:export-always 'define-package :nyxt)
(defmacro define-package (name &body options)
  "A helper around `uiop:define-package'.
`:cl' and `:nyxt' are automatically used.
`nyxt::*imports*' are automatically imported."
  (let* ((uses (append (serapeum:keep :use options :key #'first)
                       '((:use :cl :nyxt :nyxt/utilities))))
         (imports (append (serapeum:keep :import-from options :key #'first)
                          (mapcar (lambda (import) (cons :import-from import))
                                  *imports*)))
         (options (remove :use (remove :import-from options :key #'first)
                          :key #'first)))
    `(progn
       (serapeum:eval-always
         (without-package-locks
           (uiop:define-package ,name
             ,@uses
             ,@imports
             ,@options)))
       (nyxt::use-nyxt-package-nicknames ',name)
       #+sb-package-locks
       (sb-ext:lock-package ',name))))

(deftype class-symbol ()
  `(and symbol (satisfies find-class)))

#+nyxt-debug-make-instance
(serapeum:-> make-instance (class-symbol &rest t) t)
#+nyxt-debug-make-instance
(defun make-instance (sym &rest args)
  "This wrapper of `make-instance' can be used from a test suite to check if all
calls are made on valid classes.

The check seems to only work on CCL, but not even everywhere, for instance slot
initforms may not be caught."
  (apply #'cl:make-instance sym args))

(uiop:define-package :nyxt-user
  (:use :cl :nyxt :nyxt/utilities)
  (:import-from :nkeymaps #:define-key #:define-keyscheme-map)
  (:documentation "Package left for the user to fiddle with.  If the
configuration file package is left unspecified, it defaults to this.  It's not
recommended to use `nyxt' itself to avoid clobbering internal symbols.

By default, the `:nyxt' and `:cl' packages are `:use'd.

To import more symbols, you can use the `import' function.
For instance, to access `match' directly (without having to prefix it with
`trivia:', add this at the top of your configuration file:

  (import 'trivia:match)

You can also use package local nicknames if you want to abbreviate package
prefix.
For instance, to be able to use `alex:' and `sera:' in place of `alexandria:'
and `serapeum:':

  (trivial-package-local-nicknames:add-package-local-nickname :alex :alexandria-2 :nyxt-user)
  (trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum :nyxt-user)"))

(trivial-package-local-nicknames:add-package-local-nickname :hooks :nhooks :nyxt-user)
(trivial-package-local-nicknames:add-package-local-nickname :files :nfiles :nyxt-user)
(trivial-package-local-nicknames:add-package-local-nickname :keymaps :nkeymaps :nyxt-user)
