local addonName, ns = ...

local SlickUI = {}
ns.SlickUI = SlickUI
_G.SlickUI = SlickUI

SlickUI.Name = addonName
SlickUI.Version = C_AddOns.GetAddOnMetadata(addonName, "Version") or "0.1.0"
SlickUI.Modules = {}
SlickUI.ModuleOrder = {}
SlickUI.UnitFrames = {}
SlickUI.PendingDiagnostics = {}

local MAX_DIAGNOSTIC_ENTRIES = 250

function SlickUI:DiagnosticLog(source, action, details)
	local entry = {
		time = date("%Y-%m-%d %H:%M:%S"),
		uptime = GetTime and GetTime() or 0,
		source = tostring(source or "Core"),
		action = tostring(action or "unknown"),
		details = details and tostring(details):sub(1, 1000) or "",
	}
	local journal = self.DB and self.DB.diagnostics and self.DB.diagnostics.entries
	if type(journal) ~= "table" then
		journal = self.PendingDiagnostics
	end
	table.insert(journal, entry)
	while #journal > MAX_DIAGNOSTIC_ENTRIES do
		table.remove(journal, 1)
	end
end

function SlickUI:BuildDiagnosticExport()
	local journal = self.DB and self.DB.diagnostics and self.DB.diagnostics.entries or self.PendingDiagnostics
	local lines = {
		"SlickUI Diagnostic Journal",
		"Version: " .. tostring(self.Version),
		"Generated: " .. date("%Y-%m-%d %H:%M:%S"),
		"Entries: " .. tostring(type(journal) == "table" and #journal or 0),
		"",
	}
	for index, entry in ipairs(journal or {}) do
		lines[#lines + 1] = string.format(
			"%03d | %s | %.3f | %s | %s%s",
			index,
			tostring(entry.time or "unknown"),
			tonumber(entry.uptime) or 0,
			tostring(entry.source or "unknown"),
			tostring(entry.action or "unknown"),
			entry.details and entry.details ~= "" and (" | " .. tostring(entry.details)) or ""
		)
	end
	return table.concat(lines, "\n")
end

local function CreateDisplayDefaults(isPlayer)
	if isPlayer then
		return {
			topLeft = "level", topCenter = "name", topRight = "healthPercent",
			middleLeft = "healthCurrent", middleCenter = "none", middleRight = "healthMissing",
			bottomLeft = "status", bottomCenter = "powerValues", bottomRight = "powerPercent",
		}
	end
	return {
		topLeft = "name", topCenter = "none", topRight = "healthPercent",
		middleLeft = "none", middleCenter = "none", middleRight = "none",
		bottomLeft = "level", bottomCenter = "none", bottomRight = "status",
	}
end

local function CreateTargetDisplayDefaults()
	local defaults = CreateDisplayDefaults(false)
	defaults.middleCenter = "classification"
	return defaults
end

local function CreateDisplayFontDefaults(isPlayer)
	return {
		topLeft = 9, topCenter = isPlayer and 10 or 9, topRight = 9,
		middleLeft = 9, middleCenter = 9, middleRight = 9,
		bottomLeft = 9, bottomCenter = 9, bottomRight = 9,
	}
end

local function CreateAuraDefaults(unit)
	local compact = unit == "pet" or unit == "targettarget"
	return {
		buffs = {
			enabled = unit ~= "targettarget" and unit ~= "boss",
			knownSpellsOnly = false,
			includeConsumables = true,
			position = "ABOVE",
			iconSize = compact and 18 or 24,
			spacing = 3,
			maxIcons = compact and 5 or 8,
		},
		debuffs = {
			enabled = unit ~= "targettarget" and unit ~= "boss",
			position = unit == "player" and "ABOVE" or "BELOW",
			iconSize = compact and 18 or 24,
			spacing = 3,
			maxIcons = compact and 5 or 8,
		},
	}
end

SlickUI.Defaults = {
	diagnostics = {
		entries = {},
	},
	locked = true,
	editMode = {
		snapToGrid = true,
		snapToFrames = true,
		gridSize = 20,
		hiddenMovers = {},
	},
	externalFrames = {
		quickMoveEnabled = false,
		positions = {},
		minimapProxy = {},
	},
	characterEquipment = {
		showItemLevels = true,
		showEnchants = true,
		highestByCharacter = {},
	},
	bags = {
		enabled = false,
		bankEnabled = true,
		bagRailCollapsed = false,
		bankRailCollapsed = false,
		bankMode = "single",
		bankPosition = {
			point = "CENTER",
			relativePoint = "CENTER",
			x = 100,
			y = 10,
		},
		mode = "unified",
		buttonSize = 38,
		spacing = 4,
		columns = 12,
		scale = 1,
		position = {
			point = "CENTER",
			relativePoint = "CENTER",
			x = 180,
			y = 20,
		},
	},
	classBuffs = {
		enabled = true,
		trackSelfOnly = true,
		tracked = {},
	},
	groupControls = {
		enabled = true,
		shown = true,
		pullSeconds = 10,
	},
	flightTimer = {
		enabled = true,
		showMapTimes = true,
		routes = {},
	},
	reputationTracker = {
		enabled = true,
	},
	showCastbars = true,
	scale = 1,
	optionsPage = "general",
	errorCatcher = {
		enabled = true,
		sound = true,
		unread = 0,
		history = {},
	},
	alerts = {
		interrupt = {
			enabled = true,
			sound = true,
			announce = false,
		},
		groupDeath = {
			sound = true,
		},
		groundHazard = {
			enabled = true,
			sound = true,
		},
		cleanse = {
			enabled = true,
			sound = true,
			onlyWhenReady = true,
		},
		cooldowns = {
			enabled = true,
			sound = true,
			duration = 3.5,
			spellsBySpec = {},
			specMigrationComplete = false,
		},
	},
	queueStatus = {
		enabled = true,
		position = { x = 0, y = -170 },
		proposalID = nil,
		proposalExpiresAt = nil,
	},
	systemFonts = {
		enabled = true,
		general = true,
		chat = true,
		tooltipsAndQuests = true,
		numbersAndCombat = true,
		sizeOffset = 0,
		slickUIScale = 1,
	},
	chatTools = {
		enabled = true,
		showCopyButton = true,
	},
	petsAndMounts = {
		filters = { ground = true, flying = true, aquatic = true, rideAlong = true },
		mountPool = {},
	},
	questTracker = {
		enabled = true,
		blizzardCurrentZoneTracking = false,
		blizzardWatchBackupActive = false,
		blizzardWatchBackup = {},
		blizzardManagedWatches = {},
		width = 330,
		height = 560,
		scale = 1,
		backgroundAlpha = 0.92,
		showBorder = true,
		showHeader = true,
		groupByZone = false,
		currentZoneOnly = false,
		collapsed = false,
		textSizePreset = "normal",
		headerFontSize = 15,
		questFontSize = 12,
		objectiveFontSize = 10,
		scrollStep = 45,
	},
	goldLedger = {
		enabled = true,
		showTracker = true,
		viewMode = "transactions",
		characterScope = "current",
		maxEntries = 1000,
		entries = {},
		characterBalances = {},
		warbandBalance = 0,
		balanceFontSize = 10,
		balanceWindowPosition = { x = 0, y = 80 },
		trackerPosition = { x = 0, y = 180 },
	},
	fishing = {
		enabled = true,
		showIndicator = true,
		playCatchSound = true,
		bobberToyID = 0,
		maxEntries = 5000,
		entries = {},
		lifetimeSeconds = 0,
		characterSeconds = {},
	},
	crafting = {
		shoppingList = {},
		vendorItems = {},
		autoBuyVendor = false,
	},
	vendorAutomation = {
		sellGreys = false,
		autoRepair = false,
		repairFunds = "personal",
	},
	xpTracker = {
		enabled = true,
		windowPosition = { x = 0, y = 180 },
	},
	actionBars = {
		enabled = true,
		visibilityInitialized = false,
		hideHotkeys = false,
		hideMacroNames = true,
		hideCounts = false,
		selectedBar = "main",
		bars = {
			main = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			bottomLeft = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			bottomRight = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			right = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 1, scale = 1, opacity = 1 },
			left = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 1, scale = 1, opacity = 1 },
			bar5 = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			bar6 = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			bar7 = { shown = true, buttonSize = 38, spacing = 4, numberOfSlots = 12, buttonsPerRow = 12, scale = 1, opacity = 1 },
			pet = { shown = true, buttonSize = 32, spacing = 4, numberOfSlots = 10, buttonsPerRow = 10, scale = 1, opacity = 1 },
		},
	},
	informationBars = {
		adjustScreen = true,
		snap = 20,
		clock = {
			useLocalTime = false,
			use24Hour = true,
			presets = { 5, 10, 30 },
			alarmMessage = "Break Time Over",
			broadcastToGroup = true,
		},
		bars = {
			top1 = { enabled = true, locked = true, height = 26, opacity = 0.88, fontSize = 11 },
			top2 = { enabled = false, locked = true, height = 24, opacity = 0.88, fontSize = 11 },
			bottom1 = { enabled = false, locked = true, height = 26, opacity = 0.88, fontSize = 11 },
			bottom2 = { enabled = false, locked = true, height = 24, opacity = 0.88, fontSize = 11 },
		},
		blocks = {
			location = { enabled = true, bar = "top1", x = 20 },
			xp = { enabled = true, bar = "top1", x = 200 },
			gold = { enabled = true, bar = "top1", x = 400 },
			fishing = { enabled = false, bar = "top1", x = 500 },
			bags = { enabled = true, bar = "top1", x = 540 },
			durability = { enabled = true, bar = "top1", x = 660 },
			performance = { enabled = true, bar = "top1", x = 820 },
			clock = { enabled = true, bar = "top1", x = 1020 },
			memory = { enabled = false, bar = "top1", x = 1180 },
			speed = { enabled = false, bar = "top1", x = 1320 },
			windowMove = { enabled = false, bar = "top1", x = 1460 },
			groupItemLevel = { enabled = false, bar = "top1", x = 1600 },
			specialization = { enabled = false, bar = "top1", x = 1720 },
			difficulty = { enabled = false, bar = "top1", x = 1840 },
			rangeCheck = { enabled = false, bar = "top1", x = 1980 },
			mythicKeys = { enabled = false, bar = "top1", x = 2120 },
			groupControls = { enabled = false, bar = "top1", x = 2260 },
			gearHighs = { enabled = false, bar = "top1", x = 2400 },
			interruptCooldowns = { enabled = false, bar = "top1", x = 2540 },
			guild = { enabled = false, bar = "top1", x = 2680 },
			friends = { enabled = false, bar = "top1", x = 2820 },
		},
	},
	interruptCooldowns = {
		enabled = true,
		showOnFrames = true,
		windowShown = false,
		backgroundAlpha = 0.65,
		windowWidth = 280,
		windowHeight = 230,
	},
	mythicKeys = {
		cache = {},
		scopeFilter = "all",
		minimumLevel = 0,
		search = "",
		maxAgeDays = 21,
	},
	rangeDisplay = {
		shown = false,
		fontSize = 24,
		x = 0,
		y = 150,
	},
	unitFrames = {
		classColors = true,
		personalSpacing = 40,
		rowSpacing = 18,
		frameDisplays = {
			player = CreateDisplayDefaults(true),
			target = CreateTargetDisplayDefaults(),
			focus = CreateDisplayDefaults(false),
			pet = CreateDisplayDefaults(false),
			targettarget = CreateDisplayDefaults(false),
			boss = CreateDisplayDefaults(false),
			group = CreateDisplayDefaults(false),
		},
		frameDisplayFontSizes = {
			player = CreateDisplayFontDefaults(true),
			target = CreateDisplayFontDefaults(false),
			focus = CreateDisplayFontDefaults(false),
			pet = CreateDisplayFontDefaults(false),
			targettarget = CreateDisplayFontDefaults(false),
			boss = CreateDisplayFontDefaults(false),
			group = CreateDisplayFontDefaults(false),
		},
		auras = {
			player = CreateAuraDefaults("player"),
			target = CreateAuraDefaults("target"),
			focus = CreateAuraDefaults("focus"),
			pet = CreateAuraDefaults("pet"),
			targettarget = CreateAuraDefaults("targettarget"),
			boss = CreateAuraDefaults("boss"),
		},
		frames = {
			player = { enabled = true, width = 240, height = 48, scale = 1, powerHeight = 9, castbarMode = "ATTACHED", castbarWidth = 240, castbarHeight = 18 },
			target = { enabled = true, width = 240, height = 48, scale = 1, powerHeight = 9, castbarMode = "ATTACHED", castbarWidth = 240, castbarHeight = 18 },
			focus = { enabled = true, width = 190, height = 36, scale = 1, powerHeight = 6, castbarMode = "ATTACHED", castbarWidth = 190, castbarHeight = 18 },
			pet = { enabled = true, width = 110, height = 28, scale = 1, powerHeight = 4 },
			targettarget = { enabled = true, width = 110, height = 28, scale = 1, powerHeight = 4 },
			boss = { enabled = true, width = 190, height = 36, scale = 1, powerHeight = 6, spacing = 8 },
		},
		group = {
			partyEnabled = true,
			raidEnabled = true,
			hideBlizzardFrames = true,
			mainTankEnabled = true,
			mainAssistEnabled = true,
			showIncomingHealing = true,
			showAbsorbShields = true,
			showHealAbsorbs = true,
			capOverhealing = true,
			showDispellableDebuffs = true,
			showPersonalHealingEffects = true,
			showCastbar = true,
			adaptive = true,
			vertical = false,
			showSolo = false,
			anchor = "CENTER",
			classColors = true,
			width = 120,
			height = 30,
			powerHeight = 4,
			spacing = 4,
			membersPerRow = 8,
			minimumScale = 0.58,
			previewCount = 5,
		},
	},
	nameplates = {
		enabled = true,
		showEnemies = true,
		showFriendlyPlayers = false,
		showFriendlyNPCs = false,
		showFriendlyPets = false,
		classColorPlayerNames = false,
		width = 150,
		height = 16,
		scale = 1,
		fontSize = 11,
		showCastbar = true,
		castbarHeight = 12,
		showTargetHighlight = true,
		enlargeTarget = true,
		targetScale = 1.2,
		showThreat = true,
		showClassification = true,
		showQuest = true,
		stackNameplates = true,
		display = {
			left = "classification",
			center = "none",
			right = "healthPercent",
		},
		horizontalSpacing = 0.8,
		verticalSpacing = 1.1,
	},
	minimap = {
		hide = false,
		minimapPos = 220,
	},
	minimapButtonBar = {
		enabled = true,
		collapsed = false,
		orientation = "HORIZONTAL",
		rows = 1,
		length = 8,
		buttonSize = 31,
		spacing = 2,
		locked = true,
		x = 500,
		y = 250,
	},
	positions = {
		player = { x = -170, y = -205 },
		castbarPlayer = { x = -170, y = -270 },
		interruptCooldowns = { x = 0, y = 120 },
		target = { x = 170, y = -205 },
		castbarTarget = { x = 170, y = -270 },
		focus = { x = 170, y = -275 },
		castbarFocus = { x = 170, y = -325 },
		pet = { x = -235, y = -258 },
		targettarget = { x = 235, y = -258 },
		boss = { x = 500, y = 120 },
		group = { x = 0, y = -225 },
		mainTank = { x = -520, y = -225 },
		mainAssist = { x = 520, y = -225 },
		actionBarMain = { x = 0, y = -310 },
		actionBarBottomLeft = { x = 0, y = -354 },
		actionBarBottomRight = { x = 0, y = -398 },
		actionBarRight = { x = 760, y = -30 },
		actionBarLeft = { x = 716, y = -30 },
		actionBar5 = { x = 0, y = -442 },
		actionBar6 = { x = 0, y = -486 },
		actionBar7 = { x = 0, y = -530 },
		actionBarPet = { x = 0, y = -266 },
		questTracker = { x = 650, y = 40 },
		groupControls = { x = 0, y = -175 },
		flightTimer = { x = 0, y = 240 },
	},
}

local function CopyDefaults(source, destination)
	for key, value in pairs(source) do
		if type(value) == "table" then
			if type(destination[key]) ~= "table" then
				destination[key] = {}
			end

			CopyDefaults(value, destination[key])
		elseif destination[key] == nil then
			destination[key] = value
		end
	end
end

function SlickUI:Print(message)
	DEFAULT_CHAT_FRAME:AddMessage("|cffffc44dSlickUI:|r " .. tostring(message))
end

function SlickUI:IsAvailableOutOfCombat(actionName)
	if InCombatLockdown() then
		self:Print((actionName or "That action") .. " is unavailable during combat.")
		return false
	end

	return true
end

function SlickUI:InitializeDatabase()
	if type(SlickUIDB) ~= "table" then
		SlickUIDB = {}
	end

	local unitFrameDB = type(SlickUIDB.unitFrames) == "table" and SlickUIDB.unitFrames or nil
	local migratePlayerDisplay = unitFrameDB and type(unitFrameDB.frameDisplays) ~= "table"
	local legacyDisplay = migratePlayerDisplay and unitFrameDB.playerDisplay or nil
	local legacyFontSizes = migratePlayerDisplay and unitFrameDB.playerDisplayFontSizes or nil
	local legacyAuraFilters = {}
	if unitFrameDB and type(unitFrameDB.auras) == "table" then
		for unit, auraSettings in pairs(unitFrameDB.auras) do
			local buffs = type(auraSettings) == "table" and auraSettings.buffs or nil
			if type(buffs) == "table" and buffs.knownSpellsOnly == nil and buffs.onlyMine ~= nil then
				legacyAuraFilters[unit] = buffs.onlyMine == true
			end
		end
	end

	CopyDefaults(self.Defaults, SlickUIDB)
	if #self.PendingDiagnostics > 0 then
		for _, entry in ipairs(self.PendingDiagnostics) do
			table.insert(SlickUIDB.diagnostics.entries, entry)
		end
		while #SlickUIDB.diagnostics.entries > MAX_DIAGNOSTIC_ENTRIES do
			table.remove(SlickUIDB.diagnostics.entries, 1)
		end
		wipe(self.PendingDiagnostics)
	end
	if SlickUIDB.targetClassificationCentered ~= true then
		SlickUIDB.unitFrames.frameDisplays.target.middleCenter = "classification"
		SlickUIDB.targetClassificationCentered = true
	end
	for unit, enabled in pairs(legacyAuraFilters) do
		if SlickUIDB.unitFrames.auras[unit] then
			SlickUIDB.unitFrames.auras[unit].buffs.knownSpellsOnly = enabled
		end
	end
	if migratePlayerDisplay then
		if type(legacyDisplay) == "table" then
			for slotKey, value in pairs(legacyDisplay) do
				SlickUIDB.unitFrames.frameDisplays.player[slotKey] = value
			end
		end
		if type(legacyFontSizes) == "table" then
			for slotKey, value in pairs(legacyFontSizes) do
				SlickUIDB.unitFrames.frameDisplayFontSizes.player[slotKey] = value
			end
		end
	end
	if SlickUIDB.positions.group and SlickUIDB.positions.group.x == 0 and SlickUIDB.positions.group.y == -315 then
		SlickUIDB.positions.group.y = -225
	end
	self.DB = SlickUIDB
end

function SlickUI:RegisterModule(name, module)
	if self.Modules[name] == nil then
		table.insert(self.ModuleOrder, name)
	end
	self.Modules[name] = module
end

SLASH_SLICKUIRELOAD1 = "/rl"
SlashCmdList.SLICKUIRELOAD = function()
	ReloadUI()
end

local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("ADDON_LOADED")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
EventFrame:SetScript("OnEvent", function(_, event, arg1, arg2)
	if event ~= "ADDON_LOADED" then
		local detail = event == "PLAYER_ENTERING_WORLD" and string.format("initial=%s reload=%s", tostring(arg1), tostring(arg2)) or ""
		SlickUI:DiagnosticLog("Core", event, detail)
		return
	end
	if arg1 ~= addonName then
		return
	end

	SlickUI:InitializeDatabase()
	SlickUI:DiagnosticLog("Core", "database-ready", "modules=" .. tostring(#SlickUI.ModuleOrder))
	-- Initialize in TOC registration order. Several secure-frame modules depend
	-- on their foundations being ready first; hash-table iteration can change
	-- when an unrelated module is added and produce protected-action taint.
	for _, moduleName in ipairs(SlickUI.ModuleOrder) do
		local module = SlickUI.Modules[moduleName]
		if type(module.Initialize) == "function" then
			SlickUI.InitializingModule = moduleName
			local started = GetTime()
			SlickUI:DiagnosticLog(moduleName, "initialize-start")
			module:Initialize()
			SlickUI:DiagnosticLog(moduleName, "initialize-finish", string.format("%.3f sec", GetTime() - started))
			SlickUI.InitializingModule = nil
		end
	end
	SlickUI:Print("v" .. SlickUI.Version .. " loaded. Type /slick for options.")
end)
