;;; aidermacs-backends.el --- Backend dispatcher for aidermacs -*- lexical-binding: t; -*-
;; Author: Mingde (Matthew) Zeng <matthewzmd@posteo.net>
;; Keywords: ai emacs llm aider ai-pair-programming tools
;; URL: https://github.com/MatthewZMD/aidermacs
;; SPDX-License-Identifier: Apache-2.0

;; This file is not part of GNU Emacs.

;;; Commentary:

;; Backend dispatcher for Aidermacs, allowing choice between
;; different backend implementations for Aider process.
;; Supports Comint and VTerm backends.
;;
;; Features:
;; - Backend selection via `aidermacs-backend` variable
;; - Abstracts backend-specific Aider functions
;; - Manages output history and callbacks

;;; Code:

(require 'aidermacs-backend-comint)
(require 'aidermacs-backend-vterm)

(declare-function aidermacs-run-vterm "aidermacs-backend-vterm")
(declare-function aidermacs--send-command-vterm "aidermacs-backend-vterm")
(declare-function aidermacs--prepare-for-code-edit "aidermacs-output")
(declare-function aidermacs-project-root "aidermacs")
(declare-function aidermacs--get-files-in-session "aidermacs")

(defgroup aidermacs-backends nil
  "Backend dispatcher for aidermacs."
  :group 'aidermacs)

(defcustom aidermacs-backend 'comint
  "Backend to use for the aidermacs process.
Options are `comint' (the default) or `vterm'.  When set to `vterm',
aidermacs launches a fully functional vterm buffer instead
of using a comint process."
  :type '(choice (const :tag "Comint" comint)
                 (const :tag "VTerm" vterm)))

(defvar-local aidermacs--current-callback nil
  "Store the callback function for the current command.")

(defvar-local aidermacs--in-callback nil
  "Flag to prevent recursive callbacks.")

(defcustom aidermacs-before-run-backend-hook nil
  "Hook run before the aidermacs backend is startd."
  :type 'hook)

(defconst aidermacs-prompt-regexp "^[^[:space:]<]*>[[:space:]]+$"
  "Regexp to match Aider's command prompt.")

(defvar aidermacs-question-regexp "(Y)es/(N)o"
  "Regexp to detect aider Y/N confirmation prompts.
Matches all confirmation prompts including:
- Edit the files? (Y)es/(N)o [Yes]:
- Attempt to fix lint errors? (Y)es/(N)o [Yes]:
- Add file to the chat? (Y)es/(N)o/(D)on't ask again [Yes]:
- Add file to the chat? (Y)es/(N)o/(A)ll/(S)kip all/(D)on't ask again [Yes]:
- Add URL to the chat? (Y)es/(N)o/(D)on't ask again [Yes]:
- Add URL to the chat? (Y)es/(N)o/(A)ll/(S)kip all/(D)on't ask again [Yes]:
The (Y)es/(N)o pattern is the common denominator across all variants.

Limitations of this permissive regexp:
1. False positives: Any text containing \"(Y)es/(N)o\" will be treated as a prompt,
   potentially triggering notifications prematurely. This includes:
   - User messages discussing confirmation prompts
   - AI responses containing the pattern as example text
   - Code snippets or documentation mentioning the pattern
2. False positive probability: Moderate. The pattern is specific enough to avoid
   most casual text, but technical discussions about aider's prompts may trigger it.
   In normal usage, false positives are infrequent but possible.
3. No context awareness: The regexp doesn't check if the pattern appears at the
   end of a line (where prompts usually are) or is followed by \" [Yes]:\".
   This increases recall but reduces precision.
4. Timing implications: Premature detection may cause notifications to be sent
   before the user has actually seen the prompt, reducing their usefulness.")

;; Backend dispatcher functions
(defun aidermacs-run-backend (program args buffer-name)
  "Run aidermacs using the selected backend.
PROGRAM is the aidermacs executable path.  ARGS are command line arguments.
BUFFER-NAME is the name for the aidermacs buffer."
  (message "Running %s with %s" program args)
  ;; make a copy of process-environment, so that secrets set in the hook is only visible by aider
  (let ((process-environment process-environment))
    (run-hooks 'aidermacs-before-run-backend-hook)
    (cond
     ((eq aidermacs-backend 'vterm)
      (aidermacs-run-vterm program args buffer-name))
     (t
      (aidermacs-run-comint program args buffer-name)))))

(defun aidermacs--is-aidermacs-buffer-p (&optional buffer)
  "Check if BUFFER is any type of aidermacs buffer.
If BUFFER is nil, check the current buffer.
Returns non-nil if the buffer has either `aidermacs-comint-mode' or
`aidermacs-vterm-mode' enabled."
  (let ((buf (or buffer (current-buffer))))
    (with-current-buffer buf
      (or (derived-mode-p 'aidermacs-comint-mode)
          (bound-and-true-p aidermacs-vterm-mode)))))

(defun aidermacs--send-command-backend (buffer command &optional redirect)
  "Send command to buffer using the appropriate backend.
BUFFER is the target buffer.  COMMAND is the text to send.
If REDIRECT is non-nil it redirects the output (hidden) for comint backend."
  (if (eq aidermacs-backend 'vterm)
      (aidermacs--send-command-vterm buffer command)
    (if redirect
        (aidermacs--send-command-redirect-comint buffer command)
      (aidermacs--send-command-comint buffer command))))

(provide 'aidermacs-backends)

;;; aidermacs-backends.el ends here
