(ns ajet.chat.cli.core
  "Main entry point and command dispatcher for the ajet CLI client.

   Parses args with tools.cli, dispatches to subcommands:
     login, logout, whoami, communities, channels, read, send, edit, delete,
     dms, dm, notifications, search, status, who, invite, config, tui

   Global options:
     --json   Output JSON (for scripting)
     --help   Show usage help

   Exit codes:
     0   = success
     1   = general error
     2   = usage error
     3   = auth error
     4   = permission error
     5   = not found
     130 = SIGINT"
  (:require [clojure.tools.cli :as cli]
            [ajet.chat.cli.auth :as auth]
            [ajet.chat.cli.commands :as commands]
            [ajet.chat.cli.config :as config]
            [ajet.chat.cli.output :as output]
            [ajet.chat.cli.tui :as tui])
  (:gen-class))

;;; ---------------------------------------------------------------------------
;;; CLI option specs
;;; ---------------------------------------------------------------------------

(def ^:private global-options
  [["-j" "--json" "Output JSON (for scripting)"]
   ["-h" "--help" "Show help"]])

(def ^:private login-options
  [["-t" "--token TOKEN" "Login with an API token directly"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private channels-options
  [["-c" "--community SLUG" "Community slug"]
   [nil  "--join CHANNEL" "Join a channel by name"]
   [nil  "--leave CHANNEL" "Leave a channel by name"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private read-options
  [["-c" "--community SLUG" "Community slug"]
   ["-l" "--limit N" "Number of messages to fetch" :default 50 :parse-fn parse-long]
   ["-b" "--before ID" "Fetch messages before this message ID"]
   ["-t" "--thread ID" "Read thread replies for a message"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private send-options
  [["-c" "--community SLUG" "Community slug"]
   ["-s" "--stdin" "Read message from stdin"]
   ["-i" "--image PATH" "Attach an image file"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private edit-options
  [["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private delete-options
  [["-f" "--force" "Skip confirmation"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private dm-options
  [["-r" "--read" "Read DM conversation instead of sending"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private notifications-options
  [["-a" "--all" "Show all notifications (not just unread)"]
   ["-m" "--mark-read" "Mark all notifications as read"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private search-options
  [["-c" "--community SLUG" "Community slug"]
   [nil  "--channel CHANNEL" "Search in specific channel"]
   [nil  "--from USER" "Search by author"]
   [nil  "--type TYPE" "Filter by type (messages, channels)"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private status-options
  [["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private who-options
  [["-c" "--community SLUG" "Community slug"]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private invite-options
  [["-c" "--community SLUG" "Community slug"]
   [nil  "--max-uses N" "Maximum number of invite uses" :parse-fn parse-long]
   ["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private config-options
  [["-j" "--json" "Output JSON"]
   ["-h" "--help" "Show help"]])

(def ^:private tui-options
  [["-c" "--community SLUG" "Open to specific community"]
   [nil  "--channel CHANNEL" "Open to specific channel"]
   ["-h" "--help" "Show help"]])

;;; ---------------------------------------------------------------------------
;;; Usage / help text
;;; ---------------------------------------------------------------------------

(def ^:private program-name "ajet")

(def ^:private usage-header
  (str program-name " - ajet chat CLI client\n"
       "\n"
       "Usage: " program-name " <command> [options] [args]\n"
       "\n"
       "Commands:\n"
       "  login              Login via OAuth or API token\n"
       "  logout             Clear session\n"
       "  whoami             Show current user info\n"
       "  communities        List communities\n"
       "  channels           List/join/leave channels\n"
       "  read <channel>     Read messages in a channel\n"
       "  send <channel>     Send a message\n"
       "  edit <id> <text>   Edit a message\n"
       "  delete <id>        Delete a message\n"
       "  dms                List DM conversations\n"
       "  dm <user> <text>   Send a DM\n"
       "  notifications      Manage notifications\n"
       "  search <query>     Search messages\n"
       "  status [text]      Show/set status\n"
       "  who                Show online users\n"
       "  invite             Manage invites (create/list/revoke)\n"
       "  config             Show/set configuration\n"
       "  tui                Launch interactive TUI\n"
       "\n"
       "Global options:\n"
       "  -j, --json         Output JSON (for scripting)\n"
       "  -h, --help         Show help\n"
       "\n"
       "Run '" program-name " <command> --help' for command-specific options."))

(defn- command-usage
  "Generate usage text for a specific command."
  [command options-spec description]
  (let [{:keys [summary]} (cli/parse-opts [] options-spec)]
    (str "Usage: " program-name " " command " " description "\n"
         "\n"
         "Options:\n"
         summary)))

;;; ---------------------------------------------------------------------------
;;; Error handling
;;; ---------------------------------------------------------------------------

(defn- api-error->exit-code
  "Map an API error status to an exit code."
  [status]
  (cond
    (= status 401) 3
    (= status 403) 4
    (= status 404) 5
    (>= status 500) 1
    :else           1))

(defn- api-error->hint
  "Generate a hint for common API error statuses."
  [status]
  (case status
    401 "Run 'ajet login' to sign in"
    403 "You don't have permission for this action"
    404 nil
    429 "Too many requests. Wait a moment and try again"
    nil))

(defn- handle-error
  "Handle an exception and return an exit code."
  [e]
  (let [data (ex-data e)]
    (case (:type data)
      :ajet.chat/api-error
      (let [status (:status data)
            body   (:body data)
            msg    (or (:message body) (:error body) (.getMessage e))
            detail (or (:detail body) (:details body))
            hint   (or (:hint data) (api-error->hint status))]
        (output/print-error msg detail hint)
        (api-error->exit-code status))

      :ajet.chat/auth-error
      (do
        (output/print-error (.getMessage e)
                            "No session token found. You need to authenticate first."
                            (or (:hint data) "Run 'ajet login' to sign in"))
        3)

      :ajet.chat/usage-error
      (do
        (output/print-error (.getMessage e) nil (:hint data))
        2)

      :ajet.chat/not-found
      (do
        (output/print-error (.getMessage e) nil (:hint data))
        5)

      :ajet.chat/validation-error
      (do
        (output/print-error (.getMessage e) (:explain data) nil)
        2)

      :ajet.chat/cancelled
      0

      ;; Unknown ex-info
      (if data
        (do
          (output/print-error (.getMessage e) (pr-str data) nil)
          1)
        ;; Regular exception
        (do
          (output/print-error (.getMessage e)
                              nil
                              "If this persists, check your server connection with 'ajet config'")
          1)))))

;;; ---------------------------------------------------------------------------
;;; Command dispatch
;;; ---------------------------------------------------------------------------

(defn- parse-and-dispatch
  "Parse command-specific options and dispatch to the handler."
  [handler-fn raw-args option-spec]
  (let [{:keys [options arguments errors summary]} (cli/parse-opts raw-args option-spec)]
    (cond
      (:help options)
      (do (println summary) 0)

      errors
      (do
        (doseq [err errors]
          (output/print-error err))
        2)

      :else
      (handler-fn arguments (assoc options :json? (:json options))))))

(defn- dispatch
  "Dispatch to the appropriate command handler."
  [command raw-args]
  (case command
    "login"
    (let [{:keys [options errors]} (cli/parse-opts raw-args login-options)]
      (cond
        errors   (do (doseq [e errors] (output/print-error e)) 2)
        (:help options) (do (println (command-usage "login" login-options "[options]")) 0)
        (:token options) (auth/login-token (:token options) {:json? (:json options)})
        :else    (auth/login-interactive {:json? (:json options)})))

    "logout"
    (let [{:keys [options]} (cli/parse-opts raw-args global-options)]
      (if (:help options)
        (do (println (command-usage "logout" global-options "")) 0)
        (auth/logout {:json? (:json options)})))

    "whoami"
    (let [{:keys [options]} (cli/parse-opts raw-args global-options)]
      (if (:help options)
        (do (println (command-usage "whoami" global-options "")) 0)
        (auth/whoami {:json? (:json options)})))

    "communities"
    (parse-and-dispatch commands/communities raw-args global-options)

    "channels"
    (parse-and-dispatch commands/channels raw-args channels-options)

    "read"
    (parse-and-dispatch commands/read-messages raw-args read-options)

    "send"
    (parse-and-dispatch commands/send-message raw-args send-options)

    "edit"
    (parse-and-dispatch commands/edit-message raw-args edit-options)

    "delete"
    (parse-and-dispatch commands/delete-message raw-args delete-options)

    "dms"
    (parse-and-dispatch commands/list-dms raw-args global-options)

    "dm"
    (parse-and-dispatch commands/send-dm raw-args dm-options)

    "notifications"
    (parse-and-dispatch commands/notifications raw-args notifications-options)

    "search"
    (parse-and-dispatch commands/search raw-args search-options)

    "status"
    (parse-and-dispatch commands/set-status raw-args status-options)

    "who"
    (parse-and-dispatch commands/who-online raw-args who-options)

    "invite"
    (parse-and-dispatch commands/invite raw-args invite-options)

    "config"
    (parse-and-dispatch commands/config-cmd raw-args config-options)

    "tui"
    (let [{:keys [options errors]} (cli/parse-opts raw-args tui-options)]
      (cond
        errors (do (doseq [e errors] (output/print-error e)) 2)
        (:help options) (do (println (command-usage "tui" tui-options "[options]")) 0)
        :else (tui/launch! {:community (:community options)
                            :channel   (:channel options)})))

    ;; Unknown command
    (do
      (output/print-error (str "Unknown command: " command)
                          nil
                          (str "Run '" program-name " --help' to see available commands"))
      2)))

;;; ---------------------------------------------------------------------------
;;; Main entry point
;;; ---------------------------------------------------------------------------

(defn -main
  "Main entry point. Parses arguments and dispatches to commands."
  [& args]
  ;; Register SIGINT handler
  (let [original-handler (Thread/getDefaultUncaughtExceptionHandler)]
    (.addShutdownHook (Runtime/getRuntime)
      (Thread. ^Runnable (fn []
                           ;; Restore terminal on shutdown (in case TUI was running)
                           (print "\033[?25h\033[?1049l")
                           (flush)))))

  (let [args (vec args)]
    (if (empty? args)
      ;; No arguments: show help
      (do
        (println usage-header)
        (System/exit 0))

      ;; Parse global options first to check for --help
      (let [{:keys [options arguments]} (cli/parse-opts args global-options :in-order true)]
        (cond
          (:help options)
          (do
            (println usage-header)
            (System/exit 0))

          (empty? arguments)
          (do
            (println usage-header)
            (System/exit 0))

          :else
          (let [command  (first arguments)
                cmd-args (vec (rest arguments))
                ;; Merge global --json flag into remaining args if present
                all-args (if (:json options)
                           (into ["--json"] cmd-args)
                           cmd-args)
                exit-code (try
                            (dispatch command all-args)
                            (catch clojure.lang.ExceptionInfo e
                              (handle-error e))
                            (catch java.net.ConnectException _e
                              (output/print-error
                                (str "Cannot connect to server at " (config/get-server-url))
                                "The server may be down or the URL may be incorrect."
                                "Check your config with 'ajet config'")
                              1)
                            (catch java.net.SocketTimeoutException _e
                              (output/print-error
                                "Request timed out"
                                "The server took too long to respond."
                                "Try again, or check your connection")
                              1)
                            (catch InterruptedException _e
                              130)
                            (catch Exception e
                              (handle-error e)))]
            (System/exit (or exit-code 0))))))))
