local ADDON_NAME = ...

HermitModeDB = HermitModeDB or {}

local defaults = {
    mode = "full", -- full, readonly, off
    autoDND = true,
}

-- Incoming social chat events hidden while FULL mode is active.
local blockedEvents = {
    CHAT_MSG_SAY = true,
    CHAT_MSG_YELL = true,
    CHAT_MSG_WHISPER = true,
    CHAT_MSG_WHISPER_INFORM = true,
    CHAT_MSG_EMOTE = true,
    CHAT_MSG_TEXT_EMOTE = true,
    CHAT_MSG_CHANNEL = true,
    CHAT_MSG_PARTY = true,
    CHAT_MSG_PARTY_LEADER = true,
    CHAT_MSG_PARTY_GUIDE = true,
    CHAT_MSG_MONSTER_PARTY = true,
    CHAT_MSG_RAID = true,
    CHAT_MSG_RAID_LEADER = true,
    CHAT_MSG_RAID_WARNING = true,
    CHAT_MSG_GUILD = true,
    CHAT_MSG_OFFICER = true,
    CHAT_MSG_INSTANCE_CHAT = true,
    CHAT_MSG_INSTANCE_CHAT_LEADER = true,
    CHAT_MSG_BATTLEGROUND = true,
    CHAT_MSG_BATTLEGROUND_LEADER = true,
}

-- Outgoing social chat types blocked in FULL and READ ONLY modes.
local blockedChatTypes = {
    SAY = true,
    YELL = true,
    WHISPER = true,
    EMOTE = true,
    TEXT_EMOTE = true,
    CHANNEL = true,
    PARTY = true,
    RAID = true,
    RAID_WARNING = true,
    GUILD = true,
    OFFICER = true,
    INSTANCE_CHAT = true,
    INSTANCE_CHAT_LEADER = true,
    BATTLEGROUND = true,
    BATTLEGROUND_LEADER = true,
}

local function Print(message)
    DEFAULT_CHAT_FRAME:AddMessage("|cff7f7fffHermitMode:|r " .. message)
end

local function InitDB()
    if type(HermitModeDB) ~= "table" then
        HermitModeDB = {}
    end

    -- Migrate settings from older versions that used an enabled boolean.
    if HermitModeDB.mode == nil then
        if HermitModeDB.enabled == false then
            HermitModeDB.mode = "off"
        else
            HermitModeDB.mode = defaults.mode
        end
    end

    if HermitModeDB.autoDND == nil then
        HermitModeDB.autoDND = defaults.autoDND
    end

    HermitModeDB.enabled = nil
end

local function Mode()
    return HermitModeDB.mode or defaults.mode
end

local function OutgoingBlocked(chatType)
    return Mode() ~= "off" and blockedChatTypes[chatType]
end

local function IncomingBlocked(event)
    return Mode() == "full" and blockedEvents[event]
end

-- Hide incoming social messages from all chat frames in FULL mode.
local function ChatFilter(self, event, message, author, ...)
    if IncomingBlocked(event) then
        return true
    end
    return false
end

for event in pairs(blockedEvents) do
    ChatFrame_AddMessageEventFilter(event, ChatFilter)
end

-- Capture both chat APIs used by current Classic clients.
local originalGlobalSendChatMessage = SendChatMessage
local originalCChatSendChatMessage = C_ChatInfo and C_ChatInfo.SendChatMessage

local function BlockOrSendGlobal(text, chatType, language, target)
    chatType = chatType or "SAY"

    if OutgoingBlocked(chatType) then
        Print("Outgoing message blocked for " .. tostring(chatType) .. ".")
        return
    end

    return originalGlobalSendChatMessage(text, chatType, language, target)
end

local function BlockOrSendCChat(text, chatType, languageID, target)
    chatType = chatType or "SAY"

    if OutgoingBlocked(chatType) then
        Print("Outgoing message blocked for " .. tostring(chatType) .. ".")
        return
    end

    return originalCChatSendChatMessage(text, chatType, languageID, target)
end

if type(originalGlobalSendChatMessage) == "function" then
    SendChatMessage = BlockOrSendGlobal
end

if C_ChatInfo and type(originalCChatSendChatMessage) == "function" then
    C_ChatInfo.SendChatMessage = BlockOrSendCChat
end

-- Battle.net whispers use a separate API and must be blocked independently.
if type(BNSendWhisper) == "function" then
    local originalBNSendWhisper = BNSendWhisper

    BNSendWhisper = function(presenceID, text)
        if Mode() ~= "off" then
            Print("Outgoing Battle.net whisper blocked.")
            return
        end

        return originalBNSendWhisper(presenceID, text)
    end
end

-- Close the edit box when it is opened directly in a blocked chat mode.
-- The actual send APIs are also intercepted, so this is an additional safeguard.
local function GuardEditBox(editBox)
    if not editBox or not editBox.GetAttribute then
        return
    end

    local chatType = editBox:GetAttribute("chatType")

    if OutgoingBlocked(chatType) then
        editBox:SetText("")
        editBox:Hide()
        Print("This chat type is blocked for outgoing messages.")
    end
end

if ChatFrame_OpenChat then
    hooksecurefunc("ChatFrame_OpenChat", function(text, chatFrame)
        if Mode() == "off" then
            return
        end

        C_Timer.After(0, function()
            local editBox = ChatEdit_ChooseBoxForSend and ChatEdit_ChooseBoxForSend(chatFrame) or ChatFrame1EditBox
            GuardEditBox(editBox)
        end)
    end)
end

if ChatEdit_ActivateChat then
    hooksecurefunc("ChatEdit_ActivateChat", function(editBox)
        C_Timer.After(0, function()
            GuardEditBox(editBox)
        end)
    end)
end

-- DND must bypass HermitMode's own outgoing-message protection.
local function RawSendDND(text)
    if originalCChatSendChatMessage then
        return originalCChatSendChatMessage(text, "DND")
    elseif originalGlobalSendChatMessage then
        return originalGlobalSendChatMessage(text, "DND")
    end
end

local function SetDND(enabled)
    if enabled then
        if not UnitIsDND("player") then
            RawSendDND("Hermit Mode active")
        end
    else
        if UnitIsDND("player") then
            RawSendDND("")
        end
    end
end

local function ApplyState()
    local mode = Mode()

    if mode == "full" then
        if HermitModeDB.autoDND then
            SetDND(true)
        end
        Print("FULL: incoming social chat is hidden and outgoing social messages are blocked.")

    elseif mode == "readonly" then
        if HermitModeDB.autoDND then
            SetDND(true)
        end
        Print("READ ONLY: you can read social chat, but outgoing social messages are blocked.")

    else
        if HermitModeDB.autoDND then
            SetDND(false)
        end
        Print("OFF: normal chat behavior restored.")
    end
end

local frame = CreateFrame("Frame")
frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function()
    InitDB()
    ApplyState()
end)

SLASH_HERMITMODE1 = "/hermit"
SlashCmdList.HERMITMODE = function(message)
    message = (message or ""):lower():match("^%s*(.-)%s*$")

    if message == "on" or message == "full" then
        HermitModeDB.mode = "full"
        ApplyState()

    elseif message == "readonly" or message == "read only" or message == "ro" then
        HermitModeDB.mode = "readonly"
        ApplyState()

    elseif message == "off" then
        HermitModeDB.mode = "off"
        ApplyState()

    elseif message == "status" or message == "" then
        Print("Mode: " .. string.upper(Mode()) .. " | Automatic DND: " .. (HermitModeDB.autoDND and "ON" or "OFF"))

    elseif message == "dnd on" then
        HermitModeDB.autoDND = true
        if Mode() ~= "off" then
            SetDND(true)
        end
        Print("Automatic DND: ON")

    elseif message == "dnd off" then
        HermitModeDB.autoDND = false
        SetDND(false)
        Print("Automatic DND: OFF")

    elseif message == "help" then
        Print("Commands: /hermit full, /hermit readonly, /hermit off, /hermit status, /hermit dnd on, /hermit dnd off")

    else
        Print("Unknown command. Type /hermit help for a list of commands.")
    end
end
