local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme

local InterruptCooldowns = { States = {}, Icons = {}, Prefix = "SUIINT1" }
SlickUI.InterruptCooldowns = InterruptCooldowns

local SPELLS = {
	[47528] = 15, [183752] = 15, [106839] = 15, [78675] = 60,
	[351338] = 40, [147362] = 24, [187707] = 15, [2139] = 24,
	[116705] = 15, [96231] = 15, [15487] = 45, [1766] = 15,
	[57994] = 12, [119910] = 24, [19647] = 24, [132409] = 24,
	[119914] = 30, [89766] = 30, [6552] = 15,
}

local function Readable(value)
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return value ~= nil
end

local function Settings()
	return SlickUI.DB and SlickUI.DB.interruptCooldowns
end

local function UnitNameSafe(unit)
	local name = UnitName(unit)
	return Readable(name) and type(name) == "string" and name or nil
end

local function UnitGUIDSafe(unit)
	local guid = UnitGUID(unit)
	return Readable(guid) and type(guid) == "string" and guid or nil
end

local function NormalizePlayerName(name)
	if not Readable(name) or type(name) ~= "string" then return nil end
	return name:lower():gsub("%s+", "")
end

local function FindUnitByName(name)
	local wanted = NormalizePlayerName(name)
	if not wanted then return nil end
	local wantedShort = wanted:match("^[^-]+")
	for index = 1, 40 do
		local unit = IsInRaid() and ("raid" .. index) or (index == 1 and "player" or "party" .. (index - 1))
		if UnitExists(unit) then
			local shortName = UnitNameSafe(unit)
			local fullName
			if UnitFullName then
				local character, realm = UnitFullName(unit)
				if Readable(character) and type(character) == "string" then
					fullName = character
					if Readable(realm) and type(realm) == "string" and realm ~= "" then
						fullName = fullName .. "-" .. realm
					end
				end
			end
			local unitShort = NormalizePlayerName(shortName)
			local unitFull = NormalizePlayerName(fullName)
			if unitFull == wanted or unitShort == wanted or unitShort == wantedShort then return unit end
		end
	end
end

function InterruptCooldowns:SetCooldown(guid, spellID, duration)
	if not Readable(guid) or type(guid) ~= "string" or type(spellID) ~= "number" then return end
	duration = tonumber(duration) or SPELLS[spellID]
	if not duration then return end
	local info = C_Spell and C_Spell.GetSpellInfo and C_Spell.GetSpellInfo(spellID)
	self.States[guid] = {
		spellID = spellID,
		icon = info and info.iconID,
		ends = GetTime() + math.max(0, duration),
	}
end

function InterruptCooldowns:CreateIcon(frame)
	-- Regions remain cosmetic children of the secure unit button, avoiding
	-- protected frame Show/Hide operations while the group is in combat.
	local texture = frame:CreateTexture(nil, "OVERLAY", nil, 7)
	texture:SetSize(18, 18)
	texture:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -2, -2)
	texture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
	local text = Theme:CreateText(frame, 9)
	text:SetPoint("CENTER", texture, "CENTER", 0, 0)
	text:SetTextColor(1, 1, 1, 1)
	local holder = { Texture = texture, Text = text }
	texture:Hide()
	text:Hide()
	self.Icons[frame] = holder
	return holder
end

function InterruptCooldowns:UpdateFrameIcons()
	local settings = Settings()
	for frame in pairs(SlickUI.GroupFrames and SlickUI.GroupFrames.LiveFrames or {}) do
		local icon = self.Icons[frame] or self:CreateIcon(frame)
		local guid = frame.unit and UnitGUIDSafe(frame.unit)
		local state = guid and self.States[guid]
		if settings and settings.enabled and settings.showOnFrames and state then
			local remaining = math.max(0, state.ends - GetTime())
			icon.Texture:SetTexture(state.icon or 134400)
			icon.Text:SetText(remaining > 0 and tostring(math.ceil(remaining)) or "R")
			icon.Texture:SetDesaturated(remaining > 0)
			icon.Texture:Show()
			icon.Text:Show()
		else
			icon.Texture:Hide()
			icon.Text:Hide()
		end
	end
end

function InterruptCooldowns:CreateWindow()
	if self.Window then return self.Window end
	local frame = CreateFrame("Frame", "SlickUIInterruptCooldownWindow", UIParent, "BackdropTemplate")
	frame:SetSize(280, 230)
	frame:SetFrameStrata("MEDIUM")
	frame:SetClampedToScreen(true)
	frame.SlickMoverOnly = true
	Theme:CreatePanel(frame)
	local title = Theme:CreateText(frame, 15)
	title:SetPoint("TOPLEFT", 12, -10)
	title:SetText("Group Interrupts")
	title:SetTextColor(unpack(Theme.Colors.goldText))
	local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
	close:SetPoint("TOPRIGHT", -2, -2)
	close:SetScript("OnClick", function() InterruptCooldowns:SetWindowShown(false) end)
	frame.Rows = {}
	for index = 1, 40 do
		local row = Theme:CreateText(frame, 11)
		local column = math.floor((index - 1) / 20)
		local line = (index - 1) % 20
		row:SetPoint("TOPLEFT", 14 + column * 260, -35 - line * 18)
		row:SetWidth(245)
		row:SetJustifyH("LEFT")
		row:Hide()
		frame.Rows[index] = row
	end
	self.Window = frame
	SlickUI.UnitFrames.interruptCooldowns = frame
	SlickUI.Movers:Register(frame, "interruptCooldowns", "Group Interrupts")
	return frame
end

function InterruptCooldowns:UpdateWindow()
	local frame = self:CreateWindow()
	local settings = Settings()
	frame:SetBackdropColor(0.015, 0.018, 0.025, settings.backgroundAlpha or 0.65)
	local rowIndex = 0
	for index = 1, 40 do
		local unit = IsInRaid() and ("raid" .. index) or (index == 1 and "player" or "party" .. (index - 1))
		if UnitExists(unit) then
			local name, guid = UnitNameSafe(unit), UnitGUIDSafe(unit)
			local state = guid and self.States[guid]
			if name then
				rowIndex = rowIndex + 1
				local remaining = state and math.max(0, state.ends - GetTime()) or nil
				local status = not state and "Unknown" or (remaining > 0 and (math.ceil(remaining) .. "s") or "Ready")
				local color = state and remaining <= 0 and "|cff55ff77" or (state and "|cffffcc44" or "|cff999999")
				frame.Rows[rowIndex]:SetText(string.format("%s%-18s  %s|r", color, name, status))
				frame.Rows[rowIndex]:Show()
			end
		end
	end
	for index = rowIndex + 1, #frame.Rows do frame.Rows[index]:Hide() end
	frame:SetWidth(rowIndex > 20 and 540 or 280)
	frame:SetHeight(math.max(70, 48 + math.min(rowIndex, 20) * 18))
	frame:SetShown(settings.windowShown == true)
end

function InterruptCooldowns:SetWindowShown(shown)
	Settings().windowShown = shown == true
	self:UpdateWindow()
	if SlickUI.InformationBars then SlickUI.InformationBars:UpdateBlocks() end
end

function InterruptCooldowns:ToggleWindow()
	self:SetWindowShown(not Settings().windowShown)
end

function InterruptCooldowns:GetWidgetText()
	local ready, total = 0, 0
	for index = 1, 40 do
		local unit = IsInRaid() and ("raid" .. index) or (index == 1 and "player" or "party" .. (index - 1))
		local guid = UnitExists(unit) and UnitGUIDSafe(unit)
		local state = guid and self.States[guid]
		if guid then
			total = total + 1
			if state and state.ends <= GetTime() then ready = ready + 1 end
		end
	end
	return string.format("Interrupts: %d/%d ready", ready, total)
end

function InterruptCooldowns:OpenSettings()
	if SlickUI.Options then
		SlickUI.Options:Open()
		C_Timer.After(0, function() SlickUI.Options:SelectPage("alerts") end)
	end
end

function InterruptCooldowns:HandleWidgetClick(_, button)
	if button == "RightButton" then self:OpenSettings() else self:ToggleWindow() end
end

function InterruptCooldowns:BroadcastReadySpell()
	local spellID = SlickUI.InterruptAlerts and SlickUI.InterruptAlerts:GetSpellID()
	local guid = UnitGUIDSafe("player")
	if type(spellID) == "number" and SPELLS[spellID] and guid then
		self:SetCooldown(guid, spellID, 0)
		if C_ChatInfo and C_ChatInfo.SendAddonMessage and IsInGroup() then
			C_ChatInfo.SendAddonMessage(self.Prefix, "R;" .. tostring(spellID), IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or (IsInRaid() and "RAID" or "PARTY"))
		end
	end
end

function InterruptCooldowns:HandleUnitSpellcast(unit, castSpellID)
	if type(unit) ~= "string" or not Readable(castSpellID) or type(castSpellID) ~= "number" then return end
	local duration = SPELLS[castSpellID]
	if not duration then return end
	local guid = UnitGUIDSafe(unit)
	if not guid then return end
	self:SetCooldown(guid, castSpellID, duration)
	if unit == "player" and C_ChatInfo and C_ChatInfo.SendAddonMessage and IsInGroup() then
		C_ChatInfo.SendAddonMessage(self.Prefix, tostring(castSpellID) .. ";" .. tostring(duration), IsInGroup(LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT" or (IsInRaid() and "RAID" or "PARTY"))
	end
end

-- Combat-log registration is forbidden to tainted addon code on current
-- clients. Group cooldowns arrive through UNIT_SPELLCAST_SUCCEEDED on the
-- interrupt-alert dispatcher and through SlickUI addon messages.
local InterruptCooldownEvents = CreateFrame("Frame")
InterruptCooldownEvents:RegisterEvent("GROUP_ROSTER_UPDATE")
InterruptCooldownEvents:RegisterEvent("PLAYER_ENTERING_WORLD")
InterruptCooldownEvents:RegisterEvent("CHAT_MSG_ADDON")

function InterruptCooldowns:Initialize()
	self:CreateWindow()
	if C_ChatInfo and C_ChatInfo.RegisterAddonMessagePrefix then C_ChatInfo.RegisterAddonMessagePrefix(self.Prefix) end
	local events = InterruptCooldownEvents
	events:SetScript("OnEvent", function(_, event, prefix, message, _, sender)
		if event == "GROUP_ROSTER_UPDATE" or event == "PLAYER_ENTERING_WORLD" then
			C_Timer.After(1, function() InterruptCooldowns:BroadcastReadySpell() end)
		elseif event == "CHAT_MSG_ADDON" and prefix == InterruptCooldowns.Prefix and type(message) == "string" then
			local readySpell = message:match("^R;(%d+)$")
			local spellText, durationText = message:match("^(%d+);([%d%.]+)$")
			local unit = FindUnitByName(sender)
			local guid = unit and UnitGUIDSafe(unit)
			if guid and readySpell then
				InterruptCooldowns:SetCooldown(guid, tonumber(readySpell), 0)
			elseif guid then
				InterruptCooldowns:SetCooldown(guid, tonumber(spellText), tonumber(durationText))
			end
		end
	end)
	self.Events = events
	self.Ticker = C_Timer.NewTicker(0.25, function()
		InterruptCooldowns:UpdateFrameIcons()
		InterruptCooldowns:UpdateWindow()
	end)
	self:UpdateWindow()
	C_Timer.After(1, function() InterruptCooldowns:BroadcastReadySpell() end)
end

SlickUI:RegisterModule("InterruptCooldowns", InterruptCooldowns)
