;;; oai.el --- AI-LLM chat blocks for org-mode -*- lexical-binding: t; -*-

;; Copyright (C) 2025 github.com/Anoncheg1,codeberg.org/Anoncheg
;; Author: <github.com/Anoncheg1,codeberg.org/Anoncheg>
;; Keywords: org, comm, url, link
;; URL: https://codeberg.org/Anoncheg/emacs-oai
;; Package-Version: 0.3
;; Package-Revision: 4813a1578818
;; Created: 27 dec 2025
;; Package-Requires: ((emacs "29.1"))
;; Optional dependency: ((org-links "0.2"))
;; SPDX-License-Identifier: AGPL-3.0-or-later

;;; License

;; This file is NOT part of GNU Emacs.

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU Affero 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 Affero General Public License for more details.

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

;; Licensed under the GNU Affero General Public License, version 3 (AGPLv3)
;; <https://www.gnu.org/licenses/agpl-3.0.en.html>

;;; Commentary:

;; OAI as a minor mode extend Org major mode with "ai block" that
;;   allows you to interact with the OpenAI-compatible REST APIs.

;; OAI was inspired by org-ai package of Robert Krahn <https://github.com/rksm/org-ai>

;; It allows you to:
;; - Use #+begin_ai..#+end_ai blocks for org-mode
;; - Call multiple requests from multiple block and buffers in parallel.
;; - Use tags `@Backtrace` @Bt and Org links to insert target in query.
;; - Highlighting for major elements.
;; - Autofilling, hooks, powerful debugging
;; - Noweb and tangling
;; - Customization for engineering, there is :chain for sequence of
;;   calls out-of-the-box.
;;
;; The Internet connection uses the built-in libraries url.el and url-http.el.
;;
;; See see https://github.com/Anoncheg1/emacs-oai for the full set
;; of features and setup instructions.
;;
;;; Configuration:

;; (add-to-list 'load-path "path/to/oai") ; (optional)
;; (require 'oai)
;; (setq oai-restapi-con-token "xxx") ; oai-restapi.el (optional)
;; (add-hook 'org-mode-hook #'oai-mode) ; oai.el
;;
;; ;; Optional hooks:
;; (add-hook 'oai-block-after-chat-insertion-hook
;;   #'oai-optional-remove-distant-empty-lines-hook-function)
;; (add-hook 'oai-block-after-chat-insertion-hook
;;   #'oai-optional-remove-headers-hook-function)

;; First hook remove empty lines if there is too much of them in response.
;; Second fix conflict with Org mode when LLM return string starting
;;  with "*" character.

;; You will need an OpenAI API key-token.
;; It can be stored in variable or in file ~/.authinfo.gpg with format:
;;  "machine api.openai.com login oai password <your-api-key>"
;; The file is picked up when the package is loaded.
;;
;; Keys binded by default:
;; - In block #+begin_ai..#+end_ai blocks:
;;     - C-c C-c - to send the text to the OpenAI API and insert a response
;;     - C-c . - to inspect raw data (and C-u C-c .)
;;     - C-c C-.  - to see url.el raw HTTP data (working only during request)
;;     - M-h - recursive mark element (C-u M-h - mark chat message)
;;     - C-c C-t - set :max-tokens
;;     - C-g - to stop requst (in debug-buffer - stop all requests).

;;;; Notes

;; For links pointing to to file with ".ai" extension, they will be
;;  included directly without wrapping in markdown block as chat extension.

;;;; Customization:

;; M-x customize-group RET oai
;; M-x customize-group RET oai-faces

;; Terms:
;; - chat roles or prefixes - [AI]: [ME:]
;; - parts or messages - major parts of chat with prefixes of roles
;; - two steps of preparing messages:
;;   1) apply additional system messages from info. `oai-block--prepare-chat-messages'
;;   2) expand links and noweb references. `oai-block-tags-get-content-ai-messages' and others.

;;;; Known issues:

;; - Exporting dont properly format markdown code blocks and quotes "> "

;;;; Other packages:

;; - Modern navigation in major modes https://github.com/Anoncheg1/firstly-search
;; - Search with Chinese	https://github.com/Anoncheg1/pinyin-isearch
;; - Ediff no 3-th window	https://github.com/Anoncheg1/ediffnw
;; - Dired history		https://github.com/Anoncheg1/dired-hist
;; - Selected window contrast	https://github.com/Anoncheg1/selected-window-contrast
;; - Copy link to clipboard	https://github.com/Anoncheg1/emacs-org-links
;; - Solution for "callback hell"	https://github.com/Anoncheg1/emacs-async1
;; - Restore buffer state	https://github.com/Anoncheg1/emacs-unmodified-buffer1
;; - outline.el usage		https://github.com/Anoncheg1/emacs-outline-it

;;;; Donate:

;; - BTC (Bitcoin) address: 1CcDWSQ2vgqv5LxZuWaHGW52B9fkT5io25
;; - USDT (Tether) address: TVoXfYMkVYLnQZV3mGZ6GvmumuBfGsZzsN
;; - TON (Telegram) address: UQC8rjJFCHQkfdp7KmCkTZCb5dGzLFYe2TzsiZpfsnyTFt9D

;;;; TODO:

;; - make oai-variable.el and pass them to -api.el functions as parameters.
;; - provide ability to replace url-http with plz or oai-restapi with llm(plz)
;; - implement "#+PROPERTY: var foo=1" and "#+begin_ai :var
;;       foo=1" and to past to text in [foo]
;; - more tags? like: "Fix @problems then document the
;;         changes in @/CHANGELOG.md" @url, @file, @folder, @header? (Org)
;; - use oai-restapi-prepare-content for :chain
;; - Think about to pass callback for writing to chain implementations
;;    and main implementation, to make it more general.
;; - make org-block-tags optional or not
;; - key to enable full Org highlighting? think about it
;; - fontify latex [[file:/usr/share/emacs/30.2/lisp/org/org.el::16097::(defun org-inside-latex-macro-p ()]]
;; [[file:/usr/share/emacs/30.2/lisp/textmodes/tex-mode.el::1277::(setq-local font-lock-defaults]]
;; - small markdown mode on highlighting
;; - simple Elisp function to ask LLM
;; - add guide to use `oai-restapi-request' and with retries for simple
;;   ELisp LLM call and get result for TAB key and some place in buffer.
;; - add option for tag to expand only the last user prompt or in all.
;; - C-c C-k should jump to current bexgining of message, not next
;; - add buttons: 1) generate button based on LLM answer 2) handle clicking.
;; - default requst as one plist configuration
;; - support for https://github.com/LionyxML/markdown-ts-mode
;; - check big markdown-mode for insights for us.
;; - stop previous request if new one called with all equal parameters
;; - fill-paragraph should not break markdown quotes and bolds
;; - make font-lock better like in [[file:/usr/share/emacs/30.2/lisp/gnus/message.el
;; ::1701::(defun message-font-lock-make-cited-text-matcher (level maxlevel)]]
;; - make `oai-expand-block' executed with `org-babel-expand-src-block'.
;; - provide place or hook to add custom expansion of link to one line for user defined mode
;; - support vars as tags    https://orgmode.org/manual/Environment-of-a-Code-Block.html
;; - write test for `oai-block-tags-get-content' and `oai-block-tags--get-content-at-point-org'
;;   and noweb parameter usage
;; - add advanced forward section that check what type of region is
;;   active and do appropriate forward with preserving region
;; - noweb evaluation with support of variables with some text. like <<call("as")>>
;; - rebind keys to C-x C-a
;; - function to replace "^[\s+]- **word1 [word2]:**" to "^^[\s+]- word1 [word2] :: " and highligh it.
;; - fix highlight to highlight when there is only "#+end_ai"
;; - create function that insert :max-token and any for given int or value, like
;;  `org-babel-insert-header-arg'
;; - remove bound to Org mode from oai-block-tags for more support for
;;  .ai file extension without ai block
;; - pre-call: and post-call: for preparation and postprocessing and
;;  pre-/post-service and model. or guide for hooks
;; - implement my/org-execute-in-source-block for markdown that use
;;  `org-src--edit-element', for that `org-babel-do-in-edit-buffer'
;;  should be rewrited, in which org-edit-src-code should be executed
;;  with content, not current block
;; - unbind dependency to each other of `oai-restapi' and `oai-block-tags'
;;   create oai-block-chat and collect all functions that works with chat prefixes.
;;   includes: oai-block -> oai-block-chat -> oai-restapi
;; - add optional function to put text in markdown language block to the
;;  begining of the line by removing indentation
;; - make key to remove all messages and left only the last
;; - support "C-c '" (call-interactively 'org-edit-special)

;;; Code:

;; Touch: Pain, water and warm.

;; -=-= includes
(require 'oai-debug)
(require 'oai-block)
(require 'oai-block-tags) ; `oai-block-tags-replace' for `oai-expand-block'
(require 'oai-restapi)
(require 'oai-prompt) ; for `oai-prompt-request-chain'

;; -=-= Customs and groups
(defgroup oai nil
  "OAI package customization."
  :group 'oai)

(defgroup oai-faces nil
  "Faces for OAI blocks."
  :tag "OAI Faces"
  :group 'oai)

(defcustom oai-fontification-flag t
  "Non-nil means enable fontification for markdown and Org elements in block."
  :type 'boolean
  :group 'oai)

(defcustom oai-req-type-functions (list :chat		#'oai-restapi-request-prepare ; at oai-restapi.el
                                        :completion	#'oai-restapi-request-prepare
                                        :chain		#'oai-prompt-request-chain) ; at oai-prompt.el
  "Custom variants to execute request.
If you specify :chain at block parameters line, associated function will
 be called.  See `oai-call-block' and `oai-restapi-request-prepare' for
 parameters."
  :type '(plist :key-type symbol
                :value-type function
                :tag "Property list (symbol => funcion)")
  :group 'oai)

;; -=-= C-c C-c main interface

(defun oai-ctrl-c-ctrl-c ()
  "Remove result and parse ai block header parameters.
Returning t is Org requirement."
  (when (oai-block-p)
    (oai-block-remove-result)
    (oai-call-this-or-that #'oai-restapi-request-prepare
                           oai-req-type-functions
                           (oai-parse-org-header))
    t))

(defun oai-call-this-or-that (fn-default fn-list args)
  "If you specify :chain in ai block, we call related function.
FN-DEFAULT is `oai-restapi-request-prepare' FN-LIST is
`oai-req-type-functions' variable.
ARGS is `oai-parse-org-header' result."
  (let ((info (car (last args)))
        called)
    ;; loop over `oai-req-type-functions'
    (while (and fn-list (not called))
      (let ((key (pop fn-list))
            (fn (pop fn-list)))
        (when (and fn ; skip keys with missing value
                   (not (eq 'x (alist-get key info 'x)))) ; check key exist in info
          (setq called (apply fn
                              (cons (intern (substring (symbol-name key) 1)) ; key to symbol
                                    args))))))  ; (apply fn args)
    (unless called ; executed if key exist but evaluation return nil or key not exist
      (apply fn-default (cons 'chat args))))) ; call default function


(defun oai-parse-org-header ()
  "Parsing ai block header and parameters.
Return list of arguments args."
  (let* ((element (oai-block-p)) ; oai-block.el
         (info (oai-block-get-info element)) ; ((:max-tokens . 150) (:service . "together") (:model . "xxx")) ; oai-block.el
         (sys-prompt-for-all-messages (or (not (eql 'x (alist-get :sys-everywhere info 'x)))
                                          (org-entry-get-with-inheritance "SYS-EVERYWHERE") ; org
                                          oai-restapi-default-inject-sys-prompt-for-all-messages)) ; oai-restapi.el
         (sys-prompt (or (org-entry-get-with-inheritance "SYS") ; org
                         (oai-block--get-sys :info info ; oai-block.el
                                             :default oai-restapi-default-chat-system-prompt)))
         (noweb-control (or (org-babel-noweb-p info :eval)
                            (org-entry-get (point) "oai-noweb" t)))) ; oai-restapi.el variable
    ;; - Process Org params and call agent
    (oai-block--let-params info
                           ;; format: (variable optional-default type)
                           ((service oai-restapi-con-service string) ; oai-restapi.el
                            (model (car (oai-restapi--get-values oai-restapi-con-model service)) :type string)
                            (max-tokens oai-restapi-default-max-tokens :type number)
                            (top-p nil :type number)
                            (temperature nil :type number)
                            (frequency-penalty nil :type number)
                            (presence-penalty nil :type number)
                            (stream t :type bool))
                           ;; body
                           (unless model
                             (user-error "Model not specified nor in ai block nor in oai-restapi-con-model.  To disable model completely set it to \"nil\""))
                           (when (string-equal-ignore-case model "nil")
                             (setq model nil)) ; if specified as "nil" string explicitly, to disable.
                           (list element noweb-control sys-prompt sys-prompt-for-all-messages ; message
                                 model max-tokens top-p temperature frequency-penalty presence-penalty service stream ; model params
                                 info))))

;; -=-= interactive fn: key M-x: oai-expand-block
(defun oai-expand-block-deep ()
  "Output almost RAW information about request with headers and messages.
Return list of strings to print."
  (seq-let (element noweb-control sys-prompt sys-prompt-for-all-messages model max-tokens top-p temperature frequency-penalty presence-penalty service stream info) (oai-parse-org-header)
    (let* ((req-type (oai-block--get-request-type info))
           (max-tokens-string (when (and max-tokens
                                         oai-restapi-add-max-tokens-recommendation)
                                (oai-restapi--get-length-recommendation max-tokens)))
           (messages (unless (eql req-type 'completion)
                       ;; - split content to messages
                       (oai-block-tags-get-content-ai-messages
                        element
                        noweb-control
                        nil ; links-only-last
                        nil ; not-clear-properties
                        nil ; ai-block-markers
                        nil ; disable-tags
                        req-type sys-prompt sys-prompt-for-all-messages max-tokens-string)))) ; for else see :prompt
      (list
       (oai-restapi--get-endpoint messages service)
       (oai-restapi--get-headers service)
       (oai-restapi--payload :prompt (when (eql req-type 'completion) (oai-block-get-content element t)) ; legacy
                             :messages messages
			     :model model
			     :max-tokens max-tokens
			     :temperature temperature
			     :top-p top-p
			     :frequency-penalty frequency-penalty
			     :presence-penalty presence-penalty
			     :service service
			     :stream stream)))))

(defun oai-expand-block (arg)
  "Show a temp buffer with what the ai block expands to.
If there is ai block at current position in current buffer.
This is what will be sent to the api.  ELEMENT is the ai block.
Like `org-babel-expand-src-block'.
Set `help-window-select' variable to to t to get focus.
When universal  ARG specifide  output more  raw information  splitted by
messages.
Return expanded content if at current point of current buffer supported
block was found, otherwise nil."
  ; org-babel-expand-src-block put overlay with `org-src--make-source-overlay'
  ; We add text properties in `oai-block-tags--replace-last-regex-smart'
  (interactive "P")
  (when-let* ((element (oai-block-p)) ; (oai-block-tags--block-at-point))) ; oai-block.el
              (res-str (if arg
                           (pp-to-string (oai-expand-block-deep))
                         ;; - just content with expanded links:
                         (oai-block-tags-get-content element
                                                     t  ; noweb-control
                                                     nil ; links-only-last
                                                     t ; not-clear-properties
                                                     ))))
    (if (called-interactively-p 'any)
        (let ((buf (get-buffer-create "*OAI Preview*")))
          (with-help-window buf (with-current-buffer buf
                                  (insert res-str)))
          (switch-to-buffer buf)
          t)
      ;; else
      res-str)))

;; -=-= interactive fn: key C-g: keyboard quit
(defun oai-keyboard-quit ()
  "Keyboard quit advice.
- If there is an active region at current position in current buffer, do
  nothing (normal \\<mapvar> & \\[keyboard-quit] will deactivate it).
- in debug-buffer - kill all requests."
  (interactive)
  ;; Checks:
  ;; - 1) no region mode?
  (when (not (region-active-p))
    ;; - 2) oai debug buffer?
    (if (string-equal (buffer-name (current-buffer)) oai-debug-buffer) ; in debug-buffer - kill all
        (oai-restapi-stop-all-url-requests)
      ;; - else: 3) oai-mode in current buffer or
      (when (and (bound-and-true-p oai-mode)
                     (not (minibufferp (window-buffer (selected-window))))) ; not in minubuffer
        ;; - stop current request
        (if (bound-and-true-p oai-debug-buffer)
            ;; - show all errors in debug mode
            (call-interactively #'oai-restapi-stop-url-request) ; oai-restapi.el
          ;; else - suppress error in normal mode
          (condition-case _
              (call-interactively #'oai-restapi-stop-url-request) ; oai-restapi.el
            (error nil)))))))

;; -=-= interactive fn: M-x oai-toggle-debug
(defalias 'oai-toggle-debug #'oai-debug-toggle)

;; -=-= fn: Help function to rebind major mode with chaining
(defun oai--call-next-remap-protected (command &optional seen)
  "Call the next remapping of COMMAND, skipping any commands already in SEEN.
If no further remappings found, calls COMMAND interactively if possible."
  (let ((minor-mode-map-alist (cdr minor-mode-map-alist)))
    (let ((binding (key-binding (vector 'remap command))))
      (cond
       ;; No binding found, or recursion, fallback to original
       ((or (null binding) (memq binding seen))
        (when (commandp command)
          (call-interactively command)))
       ;; Valid binding, try further
       ((commandp binding)
        (oai--call-next-remap-protected command (cons binding seen)))))))

(defun oai--call-next-key-remap-protected (key &optional seen)
  "Call the next binding of KEY, skipping handlers already in SEEN.
If no further binding found, calls the major mode's or global binding.
KEY is a string representing the keystroke.
SEEN is a list of commands already called, used to prevent recursion."

  ;; Locally shadow minor-mode-map-alist to remove the highest-priority minor mode map.
  (let ((minor-mode-map-alist (cdr minor-mode-map-alist)))
    ;; Find the current binding for KEY after skipping the top minor mode.
    (let ((binding (key-binding (kbd key) nil nil)))
      (cond
       ;; If no binding found or we've already seen this binding, try major mode and then global map.
       ((or (null binding) (memq binding seen))
        ;; Attempt to find the binding in the major mode's keymap.
        (let* ((major-mode-map (current-local-map))
               (binding-major (and major-mode-map (lookup-key major-mode-map (kbd key)))))
          (if (commandp binding-major)
              ;; If found and it's a command, call interactively.
              (call-interactively binding-major)
            ;; Otherwise, try the global map for the key.
            (let ((global-binding (key-binding (kbd key) t t)))
              (if (commandp global-binding)
                  (call-interactively global-binding)
                ;; If no valid binding anywhere, notify the user.
                (message "No valid binding for %s" key))))))
       ;; If binding is a command, recursively try to find the next remapped binding,
       ;; and add this binding to SEEN for recursion protection.
       ((commandp binding)
        (oai--call-next-key-remap-protected key (cons binding seen)))
       ;; Handle the case where binding is not a command (function, lambda, etc.).
       (t
        (message "Binding for %s is not a command" key))))))

;; -=-= interactive fns: Org keys
(defun oai-expand-block-org ()
  "Show a temp buffer with what the ai block expands to."
  (interactive)
  (if (not (call-interactively #'oai-expand-block))
    ;; else
    (oai--call-next-key-remap-protected "C-c .")))

(defun oai-set-max-tokens-org ()
  "Jump to header of ai block and set max-tokens."
  (interactive)
  (if (oai-block-p)
      (oai-block-set-block-parameter :max-tokens oai-restapi-default-max-tokens)
    ;; else
    (oai--call-next-key-remap-protected "C-c C-t")))

;; -=-= interactive fns: Org keys remapings
(defun oai-mark-at-point-org (&optional arg)
  "Call `org-mark-element' if cant mark element of ai block.
Works if cursor in ai block, otherwise call original function.
Increase region at next execution.
If optional argument ARG is non-nil, mark whole content of ai block."
  (interactive "P")
  (if (oai-block-p)
      (oai-block-mark-at-point arg)
    ;; else
    (oai--call-next-remap-protected #'org-mark-element))) ; #'mark-paragraph

(defun oai-fill-paragraph ()
  "Call `org-fill-paragraph' to selected item in ai block.
Works if cursor in ai block.
If optional argument ARG is non-nil, mark current message of chat."
  (interactive)
  ;; (oai--debug "oai-fill-paragraph")
  (if-let ((element (oai-block-p)))
      (or (call-interactively #'oai-block-fill-paragraph)
          (when (oai-block-fill-region (point)
                                       (save-excursion (forward-paragraph)
                                                       (point)))
                 (message "Line")))
    ;; else
    (oai--call-next-remap-protected #'org-fill-paragraph)))

(defun oai-next-item (arg)
  "Call `org-next-visible-heading' or move to next ai item.
Works if cursor in ai block.
Item may be header of ai block, markdown
 ### header, markodown subblock, otherwise chat messages used as items.
With ARG, repeats or can move backward if negative."
  (interactive "p")
  (if (derived-mode-p 'org-mode)
    (if (oai-block-p)
        (oai-block-next-item arg)
      ;; else
      (oai--call-next-remap-protected #'org-next-visible-heading))
    ;; else - not org mode
    (oai-block-next-item arg)))

(defun oai-previous-item (arg)
  "Call `org-previous-visible-heading' or move to previous ai item.
Works if cursor in ai block.
Item may be header of ai block, markdown
 ### header, markodown subblock, otherwise chat messages used as items.
ARG may be positive or nil."
  (interactive "p")
  (if (derived-mode-p 'org-mode)
      (if (oai-block-p)
          (oai-block-previous-item arg)
        ;; else
        (oai--call-next-remap-protected #'org-previous-visible-heading))
    ;; else - not org mode
    (oai-block-previous-item arg)))

;; -=-= Minor mode: keymap
;;;###autoload
(defvar-keymap oai-mode-map
  :repeat nil
  :parent nil
  ;; "<remap> <outline-next-visible-heading>" #'oai-next-item ; C-c C-n todo make org
  ;; "<remap> <outline-previous-visible-heading>" #'oai-previous-item ; C-c C-p todo make org
  "C-c C-p" #'oai-previous-item
  "C-c C-n" #'oai-next-item
  "<remap> <org-mark-element>" #'oai-mark-at-point-org ; M-h
  "<remap> <mark-paragraph>" #'oai-block-mark-at-point ; M-h
  "<remap> <fill-paragraph>" #'oai-fill-paragraph ; M-q
  "C-c ." #'oai-expand-block-org
  "C-c C-." #'oai-open-request-buffer
  "C-c C-t" #'oai-set-max-tokens-org)

;; -=-= Minor mode: hook - Fontify Markdown blocks and Tags - function for hook

(defun oai-block--set-ai-keywords ()
  "Hook, that Insert our fontify functions in Org font lock keywords."
  ;; add fontify-ai-subblocks - markdown blocks and tables.
  ;; Put in order to `org-font-lock-keywords': (oai-block--font-lock-fontify-markdown-and-org) (oai-block-tags--font-lock-fontify-links) (oai-block--font-lock-fontify-markdown-blocks)
  (when oai-fontification-flag
    ;; 3) fontify markdown blocks (and clear small)
    (setq org-font-lock-extra-keywords (oai-block--insert-after
                                        org-font-lock-extra-keywords
                                        (seq-position org-font-lock-extra-keywords '(org-fontify-meta-lines-and-blocks))
                                        '(oai-block--font-lock-fontify-markdown-blocks)))
    ;; 2) fontify-links (and clear small)
    (setq org-font-lock-extra-keywords (oai-block--insert-after
                                        org-font-lock-extra-keywords
                                        (seq-position org-font-lock-extra-keywords '(org-fontify-meta-lines-and-blocks))
                                        '(oai-block-tags--font-lock-fontify-links)))
    ;; 1) fontify small elements
    (setq org-font-lock-extra-keywords (oai-block--insert-after
                                        org-font-lock-extra-keywords
                                        (seq-position org-font-lock-extra-keywords '(org-fontify-meta-lines-and-blocks))
                                        '(oai-block--font-lock-fontify-markdown-and-org)))))

;; -=-= Tangling advices
(defun oai--org-babel-get-src-block-info (no-eval datum)
  "Used for Tangling as advice for `org-babel-get-src-block-info'.
Return caontent with help of `oai-block-get-content',
 `oai-block-tags-get-content' DATUM is not optional here.
If NO-EVAL is non-nil, do not evaluate Lisp in parameters."
  (oai--debug "oai--org-babel-get-src-block-info" no-eval datum)
  (let* ((lang "ai")
         (name (org-element-property :name datum))
         ;;
         (info
	  (list
	   lang ; "elisp"
           ;; 1) content: here we replace links in all messages for code simplicity.
           (oai-block-tags--clear-properties
            (oai-block-tags-replace (oai-block-get-content datum nil :tangle nil)
                                    (oai-block-get-header-marker datum)))
           ;; 2) org-babel-default-header-args + default "lang" parameters:
           (apply #'org-babel-merge-params
		  org-babel-default-header-args
		  ;; org-babel-default-header-args:ai ; (eval org-babel-default-header-args:ai t)
		  (append
		   ;; If DATUM is provided, make sure we get node
		   ;; properties applicable to its location within
		   ;; the document.
		   (org-with-point-at (org-element-property :begin datum)
		     (org-babel-params-from-properties lang no-eval))
		   (mapcar (lambda (h)
			     (org-babel-parse-header-arguments h no-eval))
			   (cons (org-element-property :parameters datum)
				 (org-element-property :header datum)))))
           ;; 3,4,5,6)
	   (or (org-element-property :switches datum) "")
           name
	   (org-element-property :post-affiliated datum)
	   (org-src-coderef-format datum))))
    (unless no-eval
      (setf (nth 2 info) (org-babel-process-params (nth 2 info))))
    (setf (nth 2 info) (org-babel-generate-file-param name (nth 2 info)))
    info))

(defun oai--org-babel-where-is-src-block-head-advice (orig-fun &rest args)
  "Advice for `org-babel-tangle' related function.
ORIG-FUN is `org-babel-where-is-src-block-head' and its ARGS."
  (if-let ((element (or (and args (oai-block-p (car args)))
                      (oai-block-p))))
      (org-element-property :begin element)
    ;; else
  (apply orig-fun args)))


(defun oai--org-babel-get-src-block-info-advice (orig-fun &rest args)
  "Advice for `org-babel-tangle' related function.
ORIG-FUN is `oai--org-babel-get-src-block-info-advice' and its ARGS."
  (seq-let (no-eval datum) args
    (if-let ((datum (or (oai-block-p datum) (oai-block-p))))
      (oai--org-babel-get-src-block-info no-eval datum)
      ;; else
      (apply orig-fun args))))
;; -=-= Minor mode

;;;###autoload
(define-minor-mode oai-mode
  "Minor mode for `org-mode' integration with the OpenAI API."
  :init-value nil
  :lighter oai-mode-line-string ; " oai" string
  :keymap oai-mode-map
  :group 'oai
  (when (derived-mode-p 'org-mode)
    (if oai-mode
        (progn
          (add-hook 'org-ctrl-c-ctrl-c-hook #'oai-ctrl-c-ctrl-c nil 'local)
          (advice-add 'keyboard-quit :before #'oai-keyboard-quit)
          (when oai-fontification-flag
            (add-hook 'org-font-lock-set-keywords-hook #'oai-block--set-ai-keywords nil 'local)
            (org-set-font-lock-defaults)
            (font-lock-refresh-defaults))
          ;; - activate "ai" block in Org mode
          (when (and (boundp 'org-protecting-blocks) (listp org-protecting-blocks))
            (add-to-list 'org-protecting-blocks "ai"))
          (when (boundp 'org-structure-template-alist)
            (add-to-list 'org-structure-template-alist '("A" . "ai")))
          ;; - Tangle: advice
          (advice-add 'org-babel-get-src-block-info :around #'oai--org-babel-get-src-block-info-advice)
          (advice-add 'org-babel-where-is-src-block-head :around #'oai--org-babel-where-is-src-block-head-advice)
          (add-to-list 'org-babel-tangle-lang-exts '("ai" . "ai")) ; language . ext
          )
      ;; else - off
      (remove-hook 'org-ctrl-c-ctrl-c-hook #'oai-ctrl-c-ctrl-c 'local)
      (advice-remove 'keyboard-quit #'oai-keyboard-quit)
      ;; font lock refrash
      (remove-hook 'org-font-lock-set-keywords-hook #'oai-block--set-ai-keywords)
      (org-set-font-lock-defaults)
      (font-lock-refresh-defaults)
      ;; tangle
      (advice-remove 'org-babel-get-src-block-info #'oai--org-babel-get-src-block-info-advice)
      (advice-remove 'org-babel-where-is-src-block-head #'oai--org-babel-where-is-src-block-head-advice))))

(defun oai--get-buffers-for-element (&optional element)
  "Simplify getting url buffers associated with ai block ELEMENT.
Or for ai block at current position in current buffer.
Used in `oai-open-request-buffer'."
  (when-let ((element (or element (oai-block-p))))
      (oai-timers--get-keys-for-variable (oai-block-get-header-marker element))))

(defun oai-open-request-buffer ()
  "Opens the url request buffer for ai block at current position."
  (interactive)
  (if-let ((element (oai-block-p)))
      (if-let* ((url-buffer (car (oai--get-buffers-for-element element)))
                (display-buffer-base-action
                 (list '(
                         ;; display-buffer--maybe-same-window  ;FIXME: why isn't this redundant?
                         display-buffer-reuse-window ; pop up bottom window
                         display-buffer-in-previous-window ;; IF RIGHT WINDOW EXIST
                         display-buffer-in-side-window ;; right side window - MAINLY USED
                         display-buffer--maybe-pop-up-frame-or-window ;; create window
                         ;; ;; If all else fails, pop up a new frame.
                         display-buffer-pop-up-frame )
                       '(window-width . 0.6) ; 80 percent
                       '(side . right))))
          (progn
            (pop-to-buffer url-buffer)
            (with-current-buffer url-buffer
              (local-set-key (kbd "C-c ?") 'delete-window)))
        ;; else
        (message "No url buffer found"))
  ;; - else - no element - call original Org key
  (oai--call-next-key-remap-protected "C-c C-.")))

;; -=-= Minor mode - string line
(defvar oai-mode-line-string "")

(defun oai-update-mode-line (count)
  "Used in ora-timers.el to show COUNT of active requests."
  (oai--debug "oai-update-mode-line %s" count)
  (if (and count (> count 0))
      (setq oai-mode-line-string (format " oai[%d]" count))
    ;; else
    (setq oai-mode-line-string " oai"))
  (force-mode-line-update))

;; -=-= aliases
(defalias 'oai-tangle #'org-babel-tangle)
;; -=-= provide
(provide 'oai)
;;; oai.el ends here
