(ns ajet.chat.cli.output
  "Output formatting for the CLI client.

   All terminal output goes through this module for consistent formatting.
   Supports human-readable and JSON output modes."
  (:require [clojure.data.json :as json]
            [clojure.string :as str]
            [ajet.chat.shared.markdown :as markdown]
            [ajet.chat.shared.mentions :as mentions])
  (:import [java.time Instant Duration ZoneId]
           [java.time.format DateTimeFormatter]))

;;; ---------------------------------------------------------------------------
;;; ANSI color helpers
;;; ---------------------------------------------------------------------------

(def ^:private ansi
  {:reset     "\033[0m"
   :bold      "\033[1m"
   :dim       "\033[2m"
   :italic    "\033[3m"
   :underline "\033[4m"
   :cyan      "\033[36m"
   :green     "\033[32m"
   :yellow    "\033[33m"
   :red       "\033[31m"
   :magenta   "\033[35m"
   :blue      "\033[34m"
   :gray      "\033[90m"
   :white     "\033[37m"
   :bg-red    "\033[41m"})

(defn- colorize [color text]
  (str (get ansi color "") text (:reset ansi)))

;;; ---------------------------------------------------------------------------
;;; Timestamp formatting
;;; ---------------------------------------------------------------------------

(def ^:private local-zone (ZoneId/systemDefault))

(def ^:private time-fmt
  (DateTimeFormatter/ofPattern "h:mm a"))

(def ^:private date-fmt
  (DateTimeFormatter/ofPattern "MMM d, yyyy"))

(def ^:private datetime-fmt
  (DateTimeFormatter/ofPattern "MMM d, yyyy h:mm a"))

(defn- parse-timestamp
  "Parse a timestamp string to an Instant. Handles ISO-8601 strings."
  [ts]
  (when ts
    (try
      (if (instance? Instant ts)
        ts
        (Instant/parse (str ts)))
      (catch Exception _ nil))))

(defn relative-time
  "Format a timestamp as a relative time string.
   Returns: \"just now\", \"5m ago\", \"2h ago\", \"yesterday\", or a date."
  [ts]
  (if-let [instant (parse-timestamp ts)]
    (let [now      (Instant/now)
          duration (Duration/between instant now)
          seconds  (.toSeconds duration)
          minutes  (.toMinutes duration)
          hours    (.toHours duration)
          days     (.toDays duration)
          zdt      (.atZone instant local-zone)]
      (cond
        (< seconds 60)    "just now"
        (< minutes 60)    (str minutes "m ago")
        (< hours 24)      (str hours "h ago")
        (= days 1)        "yesterday"
        (< days 7)        (str days "d ago")
        :else             (.format zdt date-fmt)))
    ""))

(defn- format-time
  "Format a timestamp as a local time (e.g., '10:30 AM')."
  [ts]
  (if-let [instant (parse-timestamp ts)]
    (let [zdt (.atZone instant local-zone)]
      (.format zdt time-fmt))
    ""))

(defn- format-datetime
  "Format a timestamp as a local date and time."
  [ts]
  (if-let [instant (parse-timestamp ts)]
    (let [zdt (.atZone instant local-zone)]
      (.format zdt datetime-fmt))
    ""))

;;; ---------------------------------------------------------------------------
;;; Mention rendering
;;; ---------------------------------------------------------------------------

(defn- default-mention-lookup
  "Default lookup function for mentions. Returns the ID as a fallback."
  [_type id]
  id)

;;; ---------------------------------------------------------------------------
;;; Message formatting
;;; ---------------------------------------------------------------------------

(defn- render-body
  "Render a message body with markdown and mentions for terminal display."
  [body-md & [mention-lookup]]
  (let [lookup (or mention-lookup default-mention-lookup)
        with-mentions (mentions/render body-md lookup)]
    (markdown/->ansi with-mentions)))

(defn- format-attachments
  "Format attachment list for display."
  [attachments]
  (when (seq attachments)
    (str/join "\n"
      (map (fn [att]
             (str "  " (colorize :cyan (str "[" (:content-type att "file") ": " (:filename att) "]"))))
           attachments))))

(defn- format-reactions
  "Format reactions for display."
  [reactions]
  (when (seq reactions)
    (let [grouped (group-by :emoji reactions)]
      (str "  "
           (str/join " "
             (map (fn [[emoji reacts]]
                    (str emoji " " (count reacts)))
                  grouped))))))

(defn print-message
  "Format and print a single message for terminal display.

   Message shape:
     {:id, :user-id, :username, :display-name, :body-md, :created-at,
      :edited-at, :attachments, :reactions, :thread-count, :parent-id}"
  [msg & [{:keys [mention-lookup show-channel]}]]
  (let [author     (or (:display-name msg) (:username msg) "unknown")
        time-str   (relative-time (:created-at msg))
        edited?    (:edited-at msg)
        channel    (when show-channel
                     (str (colorize :cyan (str "#" (:channel-name msg))) " "))
        header     (str "  " (colorize :bold author) "  "
                        (colorize :gray time-str)
                        (when edited? (colorize :gray " (edited)")))
        body       (render-body (:body-md msg) mention-lookup)
        indented   (str/join "\n" (map #(str "  " %) (str/split-lines body)))
        atts       (format-attachments (:attachments msg))
        reacts     (format-reactions (:reactions msg))
        thread-ct  (when (and (:thread-count msg) (pos? (:thread-count msg)))
                     (str "  " (colorize :blue (str (:thread-count msg) " replies"))))]
    (println (str (when channel (str channel "\n")) header))
    (println indented)
    (when atts (println atts))
    (when reacts (println reacts))
    (when thread-ct (println thread-ct))))

(defn print-messages
  "Format and print a list of messages with grouping.

   Messages from the same user within 5 minutes are grouped together
   (only the first gets the full header)."
  [messages & [{:keys [mention-lookup channel-name channel-topic] :as opts}]]
  (when channel-name
    (println (str (colorize :bold (str "#" channel-name))
                  (when channel-topic
                    (str (colorize :gray (str " -- " channel-topic))))))
    (println))
  (loop [msgs messages
         prev-author nil
         prev-time nil]
    (when (seq msgs)
      (let [msg          (first msgs)
            author       (or (:username msg) (:user-id msg))
            msg-time     (parse-timestamp (:created-at msg))
            same-group?  (and (= author prev-author)
                              prev-time
                              msg-time
                              (< (.toMinutes (Duration/between prev-time msg-time)) 5))
            display-name (or (:display-name msg) (:username msg) "unknown")
            time-str     (relative-time (:created-at msg))
            edited?      (:edited-at msg)]
        (if same-group?
          ;; Grouped: just the body, indented
          (let [body     (render-body (:body-md msg) mention-lookup)
                indented (str/join "\n" (map #(str "  " %) (str/split-lines body)))]
            (println indented)
            (when-let [atts (format-attachments (:attachments msg))]
              (println atts))
            (when-let [reacts (format-reactions (:reactions msg))]
              (println reacts)))
          ;; New group: full header
          (do
            (when prev-author (println)) ;; blank line between groups
            (print-message msg opts)))
        (recur (rest msgs) author (or msg-time prev-time))))))

;;; ---------------------------------------------------------------------------
;;; Table formatting
;;; ---------------------------------------------------------------------------

(defn print-table
  "Print a simple table from a list of maps.

   columns is a vector of [header-string key-keyword] pairs.
   Example: [[\"Name\" :name] [\"Slug\" :slug] [\"Members\" :member-count]]"
  [columns rows]
  (when (seq rows)
    (let [headers    (mapv first columns)
          keys       (mapv second columns)
          str-rows   (mapv (fn [row]
                             (mapv (fn [k] (str (get row k ""))) keys))
                           rows)
          all-rows   (cons headers str-rows)
          col-widths (reduce (fn [widths row]
                               (mapv (fn [w cell] (max w (count cell))) widths row))
                             (mapv (constantly 0) headers)
                             all-rows)
          fmt-row    (fn [row]
                       (str/join "  "
                         (map-indexed (fn [i cell]
                                        (let [w (nth col-widths i)]
                                          (format (str "%-" w "s") cell)))
                                      row)))]
      ;; Header
      (println (colorize :bold (fmt-row headers)))
      ;; Separator
      (println (str/join "  " (map #(apply str (repeat % "-")) col-widths)))
      ;; Data rows
      (doseq [row str-rows]
        (println (fmt-row row))))))

;;; ---------------------------------------------------------------------------
;;; Error formatting
;;; ---------------------------------------------------------------------------

(defn print-error
  "Format and print an error message with optional hint.

   Format:
     Error: <short description>

       <details>

     Hint: <actionable next step>"
  ([message]
   (print-error message nil nil))
  ([message details]
   (print-error message details nil))
  ([message details hint]
   (binding [*out* *err*]
     (println (str (colorize :red "Error: ") message))
     (when details
       (println)
       (println (str "  " details)))
     (when hint
       (println)
       (println (str (colorize :yellow "Hint: ") hint))))))

;;; ---------------------------------------------------------------------------
;;; JSON output
;;; ---------------------------------------------------------------------------

(defn print-json
  "Print data as formatted JSON. Used with --json flag."
  [data]
  (println (json/write-str data)))

;;; ---------------------------------------------------------------------------
;;; Miscellaneous
;;; ---------------------------------------------------------------------------

(defn print-success
  "Print a success message."
  [message]
  (println (str (colorize :green "OK") " " message)))

(defn print-info
  "Print an informational message."
  [message]
  (println message))

(defn confirm?
  "Ask for confirmation. Returns true if user types 'y' or 'yes'."
  [prompt]
  (print (str prompt " [y/N] "))
  (flush)
  (let [input (str/trim (or (read-line) ""))]
    (contains? #{"y" "yes" "Y" "Yes" "YES"} input)))
