(ns ajet.chat.shared.markdown
  "Discord-flavor markdown renderer.

   Dual output: HTML (for web) and ANSI escape codes (for TUI).
   Supports: bold, italic, strikethrough, underline, inline code,
   fenced code blocks, spoilers, block quotes, auto-link URLs, emoji shortcodes.

   Mention syntax (@<user:>, #<channel:>) is NOT processed here —
   that's handled by ajet.chat.shared.mentions."
  (:require [clojure.string :as str]))

;;; ---------------------------------------------------------------------------
;;; HTML Escaping
;;; ---------------------------------------------------------------------------

(defn- escape-html
  "Escape HTML special characters to prevent XSS."
  [s]
  (-> s
      (str/replace "&" "&amp;")
      (str/replace "<" "&lt;")
      (str/replace ">" "&gt;")
      (str/replace "\"" "&quot;")
      (str/replace "'" "&#39;")))

;;; ---------------------------------------------------------------------------
;;; Emoji Shortcodes
;;; ---------------------------------------------------------------------------

(def ^:private emoji-map
  "Common emoji shortcodes to Unicode."
  {":smile:"        "\uD83D\uDE04"
   ":laughing:"     "\uD83D\uDE06"
   ":heart:"        "\u2764\uFE0F"
   ":thumbsup:"     "\uD83D\uDC4D"
   ":thumbsdown:"   "\uD83D\uDC4E"
   ":fire:"         "\uD83D\uDD25"
   ":100:"          "\uD83D\uDCAF"
   ":rocket:"       "\uD83D\uDE80"
   ":wave:"         "\uD83D\uDC4B"
   ":eyes:"         "\uD83D\uDC40"
   ":thinking:"     "\uD83E\uDD14"
   ":tada:"         "\uD83C\uDF89"
   ":check:"        "\u2705"
   ":x:"            "\u274C"
   ":warning:"      "\u26A0\uFE0F"
   ":star:"         "\u2B50"
   ":sparkles:"     "\u2728"
   ":pray:"         "\uD83D\uDE4F"
   ":clap:"         "\uD83D\uDC4F"
   ":sob:"          "\uD83D\uDE2D"
   ":joy:"          "\uD83D\uDE02"
   ":wink:"         "\uD83D\uDE09"
   ":sunglasses:"   "\uD83D\uDE0E"
   ":muscle:"       "\uD83D\uDCAA"
   ":point_right:"  "\uD83D\uDC49"
   ":skull:"        "\uD83D\uDC80"
   ":ok_hand:"      "\uD83D\uDC4C"
   ":raised_hands:" "\uD83D\uDE4C"
   ":coffee:"       "\u2615"
   ":beer:"         "\uD83C\uDF7A"
   ":pizza:"        "\uD83C\uDF55"
   ":poop:"         "\uD83D\uDCA9"
   ":bug:"          "\uD83D\uDC1B"
   ":wrench:"       "\uD83D\uDD27"
   ":bulb:"         "\uD83D\uDCA1"
   ":memo:"         "\uD83D\uDCDD"
   ":lock:"         "\uD83D\uDD12"
   ":key:"          "\uD83D\uDD11"
   ":link:"         "\uD83D\uDD17"
   ":heavy_plus_sign:" "\u2795"
   ":heavy_minus_sign:" "\u2796"})

(defn- replace-emoji-shortcodes
  "Replace :shortcode: with Unicode emoji."
  [text]
  (str/replace text #":[a-z_]+:" #(get emoji-map % %)))

;;; ---------------------------------------------------------------------------
;;; HTML Rendering
;;; ---------------------------------------------------------------------------

(defn- render-fenced-code-blocks
  "Extract and render fenced code blocks, returning [processed-text, blocks-map]."
  [text]
  (let [blocks  (atom {})
        counter (atom 0)
        result  (str/replace text #"(?s)```(\w*)\n?(.*?)```"
                  (fn [[_ lang code]]
                    (let [id (str "\u0000CODEBLOCK" (swap! counter inc) "\u0000")
                          lang-class (when-not (str/blank? lang) (str " class=\"language-" lang "\""))]
                      (swap! blocks assoc id
                        (str "<pre><code" (or lang-class "") ">"
                             (escape-html code)
                             "</code></pre>"))
                      id)))]
    [result @blocks]))

(defn- render-inline-code
  "Render inline code, returning [processed-text, codes-map]."
  [text]
  (let [codes   (atom {})
        counter (atom 0)
        result  (str/replace text #"`([^`]+)`"
                  (fn [[_ code]]
                    (let [id (str "\u0000INLINECODE" (swap! counter inc) "\u0000")]
                      (swap! codes assoc id
                        (str "<code>" (escape-html code) "</code>"))
                      id)))]
    [result @codes]))

(defn- restore-placeholders
  "Replace placeholder IDs with their rendered content."
  [text placeholders]
  (reduce-kv (fn [t k v] (str/replace t k v)) text placeholders))

(defn ->html
  "Render Discord-flavor markdown to HTML.

   Handles: bold, italic, strikethrough, underline, inline code,
   fenced code blocks, spoilers, block quotes, auto-link URLs, emoji."
  [text]
  (let [;; Phase 1: Extract code blocks and inline code (don't process markdown inside)
        [text1 code-blocks] (render-fenced-code-blocks text)
        [text2 inline-codes] (render-inline-code text1)
        ;; Phase 2: Escape remaining HTML
        text3 (escape-html text2)
        ;; Phase 3: Apply formatting
        text4 (-> text3
                  ;; Bold: **text**
                  (str/replace #"\*\*(.+?)\*\*" "<strong>$1</strong>")
                  ;; Underline: __text__
                  (str/replace #"__(.+?)__" "<u>$1</u>")
                  ;; Italic: *text* or _text_
                  (str/replace #"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)" "<em>$1</em>")
                  (str/replace #"(?<!_)_(?!_)(.+?)(?<!_)_(?!_)" "<em>$1</em>")
                  ;; Strikethrough: ~~text~~
                  (str/replace #"~~(.+?)~~" "<del>$1</del>")
                  ;; Spoilers: ||text||
                  (str/replace #"\|\|(.+?)\|\|" "<span class=\"spoiler\">$1</span>")
                  ;; Block quotes: > text (at start of line)
                  (str/replace #"(?m)^&gt; (.+)$" "<blockquote>$1</blockquote>")
                  ;; Auto-link URLs
                  (str/replace #"(?<![\"=])(https?://[^\s<]+)"
                    "<a href=\"$1\" rel=\"noopener noreferrer\">$1</a>")
                  ;; Emoji shortcodes
                  (replace-emoji-shortcodes)
                  ;; Newlines to <br>
                  (str/replace "\n" "<br>"))
        ;; Phase 4: Restore code blocks and inline code
        text5 (restore-placeholders text4 (merge code-blocks inline-codes))]
    text5))

;;; ---------------------------------------------------------------------------
;;; ANSI Rendering (for TUI)
;;; ---------------------------------------------------------------------------

(def ^:private ansi
  {:reset     "\033[0m"
   :bold      "\033[1m"
   :italic    "\033[3m"
   :underline "\033[4m"
   :strike    "\033[9m"
   :dim       "\033[2m"
   :reverse   "\033[7m"
   :cyan      "\033[36m"
   :green     "\033[32m"
   :yellow    "\033[33m"
   :gray      "\033[90m"})

(defn ->ansi
  "Render Discord-flavor markdown to ANSI escape codes for terminal display."
  [text]
  (let [;; Extract fenced code blocks
        [text1 blocks] (let [bs (atom {}) c (atom 0)]
                         [(str/replace text #"(?s)```(\w*)\n?(.*?)```"
                            (fn [[_ lang code]]
                              (let [id (str "\u0000CB" (swap! c inc) "\u0000")
                                    header (if (str/blank? lang) "" (str (:cyan ansi) lang (:reset ansi) "\n"))
                                    box (str "\n" (:gray ansi) "\u250C" (apply str (repeat 60 "\u2500")) "\u2510" (:reset ansi)
                                             "\n" header
                                             (:dim ansi) code (:reset ansi)
                                             "\n" (:gray ansi) "\u2514" (apply str (repeat 60 "\u2500")) "\u2518" (:reset ansi) "\n")]
                                (swap! bs assoc id box)
                                id)))
                          @bs])
        ;; Extract inline code
        [text2 inlines] (let [cs (atom {}) c (atom 0)]
                          [(str/replace text1 #"`([^`]+)`"
                             (fn [[_ code]]
                               (let [id (str "\u0000IC" (swap! c inc) "\u0000")]
                                 (swap! cs assoc id (str (:dim ansi) (:reverse ansi) " " code " " (:reset ansi)))
                                 id)))
                           @cs])
        ;; Apply formatting
        text3 (-> text2
                  (str/replace #"\*\*(.+?)\*\*" (str (:bold ansi) "$1" (:reset ansi)))
                  (str/replace #"__(.+?)__" (str (:underline ansi) "$1" (:reset ansi)))
                  (str/replace #"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)" (str (:italic ansi) "$1" (:reset ansi)))
                  (str/replace #"(?<!_)_(?!_)(.+?)(?<!_)_(?!_)" (str (:italic ansi) "$1" (:reset ansi)))
                  (str/replace #"~~(.+?)~~" (str (:strike ansi) "$1" (:reset ansi)))
                  (str/replace #"\|\|(.+?)\|\|" (str (:reverse ansi) " SPOILER " (:reset ansi)))
                  (str/replace #"(?m)^> (.+)$" (str (:gray ansi) "\u2502 " (:reset ansi) "$1"))
                  (str/replace #"(https?://[^\s]+)" (str "\033]8;;" "$1" "\033\\\\" (:cyan ansi) "$1" (:reset ansi) "\033]8;;\033\\\\"))
                  (replace-emoji-shortcodes))
        ;; Restore code blocks
        text4 (restore-placeholders text3 (merge blocks inlines))]
    text4))
