;;; Guile-Git --- GNU Guile bindings of libgit2
;;; Copyright © 2025 Nicolas Graves <ngraves@ngraves.fr>
;;;
;;; This file is part of Guile-Git.
;;;
;;; Guile-Git 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.
;;;
;;; Guile-Git 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 Guile-Git.  If not, see <http://www.gnu.org/licenses/>.

(define-module (git stash)
  #:use-module (rnrs bytevectors)
  #:use-module (system foreign)
  #:use-module (git bindings)
  #:use-module (git structs)
  #:use-module (git types)
  #:use-module (git signature)
  #:use-module (git checkout)
  #:export (STASH-DEFAULT
            STASH-KEEP-INDEX
            STASH-INCLUDE-UNTRACKED
            STASH-INCLUDE-IGNORED

            STASH-APPLY-DEFAULT
            STASH-APPLY-REINSTATE-INDEX

            STASH-SAVE-OPTIONS-VERSION
            STASH-APPLY-OPTIONS-VERSION

            stash-save
            stash-apply
            make-stash-apply-options
            stash-foreach
            stash-drop
            stash-pop))

;;; Define stash flags
(define STASH-DEFAULT 0)
(define STASH-KEEP-INDEX 1)          ;; Don't stash the index changes
(define STASH-INCLUDE-UNTRACKED 2)   ;; Include untracked files
(define STASH-INCLUDE-IGNORED 4)     ;; Include ignored files

;;; Define stash apply flags
(define STASH-APPLY-DEFAULT 0)
(define STASH-APPLY-REINSTATE-INDEX 1) ;; Try to reinstate the stashed index

;; Version identifiers
(define STASH-SAVE-OPTIONS-VERSION 1)
(define STASH-APPLY-OPTIONS-VERSION 1)

(define stash-save
  (let ((proc (libgit2->procedure* "git_stash_save" `(* * * * ,unsigned-int))))
    (lambda* (repository stasher message
                         ;; TODO Other STASH-* flags are untested.
                         #:optional (flags STASH-INCLUDE-UNTRACKED))
      "Save the local modifications to a new stash in REPOSITORY.
STASHER is a signature representing the person stashing the changes.
MESSAGE is an optional message.
FLAGS is a combination of STASH-* flags.
Returns the OID of the commit created for the stash."
      (let ((out (make-oid-pointer)))
        (proc out
              (repository->pointer repository)
              (signature->pointer stasher)
              (string->pointer message)
              flags)
        (pointer->oid out)))))

(define make-stash-apply-options
  (let ((proc (libgit2->procedure* "git_stash_apply_options_init" `(* ,unsigned-int))))
    (lambda* (#:key
              (flags STASH-APPLY-DEFAULT)
              (checkout-options (make-checkout-options)))
      ;; TODO Add (progress-callback #f) option
      "Create a stash apply options structure with the specified parameters.
FLAGS is a combination of STASH-APPLY-* flags.
CHECKOUT-OPTIONS is a checkout options structure.
Returns a stash apply options structure to be used with stash-apply."
      (let ((options (make-stash-apply-options-bytestructure)))
        (proc (stash-apply-options->pointer options) STASH-APPLY-OPTIONS-VERSION)

        (set-stash-apply-options-flags! options flags)
        (set-stash-apply-options-checkout-options! options checkout-options)

        options))))

(define stash-apply
  (let ((proc (libgit2->procedure* "git_stash_apply" `(* ,size_t *))))
    (lambda* (repository index #:optional (options (make-stash-apply-options)))
      "Apply a single stashed state from the stash list of REPOSITORY.
INDEX is the position of the stash in the list (0 is the most recent stash).
OPTIONS is an optional stash apply options structure."
      (proc (repository->pointer repository)
            index
            (stash-apply-options->pointer options)))))

(define stash-foreach
  (let ((proc (libgit2->procedure* "git_stash_foreach" '(* * *))))
    (lambda (repository callback)
      "Loop over all the stashed states in REPOSITORY and issue CALLBACK for each one.
CALLBACK is called with three arguments: the stash index, stash message, and stash ID."
      (let ((callback* (procedure->pointer int
                                           (lambda (index message id payload)
                                             (callback index
                                                      (pointer->string message)
                                                      (pointer->oid id)))
                                           (list size_t '* '* '*))))
        (proc (repository->pointer repository) callback* %null-pointer)))))

(define stash-drop
  (let ((proc (libgit2->procedure* "git_stash_drop" `(* ,size_t))))
    (lambda (repository index)
      "Remove a stashed state from the stash list of REPOSITORY.
INDEX is the position of the stash in the list (0 is the most recent stash)."
      (proc (repository->pointer repository) index))))

(define stash-pop
  (let ((proc (libgit2->procedure* "git_stash_pop" `(* ,size_t *))))
    (lambda* (repository index #:optional (options (make-stash-apply-options)))
      "Apply a stashed state at INDEX and remove it from the stash list of REPOSITORY.
INDEX is the position of the stash in the list (0 is the most recent stash).
OPTIONS is an optional stash apply options structure."
      (proc (repository->pointer repository)
            index
            (stash-apply-options->pointer options)))))
