;;; tags.el --- Tag index and HTML helpers -*- lexical-binding: t; -*-

;;; Commentary:
;; Collects FILETAGS from posts/blogs, generates tags/*.org pages, and
;; injects tag HTML into exported pages.
;;
;; NOTE: Do NOT define `site-root' here.  build-site.el owns that definition.

;;; Code:

(require 'cl-lib)
(require 'org)

;; ── Slug ─────────────────────────────────────────────────────────────────────

(defun z/tag-slug (s)
  "Return a URL-safe filename slug for tag S."
  (replace-regexp-in-string "[^a-z0-9]+" "-" (downcase s)))

;; ── File collection ───────────────────────────────────────────────────────────

(defun z/collect-post-files ()
  "Return all .org source files from the org-posts and org-blogs projects."
  (let* ((expanded (org-publish-expand-projects org-publish-project-alist))
         (posts    (assoc "org-posts" expanded))
         (blogs    (assoc "org-blogs" expanded)))
    (cl-remove-duplicates
     (append (and posts (org-publish-get-base-files posts))
             (and blogs (org-publish-get-base-files blogs)))
     :test #'file-equal-p)))

;; ── Tag index ─────────────────────────────────────────────────────────────────

(defconst z/generated-org-names
  '("posts-list.org" "blogs-list.org" "sitemap.org" "categories.org"
    "recently-updated.org" "wip.org")
  "Generated Org files that should never be indexed for tags.")

(defun z/gather-tag-index ()
  "Return a hash-table mapping tag -> list of (FILE TITLE DATE-ISO)."
  (let ((idx (make-hash-table :test 'equal)))
    (dolist (f (z/collect-post-files))
      (when (and (string-match-p "\\.org\\'" f)
                 (file-readable-p f)
                 (not (member (file-name-nondirectory f)
                              z/generated-org-names)))
        (with-temp-buffer
          (insert-file-contents f)
          (org-mode)
          (let* ((kw    (org-collect-keywords '("TITLE" "FILETAGS" "TAGS" "DATE")))
                 (title (or (cadr (assoc "TITLE" kw)) (file-name-base f)))
                 (date  (or (cadr (assoc "DATE"  kw)) ""))
                 (raw   (cadr (or (assoc "FILETAGS" kw)
                                  (assoc "TAGS"     kw)))))
            (when raw
              (dolist (tag (split-string raw ":" t))
                (push (list f title date) (gethash tag idx))))))))
    idx))

;; ── Tag page writer ───────────────────────────────────────────────────────────

(defun z/write-tag-pages ()
  "Generate tags/*.org pages, one per tag.
Returns the number of tag pages written."
  (let* ((tags-dir (site-path "tags/"))
         (idx      (z/gather-tag-index))
         (count    0))
    (unless (file-directory-p tags-dir)
      (make-directory tags-dir t))
    (maphash
     (lambda (tag items)
       (let* ((slug    (z/tag-slug tag))
              (outfile (expand-file-name (format "%s.org" slug) tags-dir)))
         (with-temp-file outfile
           (insert (format "#+TITLE: Tag: %s\n#+OPTIONS: toc:nil num:nil title:nil\n\n* Posts tagged %s\n"
                           tag tag))
           ;; Sort newest first
           (setq items (sort items (lambda (a b) (string> (nth 2 a) (nth 2 b)))))
           (dolist (it items)
             (let* ((file  (nth 0 it))
                    (title (nth 1 it))
                    (rel   (file-relative-name file tags-dir)))
               (insert (format "- [[file:%s][%s]]\n" rel title)))))
         (cl-incf count)))
     idx)
    count))

;; ── HTML tag rendering ────────────────────────────────────────────────────────

(defun z/filetags-html (info)
  "Return an HTML <div class=\"filetags\"> snippet from export INFO, or nil."
  (let ((tags (plist-get info :filetags)))
    (when tags
      (format
       "<div class=\"filetags\">%s</div>\n"
       (mapconcat
        (lambda (tag)
          (format "<a href=\"/home/categories.html\"><span class=\"post-tag\">%s</span></a>"
                  tag))
        tags
        " ")))))

(defun z/insert-filetags-after-title (output backend info)
  "Insert FILETAGS HTML after the first <h1 class=\"title\"> in OUTPUT."
  (if (org-export-derived-backend-p backend 'html)
      (let ((block (z/filetags-html info)))
        (if (and block
                 (string-match "\\(<h1[^>]*class=[\"']title[\"'][^>]*>.*?</h1>\\)" output))
            (replace-match (concat "\\1\n" block) t nil output)
          output))
    output))

(provide 'tags)

;;; tags.el ends here