local MER, W, WF, F, E, I, V, P, G, L = unpack(ElvUI_MerathilisUI)
local module = MER:GetModule("MER_Theme") ---@class Theme
local LSM = E.Libs.LSM

local pairs = pairs

function module:Toggle(theme, value)
	if not self.Initialized then
		return
	end

	local pf = MER:GetModule("MER_Profiles")

	if theme == "gradientMode" then
		E.db.mui.themes.gradientMode.enable = value

		E.db.unitframe.colors.healthclass = true

		-- apply texture settings
		pf:UpdateProfileForGradient()

		F.Event.TriggerEvent("MER_Theme.DatabaseUpdate")
	elseif theme == "darkMode" then
		E.db.mui.themes.gradientMode.enable = false

		E.db.unitframe.colors.healthclass = false

		pf:UpdateProfileForTheme()
	end
end

local function updateCallbackWrapper(self, func, _, ignoreSettings)
	if not ignoreSettings then
		self:SettingsUpdate()
	end
	func()
end

function module:AddFrameToSettingsUpdate(category, frame, func)
	if self.settingsEvents[frame] ~= nil then
		return
	end

	self.settingsEvents[frame] = true
	F.Event.RegisterCallback(
		"MER_Theme.SettingsUpdate." .. category,
		F.Event.GenerateClosure(updateCallbackWrapper, self, func),
		frame
	)
end

function module:UpdateFadeDirection(frame)
	local unitType = frame.unitframeType or frame.__owner.unitframeType
	local fadeDirection = unitType and self.db.fadeDirection and self.db.fadeDirection[unitType]
	if not fadeDirection then
		fadeDirection = I.Enum.GradientMode.Direction.RIGHT
	end

	frame.fadeMode = I.Enum.GradientMode.Mode[I.Enum.GradientMode.Mode.HORIZONTAL]
	frame.fadeDirection = fadeDirection
end

function module:UpdateStatusBarFrame(frame)
	if not self.isEnabled or not self.db or not self.db.enable then
		return
	end
	if not frame then
		return
	end

	-- Configure Health
	if frame.Health then
		local healthTexture = LSM:Fetch("statusbar", self.db.textures.health)
		frame.Health:SetStatusBarTexture(healthTexture)
		if frame.Health.bg then
			frame.Health.bg:SetTexture(healthTexture)
		end
		self:UpdateFadeDirection(frame.Health)

		-- Hook if needed
		if not self:IsHooked(frame.Health, "PostUpdateColor") then
			self:RawHook(frame.Health, "PostUpdateColor", F.Event.GenerateClosure(self.PostUpdateHealthColor, self))
			self:AddFrameToSettingsUpdate(
				"Health",
				frame.Health,
				F.Event.GenerateClosure(self.PostUpdateHealthColor, self, frame.Health, frame.unit)
			)
		end
	end

	-- Configure CastBar
	if frame.Castbar then
		local castTexture = LSM:Fetch("statusbar", self.db.textures.cast)
		frame.Castbar:SetStatusBarTexture(castTexture)
		frame.Castbar.bg:SetTexture(castTexture)
		self:UpdateFadeDirection(frame.Castbar)

		-- Hook if needed
		if not self:IsHooked(frame.Castbar, "PostCastStart") then
			self:SecureHook(
				frame.Castbar,
				"PostCastStart",
				F.Event.GenerateClosure(self.PostUpdateCastColor, self, frame.Castbar, false)
			)
			self:SecureHook(
				frame.Castbar,
				"PostCastFail",
				F.Event.GenerateClosure(self.PostUpdateCastColor, self, frame.Castbar, true)
			)
			self:SecureHook(
				frame.Castbar,
				"PostCastInterruptible",
				F.Event.GenerateClosure(self.PostUpdateCastColor, self, frame.Castbar, false)
			)
		end
	end

	-- Configure Power Bar
	if frame.Power then
		local powerTexture = LSM:Fetch("statusbar", self.db.textures.power)
		frame.Power:SetStatusBarTexture(powerTexture)
		frame.Power.bg:SetTexture(powerTexture)
		self:UpdateFadeDirection(frame.Power)

		-- Hook if needed
		if not self:IsHooked(frame.Power, "PostUpdateColor") then
			self:RawHook(frame.Power, "PostUpdateColor", F.Event.GenerateClosure(self.PostUpdatePowerColor, self))
			self:AddFrameToSettingsUpdate(
				"Power",
				frame.Power,
				F.Event.GenerateClosure(self.PostUpdatePowerColor, self, frame.Power, frame.unit)
			)
		end
	end
end

function module:ConfigureStatusBarFrame(_, frame)
	if frame.Health then
		frame.Health._gradColorFunc = nil
		frame.Health.unitDead = nil
	end

	self:UpdateStatusBarFrame(frame)
end

function module:UpdateStatusBar(_, frame)
	if not self.isEnabled or not self.db or not self.db.enable then
		return
	end
	if not frame then
		return
	end

	local parentFrame = frame:GetParent()
	if not parentFrame then
		return
	end

	self:UpdateStatusBarFrame(parentFrame)
end

function module:UpdateStatusBars()
	for frame in pairs(self.uf.statusbars) do
		if frame then
			self:UpdateStatusBar(nil, frame)
		end
	end
end

-- Needed for gradientMode
function module:ForceSettings()
	E.db.unitframe.colors.healthclass = true
	E.db.unitframe.units.player.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.target.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.targettarget.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.focus.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.raid1.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.raid2.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.raid3.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.party.colorOverride = "USE_DEFAULT"
	E.db.unitframe.units.pet.colorOverride = "USE_DEFAULT"
end

function module:SettingsUpdate()
	-- Clear cache
	self.updateCache = {}

	-- Regenerate Colors
	F.Color.GenerateCache()

	-- ForceSettings
	self:ForceSettings()

	-- Refresh fade directions for all registered frames
	for frame in pairs(self.settingsEvents) do
		self:UpdateFadeDirection(frame)
	end
end

function module:TexturesUpdate()
	self:UpdateStatusBars()
end

function module:Disable()
	if not self.Initialized then
		return
	end
	if not self:IsHooked(self.uf, "Configure_HealthBar") then
		return
	end

	self:UnhookAll()
	self.uf:Update_AllFrames()

	F.EventManagerUnregisterAll(self.interruptNamespace)
end

function module:Enable()
	if not self.Initialized then
		return
	end
	if not self.isEnabled or not self.db or not self.db.enable then
		return
	end
	if self:IsHooked(self.uf, "Configure_HealthBar") then
		return
	end

	-- Apply settings
	self:SettingsUpdate()

	-- Hook functions for configure functions
	self:SecureHook(self.uf, "Configure_HealthBar", "ConfigureStatusBarFrame")
	self:SecureHook(self.uf, "Configure_Castbar", "ConfigureStatusBarFrame")
	self:SecureHook(self.uf, "Configure_Power", "ConfigureStatusBarFrame")

	-- Hook functions for update functions
	self:SecureHook(self.uf, "Update_StatusBars", "UpdateStatusBars")
	self:SecureHook(self.uf, "Update_StatusBar", "UpdateStatusBar")

	F.EventManagerRegister(self.interruptNamespace, "PLAYER_SPECIALIZATION_CHANGED", F.CheckInterruptSpells)

	F.EventManagerRegister(self.interruptNamespace, "PLAYER_ENTERING_WORLD", F.CheckInterruptSpells)
	F.EventManagerRegister(self.interruptNamespace, "PLAYER_LEVEL_CHANGED", F.CheckInterruptSpells)

	F.EventManagerRegister(self.interruptNamespace, "LEARNED_SPELL_IN_SKILL_LINE", F.CheckInterruptSpells)

	self.uf:Update_AllFrames()
end

function module:DatabaseUpdate()
	-- Set db
	self.db = E.db.mui.themes.gradientMode

	-- Set enable state
	local isEnabled = self.db and self.db.enable
	if self.isEnabled == isEnabled then
		return
	end
	self.isEnabled = isEnabled

	F.Event.ContinueOutOfCombat(function()
		F.Event.ContinueAfterElvUIUpdate(function()
			-- Disable only out of combat
			self:Disable()

			-- Enable only out of combat
			if self.isEnabled then
				if self.uf.Initialized then
					self:Enable()
				else
					self:SecureHook(self.uf, "Initialize", F.Event.GenerateClosure(self.Enable, self))
				end
			end
		end)
	end)
end

function module:Initialize()
	if self.Initialized then
		return
	end

	self.uf = E:GetModule("UnitFrames")

	self.updateCache = {}
	self.settingsEvents = {}

	F.Event.RegisterOnceCallback("MER.InitializedSafe", F.Event.GenerateClosure(self.DatabaseUpdate, self))
	F.Event.RegisterCallback("MER.DatabaseUpdate", self.DatabaseUpdate, self)
	F.Event.RegisterCallback("MER_Theme.DatabaseUpdate", self.DatabaseUpdate, self)
	F.Event.RegisterCallback("MER_Theme.SettingsUpdate", self.SettingsUpdate, self)
	F.Event.RegisterCallback("MER_Theme.TexturesUpdate", self.TexturesUpdate, self)

	self.interruptNamespace = "GR_INTERRUPT"

	self.Initialized = true
end

MER:RegisterModule(module:GetName())
