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

local GroundHazardAlerts = {
	ActiveUntil = 0,
	PreviewUntil = 0,
	LastSoundAt = 0,
	Elapsed = 0,
	RecentDirectHits = {},
	RecentUnitWoundAt = 0,
}
SlickUI.GroundHazardAlerts = GroundHazardAlerts

local ALERT_SOUND = "Interface\\AddOns\\SlickUI\\Media\\Sounds\\SlickUI_Double_Buzzer.wav"
local SOUND_INTERVAL = 0.90
local HAZARD_GRACE = 2.75
local DIRECT_REPEAT_WINDOW = 1.35
local UNIT_REPEAT_WINDOW = 3.25
local _, _, _, BUILD_NUMBER = GetBuildInfo()
local COMBAT_LOG_AVAILABLE = (tonumber(BUILD_NUMBER) or 0) < 120000

function GroundHazardAlerts:IsSupported()
	-- Retail 12 blocks the combat-log events used by traditional ground-effect
	-- addons. UNIT_COMBAT remains available as a deliberately less precise,
	-- protected-value-safe fallback.
	return true
end

function GroundHazardAlerts:GetDetectionMode()
	return COMBAT_LOG_AVAILABLE and "combat-log" or "adaptive"
end

local function CanReadValue(value)
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return true
end

local function IsPlayerDestination(guid, flags)
	if CanReadValue(guid) and type(guid) == "string" then
		local playerGUID = UnitGUID("player")
		if CanReadValue(playerGUID) and type(playerGUID) == "string" then
			return guid == playerGUID
		end
	end
	if type(CombatLog_Object_IsA) == "function" and CanReadValue(flags)
		and type(flags) == "number" and type(COMBATLOG_FILTER_ME) == "number" then
		local ok, matches = pcall(CombatLog_Object_IsA, flags, COMBATLOG_FILTER_ME)
		return ok and CanReadValue(matches) and matches == true
	end
	return false
end

local function PlayerHasMatchingDebuff(spellID)
	if C_UnitAuras and type(C_UnitAuras.GetPlayerAuraBySpellID) == "function" then
		local ok, aura = pcall(C_UnitAuras.GetPlayerAuraBySpellID, spellID)
		if ok and aura then return true end
	end
	if AuraUtil and type(AuraUtil.FindAuraBySpellID) == "function" then
		local ok, aura = pcall(AuraUtil.FindAuraBySpellID, spellID, "player", "HARMFUL")
		if ok and aura then return true end
	end
	return false
end

function GroundHazardAlerts:GetSettings()
	return SlickUI.DB and SlickUI.DB.alerts and SlickUI.DB.alerts.groundHazard
end

function GroundHazardAlerts:CreateWarningFrame()
	if self.WarningFrame then return self.WarningFrame end
	-- Create this lazily only when an alert is needed. Keeping it unnamed and
	-- outside the login/reload path avoids participating in protected UI layout.
	local frame = CreateFrame("Frame", nil, UIParent)
	frame:SetSize(680, 90)
	frame:SetPoint("CENTER", UIParent, "CENTER", 0, 110)
	frame:SetFrameStrata("HIGH")
	frame:Hide()

	local text = SlickUI.Theme:CreateText(frame, 30)
	text:SetPoint("CENTER")
	text:SetText("MOVE OUT OF DANGER!")
	text:SetTextColor(1, 0.05, 0.03, 1)
	text:SetShadowColor(0, 0, 0, 1)
	text:SetShadowOffset(3, -3)
	frame.Label = text

	self.WarningFrame = frame
	return frame
end

function GroundHazardAlerts:PlaySound()
	PlaySoundFile(ALERT_SOUND, "Master")
end

function GroundHazardAlerts:Activate(now)
	local settings = self:GetSettings()
	if not settings or not settings.enabled then return end
	now = now or GetTime()
	self.ActiveUntil = math.max(self.ActiveUntil or 0, now + HAZARD_GRACE)
	self:CreateWarningFrame():Show()
	if settings.sound and now - (self.LastSoundAt or 0) >= SOUND_INTERVAL then
		self.LastSoundAt = now
		self:PlaySound()
	end
end

function GroundHazardAlerts:Stop()
	self.ActiveUntil = 0
	self.PreviewUntil = 0
	if self.WarningFrame then
		self.WarningFrame:Hide()
		self.WarningFrame:SetAlpha(1)
	end
end

function GroundHazardAlerts:StartPreview()
	local now = GetTime()
	self.PreviewUntil = now + 3.2
	self.LastSoundAt = 0
	self:CreateWarningFrame():Show()
end

function GroundHazardAlerts:HandleCombatLogEvent()
	local settings = self:GetSettings()
	if not settings or not settings.enabled then return end
	local ok, info = pcall(function()
		return { CombatLogGetCurrentEventInfo() }
	end)
	if not ok or type(info) ~= "table" then return end

	local subevent = info[2]
	if not CanReadValue(subevent) or type(subevent) ~= "string" then return end
	if not IsPlayerDestination(info[8], info[10]) then return end

	local now = GetTime()
	if subevent == "ENVIRONMENTAL_DAMAGE" then
		local environmentalType = info[12]
		if CanReadValue(environmentalType) and (environmentalType == "FIRE" or environmentalType == "LAVA") then
			self:Activate(now)
		end
		return
	end

	if subevent ~= "SPELL_PERIODIC_DAMAGE" and subevent ~= "SPELL_DAMAGE" then return end
	local spellID = info[12]
	if not CanReadValue(spellID) or type(spellID) ~= "number" then return end
	-- A matching harmful aura indicates an ordinary damage-over-time effect on
	-- the player. Those are handled by cleanse/defensive tools, not this alarm.
	if PlayerHasMatchingDebuff(spellID) then return end

	if subevent == "SPELL_PERIODIC_DAMAGE" then
		self:Activate(now)
		return
	end

	-- Direct-damage ground effects exist, but a single direct hit is too broad.
	-- Require the same spell to hit again promptly before treating it as ground.
	local previous = self.RecentDirectHits[spellID]
	self.RecentDirectHits[spellID] = now
	if type(previous) == "number" and now - previous <= DIRECT_REPEAT_WINDOW then
		self:Activate(now)
	end
end

function GroundHazardAlerts:HandleUnitCombat(action, schoolMask)
	local settings = self:GetSettings()
	if not settings or not settings.enabled then return end
	if not CanReadValue(action) or type(action) ~= "string" or action ~= "WOUND" then return end
	if not CanReadValue(schoolMask) or type(schoolMask) ~= "number" then return end

	-- Pure physical damage is overwhelmingly ordinary melee and would make the
	-- fallback noisy. Magical and mixed-school damage is considered only after
	-- it repeats quickly, which catches common pools, fire, poison, and similar
	-- ticking hazards without alarming for a single spell hit.
	if schoolMask <= 1 then return end

	local now = GetTime()
	local previous = self.RecentUnitWoundAt
	self.RecentUnitWoundAt = now
	if type(previous) == "number" and previous > 0 and now - previous <= UNIT_REPEAT_WINDOW then
		self:Activate(now)
	end
end

function GroundHazardAlerts:Update(elapsed)
	self.Elapsed = self.Elapsed + elapsed
	if self.Elapsed < 0.05 then return end
	self.Elapsed = 0

	local now = GetTime()
	for spellID, timestamp in pairs(self.RecentDirectHits) do
		if now - timestamp > DIRECT_REPEAT_WINDOW then self.RecentDirectHits[spellID] = nil end
	end
	local settings = self:GetSettings()
	local active = settings and settings.enabled
		and (now <= (self.ActiveUntil or 0) or now <= (self.PreviewUntil or 0))
	if not active then
		if self.WarningFrame and self.WarningFrame:IsShown() then self.WarningFrame:Hide() end
		return
	end

	local frame = self:CreateWarningFrame()
	frame:Show()
	local pulse = (math.sin(now * math.pi * 4.2) + 1) * 0.5
	frame:SetAlpha(0.48 + (0.52 * pulse))
	if settings.sound and now - (self.LastSoundAt or 0) >= SOUND_INTERVAL then
		self.LastSoundAt = now
		self:PlaySound()
	end

end

function GroundHazardAlerts:Refresh()
	local settings = self:GetSettings()
	if not settings or not settings.enabled then self:Stop() end
end

function GroundHazardAlerts:Initialize()
	local frame = CreateFrame("Frame")
	frame:RegisterEvent("PLAYER_ENTERING_WORLD")
	if COMBAT_LOG_AVAILABLE then
		frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	else
		frame:RegisterUnitEvent("UNIT_COMBAT", "player")
	end
	frame:SetScript("OnEvent", function(_, event, unitTarget, action, _, _, schoolMask)
		if event == "PLAYER_ENTERING_WORLD" then
			GroundHazardAlerts:Stop()
			wipe(GroundHazardAlerts.RecentDirectHits)
			GroundHazardAlerts.RecentUnitWoundAt = 0
		elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then
			GroundHazardAlerts:HandleCombatLogEvent()
		elseif event == "UNIT_COMBAT" then
			-- RegisterUnitEvent already limits this to the player, so the unit token
			-- never needs a protected comparison here.
			GroundHazardAlerts:HandleUnitCombat(action, schoolMask)
		end
	end)
	frame:SetScript("OnUpdate", function(_, elapsed)
		GroundHazardAlerts:Update(elapsed)
	end)
	self.EventFrame = frame
end

SlickUI:RegisterModule("GroundHazardAlerts", GroundHazardAlerts)
