-- TOG Tools — Name Prefix module (engine)
-- Prepends a configurable nickname prefix to outgoing chat messages.
--
-- Hook strategy (most reliable across all versions):
--   Retail 12.0+  : EventRegistry "ChatFrame.OnEditBoxPreSendText" — fires
--                   pre-send on Retail only. Classic clients (1.15.x, BCC,
--                   etc.) have EventRegistry backported but do NOT fire
--                   this event, so we must gate on tocVersion ≥ 120000.
--   Classic/older : per-editbox OnEnterPressed script wrap. On modern
--                   Classic builds the editbox's OnEnterPressed script is
--                   the ChatFrameEditBoxMixin:OnEnterPressed method, NOT
--                   the global ChatEdit_OnEnterPressed — so wrapping the
--                   global silently fails. Wrapping each ChatFrameN
--                   editbox's script directly catches both the legacy
--                   global path and the mixin-method path.
--
-- We deliberately do NOT touch ChatEdit_SendText or ChatFrameEditBoxMixin
-- on Classic. OPie's Rewire dispatches macro lines via
-- `securecall(ChatEdit_SendText, box, false)` (Libs/ActionBook/Rewire.lua)
-- and uses synthetic editboxes that share the same mixin — replacing
-- either taints OPie's secure execution context and breaks any ring entry
-- delivered as macrotext (custom mount macros, etc.). Per-editbox script
-- wrapping touches only the user-visible ChatFrameN editboxes, not OPie's
-- synthetic ones, and never replaces a global function reference.
--
-- The hook only modifies editBox:SetText() BEFORE the message is sent.
-- It never calls SendChatMessage directly.

local _, addon = ...
local gv = addon.gameVersion

local NamePrefix = {}
addon.NamePrefix = NamePrefix

-- ---------------------------------------------------------------------------
-- Internal state
-- ---------------------------------------------------------------------------
local _hooked = false   -- set true once the hook is installed

-- ---------------------------------------------------------------------------
-- Core: decide whether to prefix and do it
-- ---------------------------------------------------------------------------
local function ModifyMessage(editBox)
    local cfg = addon.DB and addon.DB.global.namePrefix
    if not cfg then return end
    if not cfg.enabled then return end

    -- InCombatLockdown guard: SetText taints the protected SendChatMessage
    -- call that follows on some clients. Message still sends as-is.
    if InCombatLockdown() then return end

    local nick = cfg.nickname
    if not nick or nick == "" then return end

    -- Optionally suppress when nickname matches current character name
    if cfg.hideIfCharName then
        local charName = UnitName("player")
        if charName and nick == charName then return end
    end

    local msg = editBox:GetText()
    if not msg or msg == "" then return end

    -- Skip slash commands and macros entirely.
    if string.sub(msg, 1, 1) == "/" then return end

    -- Skip messages starting with "!" (addon commands like !keys)
    if cfg.skipExclamation and string.sub(msg, 1, 1) == "!" then return end

    -- Determine chat type
    local chatType = editBox:GetAttribute("chatType") or editBox.chatType or "SAY"

    local shouldPrefix = false
    if     chatType == "GUILD"         and cfg.guild    then shouldPrefix = true
    elseif chatType == "OFFICER"       and cfg.officer  then shouldPrefix = true
    elseif chatType == "PARTY"         and cfg.party    then shouldPrefix = true
    elseif chatType == "PARTY_LEADER"  and cfg.party    then shouldPrefix = true
    elseif chatType == "RAID"          and cfg.raid     then shouldPrefix = true
    elseif chatType == "RAID_WARNING"  and cfg.raid     then shouldPrefix = true
    elseif chatType == "INSTANCE_CHAT" and cfg.instance then shouldPrefix = true
    elseif chatType == "CHANNEL"       and cfg.channel and cfg.channel ~= "" then
        local channelTarget = editBox:GetAttribute("channelTarget") or editBox.channelTarget
        if channelTarget then
            local _, chname = GetChannelName(channelTarget)
            if chname and strupper(cfg.channel) == strupper(chname) then
                shouldPrefix = true
            end
        end
    end

    if not shouldPrefix then return end

    -- Build prefix from format string (safe substitution)
    local fmt = (cfg.format and cfg.format ~= "") and cfg.format or "(%s): "
    local prefix = string.format(fmt, nick)
    editBox:SetText(prefix .. msg)
end

-- ---------------------------------------------------------------------------
-- Hook installation
-- ---------------------------------------------------------------------------
function NamePrefix:Install()
    if _hooked then return end

    if gv.isRetail120Plus and EventRegistry then
        -- Retail 12.0+: this event fires BEFORE SendText reads the editbox,
        -- so our SetText reaches the message that gets sent.
        EventRegistry:RegisterCallback(
            "ChatFrame.OnEditBoxPreSendText",
            function(_event, editBox)
                local ok, err = pcall(ModifyMessage, editBox)
                if not ok and addon.DB and addon.DB.global.namePrefix.enabled then
                    print("|cffFF8000TOGTools|r NamePrefix error: " .. tostring(err))
                end
            end,
            NamePrefix
        )
        _hooked = true
    else
        -- Classic / older Retail: wrap each ChatFrameN editbox's OnEnterPressed
        -- script directly. We can't wrap the global ChatEdit_OnEnterPressed
        -- because on modern Classic builds the editbox script is the
        -- ChatFrameEditBoxMixin:OnEnterPressed method, not the global.
        --
        -- NOT ChatEdit_SendText, NOT ChatFrameEditBoxMixin.SendText — OPie's
        -- Rewire uses both via securecall on synthetic editboxes; replacing
        -- either taints its secure context and breaks macrotext ring entries
        -- (custom mounts, etc.). Per-editbox script wrap only touches the
        -- ChatFrameN editboxes the user types into.
        local function wrapEditBox(eb)
            if not eb then return end
            local origScript = eb:GetScript("OnEnterPressed")
            if not origScript then return end
            eb:SetScript("OnEnterPressed", function(self, ...)
                local ok, err = pcall(ModifyMessage, self)
                if not ok and addon.DB and addon.DB.global.namePrefix
                   and addon.DB.global.namePrefix.enabled then
                    print("|cffFF8000TOGTools|r NamePrefix error: " .. tostring(err))
                end
                return origScript(self, ...)
            end)
        end

        for i = 1, NUM_CHAT_WINDOWS or 10 do
            wrapEditBox(_G["ChatFrame" .. i .. "EditBox"])
        end
        _hooked = true
    end
end

function NamePrefix:OnEnable()
    self:Install()
end

-- ---------------------------------------------------------------------------
-- Register as a module (engine only — no tab key; tab is in NamePrefixTab.lua)
-- ---------------------------------------------------------------------------
-- The tab registration lives in GUI/NamePrefixTab.lua which loads after this
-- file and merges its Draw/WINDOW_SIZE into addon.modules["nameprefix"].
addon.modules["nameprefix"] = NamePrefix
