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

local Theme = {}
SlickUI.Theme = Theme

Theme.Font = "Interface\\AddOns\\SlickUI\\Media\\Fonts\\Roboto.ttf"
Theme.ChatFont = "Interface\\AddOns\\SlickUI\\Media\\Fonts\\PTSans.ttf"
Theme.Texture = "Interface\\Buttons\\WHITE8X8"
Theme.Icon = "Interface\\AddOns\\SlickUI\\Media\\Icons\\SlickUI_Icon.png"
Theme.ManagedFonts = setmetatable({}, { __mode = "k" })
Theme.WindowFontBaselines = setmetatable({}, { __mode = "k" })
Theme.FontControlledWindows = setmetatable({}, { __mode = "k" })

Theme.Colors = {
	panel = { 0.035, 0.04, 0.055, 0.92 },
	panelSoft = { 0.09, 0.10, 0.13, 0.94 },
	border = { 1.00, 0.67, 0.12, 0.92 },
	borderMuted = { 0.34, 0.36, 0.42, 0.90 },
	health = { 0.10, 0.78, 0.60, 1.00 },
	hostile = { 0.92, 0.07, 0.045, 1.00 },
	neutral = { 1.00, 0.82, 0.10, 1.00 },
	power = { 0.12, 0.48, 0.92, 1.00 },
	cast = { 1.00, 0.62, 0.12, 1.00 },
	castInterruptible = { 0.16, 0.82, 0.38, 1.00 },
	castProtected = { 0.85, 0.16, 0.12, 1.00 },
	text = { 0.96, 0.97, 1.00, 1.00 },
	mutedText = { 0.68, 0.71, 0.78, 1.00 },
	goldText = { 1.00, 0.77, 0.24, 1.00 },
}

function Theme:GetFontScale()
	local settings = SlickUI.DB and SlickUI.DB.systemFonts
	return math.max(0.75, math.min(2.50, tonumber(settings and settings.slickUIScale) or 1))
end

function Theme:ApplyManagedFont(fontString, definition)
	if not fontString or not definition then return end
	local windowFactor = 1
	local owner = fontString:GetParent()
	while owner do
		if self.FontControlledWindows[owner] then
			windowFactor = self:GetWindowFontSize(owner) / 10
			break
		end
		owner = owner:GetParent()
	end
	local scaledSize = math.max(6, math.floor(definition.size * self:GetFontScale() * windowFactor + 0.5))
	fontString:SetFont(self.Font, scaledSize, definition.flags)
	fontString:SetShadowOffset(1, -1)
	fontString:SetShadowColor(0, 0, 0, 0.85)
end

function Theme:SetFont(fontString, size, flags)
	local definition = {
		size = tonumber(size) or 12,
		flags = flags or "OUTLINE",
	}
	self.ManagedFonts[fontString] = definition
	self:ApplyManagedFont(fontString, definition)
end

function Theme:RefreshFonts()
	for fontString, definition in pairs(self.ManagedFonts) do
		self:ApplyManagedFont(fontString, definition)
	end
end

local function CreateEdge(parent, pointA, pointB, width, height)
	local edge = parent:CreateTexture(nil, "BORDER", nil, 7)
	edge:SetColorTexture(unpack(Theme.Colors.border))
	edge:SetPoint(pointA)
	edge:SetPoint(pointB)
	if width then edge:SetWidth(width) end
	if height then edge:SetHeight(height) end
	return edge
end

function Theme:CreatePanel(frame)
	local background = frame:CreateTexture(nil, "BACKGROUND", nil, -8)
	background:SetAllPoints(frame)
	background:SetColorTexture(unpack(self.Colors.panel))

	frame.SlickBackground = background
	frame.SlickEdges = {
		CreateEdge(frame, "TOPLEFT", "TOPRIGHT", nil, 1),
		CreateEdge(frame, "BOTTOMLEFT", "BOTTOMRIGHT", nil, 1),
		CreateEdge(frame, "TOPLEFT", "BOTTOMLEFT", 1, nil),
		CreateEdge(frame, "TOPRIGHT", "BOTTOMRIGHT", 1, nil),
	}
	C_Timer.After(0, function()
		if not frame or frame:GetParent() ~= UIParent then return end
		local width, height = frame:GetSize()
		if (tonumber(width) or 0) >= 260 and (tonumber(height) or 0) >= 120 then
			Theme:RegisterWindowFontControl(frame)
		end
	end)
end

function Theme:CreateBarBackground(bar)
	local background = bar:CreateTexture(nil, "BACKGROUND", nil, -7)
	background:SetAllPoints(bar)
	background:SetColorTexture(0.015, 0.018, 0.025, 0.92)
	bar.SlickBackground = background
end

function Theme:CreateText(parent, size, layer)
	local text = parent:CreateFontString(nil, layer or "OVERLAY")
	self:SetFont(text, size or 12)
	text:SetTextColor(unpack(self.Colors.text))
	return text
end

function Theme:CreateHeaderIcon(parent, size, x, y)
	local icon = parent:CreateTexture(nil, "ARTWORK")
	icon:SetSize(size or 38, size or 38)
	icon:SetPoint("TOPLEFT", x or 16, y or -10)
	icon:SetTexture(self.Icon)
	icon:SetTexCoord(0.04, 0.96, 0.04, 0.96)
	return icon
end

local FontControlExclusions = {
	SlickUIAccountGoldFrame = true,
	SlickUIMemoryMonitor = true,
	SlickUIFishingLedger = true,
}

local function VisitFontStrings(frame, callback, visited)
	if not frame or visited[frame] then return end
	visited[frame] = true
	for _, region in ipairs({ frame:GetRegions() }) do
		if region and region.IsObjectType and region:IsObjectType("FontString") then callback(region) end
	end
	for _, child in ipairs({ frame:GetChildren() }) do VisitFontStrings(child, callback, visited) end
end

function Theme:GetWindowFontSize(window)
	local name = window and window:GetName()
	local sizes = SlickUI.DB and SlickUI.DB.windowFontSizes
	return math.max(8, math.min(18, tonumber(name and sizes and sizes[name]) or 10))
end

function Theme:ApplyWindowFontSize(window)
	if not window then return end
	local factor = self:GetWindowFontSize(window) / 10
	VisitFontStrings(window, function(fontString)
		local definition = self.ManagedFonts[fontString]
		if definition then
			local size = math.max(6, math.floor(definition.size * factor * self:GetFontScale() + 0.5))
			fontString:SetFont(self.Font, size, definition.flags)
		else
			local baseline = self.WindowFontBaselines[fontString]
			if not baseline then
				local path, size, flags = fontString:GetFont()
				if path and size then
					baseline = { path = path, size = size, flags = flags }
					self.WindowFontBaselines[fontString] = baseline
				end
			end
			if baseline then
				fontString:SetFont(baseline.path, math.max(6, math.floor(baseline.size * factor + 0.5)), baseline.flags)
			end
		end
	end, {})
end

function Theme:RegisterWindowFontControl(window)
	if not window or self.FontControlledWindows[window] then return end
	local name = window:GetName()
	if not name or FontControlExclusions[name] then return end
	self.FontControlledWindows[window] = true
	window:HookScript("OnMouseUp", function(owner, button)
		if button ~= "RightButton" then return end
		if not MenuUtil or type(MenuUtil.CreateContextMenu) ~= "function" then
			SlickUI:Print("WoW's context-menu system is unavailable.")
			return
		end
		MenuUtil.CreateContextMenu(owner, function(_, root)
			root:SetTag("SLICKUI_WINDOW_FONT_" .. name)
			root:CreateTitle("Window Font Size")
			for size = 8, 18 do
				local selectedSize = size
				root:CreateRadio(selectedSize .. " pt",
					function(value) return Theme:GetWindowFontSize(owner) == value end,
					function(value)
						SlickUI.DB.windowFontSizes[name] = value
						Theme:ApplyWindowFontSize(owner)
					end,
					selectedSize)
			end
		end)
	end)
	window:HookScript("OnShow", function(owner)
		C_Timer.After(0, function() Theme:ApplyWindowFontSize(owner) end)
	end)
	self:ApplyWindowFontSize(window)
end

function Theme:DiscoverFontControlledWindows()
	local frame = EnumerateFrames()
	while frame do
		local name = frame.GetName and frame:GetName()
		if name and name:match("^SlickUI") and frame:GetParent() == UIParent and not FontControlExclusions[name] then
			local width, height = frame:GetSize()
			if (tonumber(width) or 0) >= 260 and (tonumber(height) or 0) >= 120 then
				self:RegisterWindowFontControl(frame)
			end
		end
		frame = EnumerateFrames(frame)
	end
end

local fontWindowDiscovery = CreateFrame("Frame")
fontWindowDiscovery:RegisterEvent("PLAYER_LOGIN")
fontWindowDiscovery:RegisterEvent("ADDON_LOADED")
fontWindowDiscovery:SetScript("OnEvent", function()
	C_Timer.After(0, function() Theme:DiscoverFontControlledWindows() end)
end)
