local _, ns = ...
local B, C, L, DB = unpack(ns)
local module = B:GetModule("Chat")

local gsub, strfind, strmatch, format, strsub, strlen, strupper = string.gsub, string.find, string.match, string.format, string.sub, string.len, string.upper
local tostring = tostring
local BetterDate, time, date, GetCVarBool = BetterDate, time, date, GetCVarBool
local RemoveExtraSpaces = RemoveExtraSpaces
local INTERFACE_ACTION_BLOCKED = INTERFACE_ACTION_BLOCKED
local C_DateAndTime_GetCurrentCalendarTime = C_DateAndTime.GetCurrentCalendarTime
local colon = HEADER_COLON
local isCN = strlen(colon) > 1

-- Timestamp
local timestampFormat = {
	[2] = "[%I:%M %p] ",
	[3] = "[%I:%M:%S %p] ",
	[4] = "[%H:%M] ",
	[5] = "[%H:%M:%S] ",
}

local function GetCurrentTime()
	local locTime = time()
	local realmTime = not GetCVarBool("timeMgrUseLocalTime") and C_DateAndTime_GetCurrentCalendarTime()
	if realmTime then
		realmTime.day = realmTime.monthDay
		realmTime.min = realmTime.minute
		realmTime.sec = date("%S") -- no sec value for realm time
		realmTime = time(realmTime)
	end

	return locTime, realmTime
end

-- Author Logo
local function AddAuthorLogo(link, unitName)
	if unitName and DB.Devs[unitName] then
		return "|T"..DB.chatLogo..":12:12|t"..link
	end
	return link
end

-- Channel name abbr
local LEADERSHIP = {
	PartyLeader = strmatch(CHAT_PARTY_LEADER_GET, "|h%[(.-)%]|h"),
	PartyGuide = strmatch(CHAT_PARTY_GUIDE_GET, "|h%[(.-)%]|h"),
	RaidLeader = strmatch(CHAT_RAID_LEADER_GET, "|h%[(.-)%]|h"),
	InstLeader = strmatch(CHAT_INSTANCE_CHAT_LEADER_GET, "|h%[(.-)%]|h"),
}

local CHANNEL_ABBR = {
	PARTY = {
		abbr = "P",
		leaders = {
			[LEADERSHIP.PartyLeader] = "PL",
			[LEADERSHIP.PartyGuide] = "PG",
		},
	},
	RAID = {
		abbr = "R",
		leaders = {
			[LEADERSHIP.RaidLeader] = "RL",
		},
	},
	INSTANCE_CHAT = {
		abbr = "I",
		leaders = {
			[LEADERSHIP.InstLeader] = "IL",
		},
	},
	GUILD = { abbr = "G" },
	OFFICER = { abbr = "O" },
}

local CHANNEL_ABBR_LOCALES = {
	PARTY = {
		abbr = L["PartyAbbr"],
		leaders = {
			[LEADERSHIP.PartyLeader] = L["PartyLeaderAbbr"],
			[LEADERSHIP.PartyGuide]  = L["PartyGuideAbbr"],
		},
	},
	RAID = {
		abbr = L["RaidAbbr"],
		leaders = {
			[LEADERSHIP.RaidLeader] = L["RaidLeaderAbbr"],
		},
	},
	INSTANCE_CHAT = {
		abbr = L["InstAbbr"],
		leaders = {
			[LEADERSHIP.InstLeader] = L["InstLeaderAbbr"],
		},
	},
	GUILD = { abbr = L["GuildAbbr"] },
	OFFICER = { abbr = L["OfficerAbbr"] },
}

local matchPattern = "(|H(%w+):?([^:]+):?(%d*)|h)%[(.-)%]|h"

local function AbbrChannelName(prefix, linkType, channel, channelID, channelName)
	if C.db["Chat"]["ChannelAbbr"] == 1 then return end

	if linkType ~= "channel" then return end

	if channel == "channel" then
		return prefix.."["..channelID.."]|h"
	end

	local channels = C.db["Chat"]["ChannelAbbr"] == 2 and CHANNEL_ABBR or CHANNEL_ABBR_LOCALES
	local data = channels[channel]
	if not data then
		return prefix.."["..channelName.."]|h"
	end

	local abbr = data.abbr
	local isLeader = data.leaders and data.leaders[channelName]
	if isLeader then
		abbr = isLeader
	end

	return prefix.."["..abbr.."]|h"
end

-- Kill colon before message
local channels = {
	SAY = not isCN,
	YELL = not isCN,
	WHISPER = not isCN,
	GUILD = not isCN,
	OFFICER = not isCN,
	CHANNEL = not isCN,
	PARTY = true,
	RAID = true,
	INSTANCE_CHAT = not isCN,
}

local cnColonChannels = {
	SAY = true,
	YELL = true,
	WHISPER = true,
	GUILD = true,
	OFFICER = true,
	CHANNEL = true,
	PARTY = true,
	RAID = true,
	INSTANCE_CHAT = true,
}

local cnPattern = "(|Hplayer[^]]*:([^:]+):[^]]*%].-)"..colon.."%s"
local enPattern = "(|Hplayer[^]]*:([^:]+):[^]]*%].-):%s"

local function KillColon(link, tag)
	if channels[tag] then
		return link.." "
	end
end

local function KillCNColon(link, tag)
	if cnColonChannels[tag] then
		return link.." "
	end
end

local function convertLink(text, value)
	return "|Hurl:"..tostring(value).."|h"..DB.InfoColor..text.."|r|h"
end

local function highlightURL(_, url)
	return " "..convertLink("["..url.."]", url).." "
end

-- FCFManager_GetChatTarget clone (safeguard)
local function GetChatTarget(chatGroup, playerTarget, channelTarget)
	if chatGroup == "CHANNEL" then
		return tostring(channelTarget)
	elseif chatGroup == "WHISPER" or chatGroup == "BN_WHISPER" then
		return playerTarget and strsub(playerTarget, 1, 2) ~= "|K" and strupper(playerTarget) or playerTarget
	end
end

-- Dedup cache: prevent double-processing when addons like WhisperPop
-- re-invoke event filters manually after WoW already processed them,
-- which would cause self:AddMessage to be called twice -> duplicate messages.
local processedLines = {}
local processedCount = 0
local PROCESSED_LINES_MAX = 200

-- Chat event filter: format message, respect window settings, use correct colors
local function ChatMsgFilter(self, event, msg, sender, language, channelString, target, flags, zoneChannelID, channelIndex, channelBaseName, languageID, lineID, senderGUID, bnSenderID, isMobile, isSubtitle, hideSenderInLetterbox, suppressRaidIcons)
	if B:IsSecretValue(msg) then return end

	if strfind(msg, INTERFACE_ACTION_BLOCKED) and not DB.isDeveloper then
		return true
	end

	-- Dedup: skip if this message was already processed for this chat frame
	if lineID and lineID > 0 then
		local key = self:GetName() .. "_" .. lineID
		if processedLines[key] then
			return true
		end
		processedLines[key] = true
		processedCount = processedCount + 1
		if processedCount > PROCESSED_LINES_MAX then
			wipe(processedLines)
			processedCount = 0
		end
	end

	-- Per-window visibility check
	local chatType = strsub(event, 10)
	local chatGroup = ChatFrameUtil.GetChatCategory(chatType)
	local channelLength = strlen(channelString)

	-- For CHANNEL type: check self.channelList (mirrors Blizzard's logic in MessageEventHandler)
	-- For non-CHANNEL types: use FCFManager_ShouldSuppressMessage
	if chatType == "CHANNEL" then
		if channelLength > 0 then
			local found = false
			for index, value in pairs(self.channelList) do
				if channelLength > strlen(value) then
					if ((zoneChannelID > 0) and (self.zoneChannelList and self.zoneChannelList[index] == zoneChannelID)) or (strupper(value) == strupper(channelBaseName or "")) then
						found = true
						break
					end
				end
			end
			if not found then
				return true
			end
		end
	else
		local chatTarget = GetChatTarget(chatGroup, sender, channelIndex)
		if FCFManager_ShouldSuppressMessage(self, chatGroup, chatTarget) then
			return true
		end
	end

	-- Get correct color
	local info
	if chatType == "CHANNEL" and channelIndex and channelIndex > 0 then
		info = ChatTypeInfo["CHANNEL"..channelIndex] or ChatTypeInfo[chatType]
	else
		info = ChatTypeInfo[chatType]
	end
	info = info or ChatTypeInfo["SYSTEM"]

	-- URL highlighting
	msg = gsub(msg, "(%s?)(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?:%d%d?%d?%d?%d?)(%s?)", highlightURL)
	msg = gsub(msg, "(%s?)(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?)(%s?)", highlightURL)
	msg = gsub(msg, "(%s?)([%w_-]+%.?[%w_-]+%.[%w_-]+:%d%d%d?%d?%d?)(%s?)", highlightURL)
	msg = gsub(msg, "(%s?)(%a+://[%w_/%.%?%%=~&-'%-]+)(%s?)", highlightURL)
	msg = gsub(msg, "(%s?)(www%.[%w_/%.%?%%=~&-'%-]+)(%s?)", highlightURL)
	msg = gsub(msg, "(%s?)([_%w-%.~-]+@[_%w-]+%.[_%w-%.]+)(%s?)", highlightURL)

	-- Build formatted message
	local formatKey = _G["CHAT_"..chatType.."_GET"]
	if not formatKey then return end

	local coloredName = ChatFrameUtil.GetDecoratedSenderName(event, msg, sender, language, channelString, target, flags, zoneChannelID, channelIndex, channelBaseName, languageID, lineID, senderGUID, bnSenderID, isMobile)
	local pflag = ChatFrameUtil.GetPFlag(flags, zoneChannelID, channelIndex)

	local playerLink
	if chatType == "BN_WHISPER" or chatType == "BN_WHISPER_INFORM" then
		playerLink = GetBNPlayerLink(sender, "["..coloredName.."]", bnSenderID, lineID, chatGroup, 0)
	else
		playerLink = GetPlayerLink(sender, "["..coloredName.."]", lineID, chatGroup, 0)
	end

	msg = gsub(msg, "%%", "%%%%")
	msg = C_ChatInfo.ReplaceIconAndGroupExpressions(msg, suppressRaidIcons)
	msg = RemoveExtraSpaces(msg)

	local outMsg = format(formatKey..msg, pflag..playerLink)

	-- Add channel prefix for custom channels
	if channelLength > 0 then
		local channelName = ChatFrameUtil.ResolvePrefixedChannelName(channelString)
		if channelName then
			outMsg = "|Hchannel:channel:"..(channelIndex or 0).."|h["..channelName.."]|h "..outMsg
		end
	end

	-- Apply UI modifications
	if OrzUISetDB["TimestampFormat"] > 1 then
		local locTime, realmTime = GetCurrentTime()
		local timeStamp = BetterDate(DB.GreyColor..timestampFormat[OrzUISetDB["TimestampFormat"]].."|r", realmTime or locTime)
		outMsg = timeStamp..outMsg
	end

	outMsg = gsub(outMsg, "(|Hplayer:([^|:]+))", AddAuthorLogo)
	if isCN then outMsg = gsub(outMsg, cnPattern, KillCNColon) end
	outMsg = gsub(outMsg, enPattern, KillColon)
	outMsg = gsub(outMsg, matchPattern, AbbrChannelName)

	self:AddMessage(outMsg, info.r, info.g, info.b, info.id)

	return true
end

function module:ChannelRename()
	local events = {
		"CHAT_MSG_SAY", "CHAT_MSG_YELL",
		"CHAT_MSG_GUILD", "CHAT_MSG_OFFICER",
		"CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER", "CHAT_MSG_PARTY_GUIDE",
		"CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER",
		"CHAT_MSG_INSTANCE_CHAT", "CHAT_MSG_INSTANCE_CHAT_LEADER",
		"CHAT_MSG_WHISPER", "CHAT_MSG_WHISPER_INFORM",
		"CHAT_MSG_BN_WHISPER", "CHAT_MSG_BN_WHISPER_INFORM",
		"CHAT_MSG_CHANNEL", "CHAT_MSG_MONSTER_SAY"
	}
	for _, event in ipairs(events) do
		ChatFrame_AddMessageEventFilter(event, ChatMsgFilter)
	end
end