local useUiScale = tonumber(GetCVar("useUiScale"))
local uiScale = GetCVar("uiScale")
if useUiScale ~= 1 then uiScale = 1 end
frameWidth = UIParent:GetWidth() / uiScale
xMiddle = frameWidth / 2
frameHeight = UIParent:GetHeight() / uiScale
yMiddle = frameHeight / 2
frameScale = 0.75999999046326
local _G = getfenv(0)
local timeElapsed = 0
local updateTime = 1/60
CTextures = {}
CRotateTextures = {}

-- Default minimap dimensions (before any scaling)
local defaultMinimapWidth = 140
local defaultMinimapHeight = 140

-- Track hidden minimap buttons so we can restore them
local hiddenMinimapButtons = {}


-- Minimap cluster Show hook state (for minimap hide/show toggle)
local minimapClusterHooked = false
local minimapClusterOrigShow = nil
local minimapOrigShow = nil

-- Minimap button Show hook state
local minimapBtnHooked = false

-- Saved original Show functions for minimap buttons
local hiddenBtnOrigShows = {}

-- Saved original anchor points for minimap buttons (so positions are preserved across hide/show)
-- Format: hiddenBtnOrigPoints[frame] = { {point, relativeTo, relativePoint, xOfs, yOfs}, ... }
local hiddenBtnOrigPoints = {}

-- Addon version for SavedVariables migration
local ADDON_VERSION = 7

local function print(msg)
	if msg and msg ~= "" then
		DEFAULT_CHAT_FRAME:AddMessage(msg, 1, 1, 0)
	end
end

function deepCopyHash(t)
	if t == nil then return nil end
	local nt = {}
	for k, v in pairs(t) do
		if type(v) == "table" then
			nt[k] = deepCopyHash(v)
		else
			nt[k] = v
		end
	end
	return nt
end

function destroyTable(t)
	setmetatable(t, nil)
	for k,v in pairs(t) do
		t[k] = nil
	end
end

-- ============================================
-- SavedVariables Migration
-- ============================================
-- Detects and fixes stale/invalid data from older addon versions.
-- Called during PLAYER_ENTERING_WORLD before any settings are applied.
local function MigrateSavedVariables()
	if not SexyMap2DB then
		SexyMap2DB = {}
	end

	-- Version-based migration: if the saved version is older than current,
	-- apply fixes. This runs ONCE per version bump.
	local savedVersion = SexyMap2DB.version or 0

	-- Always check for invalid data regardless of version,
	-- since the user might have stale data from before version tracking existed.

	-- Fix empty borders array (from v1.4 when some presets had empty borders)
	if SexyMap2DB.borders == nil or (type(SexyMap2DB.borders) == "table" and table.getn(SexyMap2DB.borders) == 0 and next(SexyMap2DB.borders) == nil) then
		SexyMap2DB.borders = deepCopyHash(borderPresets["Diablo by Zork"].borders)
		print("[SexyMap] Migrated: empty borders reset to default preset.")
	end

	-- Fix backdrop with invalid/missing data
	if SexyMap2DB.backdrop == nil then
		SexyMap2DB.backdrop = deepCopyHash(borderPresets["Diablo by Zork"].backdrop)
		print("[SexyMap] Migrated: missing backdrop reset to default preset.")
	else
		-- Fix empty borderColor/textureColor that cause green rendering
		if SexyMap2DB.backdrop.borderColor == nil or (type(SexyMap2DB.backdrop.borderColor) == "table" and next(SexyMap2DB.backdrop.borderColor) == nil) then
			SexyMap2DB.backdrop.borderColor = { r = 1, g = 1, b = 1, a = 1 }
		end
		if SexyMap2DB.backdrop.textureColor == nil or (type(SexyMap2DB.backdrop.textureColor) == "table" and next(SexyMap2DB.backdrop.textureColor) == nil) then
			SexyMap2DB.backdrop.textureColor = { r = 0, g = 0, b = 0, a = 1 }
		end
		-- Fix settings with .blp in edgeFile/bgFile (WoW 1.12 auto-appends .blp)
		if SexyMap2DB.backdrop.settings then
			local s = SexyMap2DB.backdrop.settings
			if s.edgeFile and string.find(s.edgeFile, "%.blp$") then
				s.edgeFile = string.gsub(s.edgeFile, "%.blp$", "")
				print("[SexyMap] Migrated: removed .blp from backdrop edgeFile.")
			end
			if s.bgFile and string.find(s.bgFile, "%.blp$") then
				s.bgFile = string.gsub(s.bgFile, "%.blp$", "")
				print("[SexyMap] Migrated: removed .blp from backdrop bgFile.")
			end
		end
	end

	-- Fix missing/empty shape
	if SexyMap2DB.shape == nil or SexyMap2DB.shape == "" then
		SexyMap2DB.shape = "Round"
	end

	-- Ensure all expected fields exist
	if SexyMap2DB.showZoneText == nil then
		SexyMap2DB.showZoneText = true
	end
	if SexyMap2DB.showMinimapButtons == nil then
		SexyMap2DB.showMinimapButtons = true
	end
	if SexyMap2DB.mapScale == nil then
		SexyMap2DB.mapScale = 1.0
	end
	if SexyMap2DB.showMinimap == nil then
		SexyMap2DB.showMinimap = true
	end
	if not SexyMap2DB.userPresets then
		SexyMap2DB.userPresets = {}
	end

	-- Mark the current version so migration doesn't re-run needlessly
	-- (but the checks above still protect against manually-edited bad data)
	SexyMap2DB.version = ADDON_VERSION
end

function CMap_OnLoad()
	print("[SexyMap] v1.7 loaded for OctoWoW. Type '/sm' or '/sexymap' to open the menu.")
	this:SetScript("OnEvent", CMap_OnEvent)
	this:SetScript("OnUpdate", CMap_OnUpdate)
	this:RegisterEvent("PLAYER_ENTERING_WORLD")
end

function CMap_OnEvent()
	if event == "PLAYER_ENTERING_WORLD" then
		this:UnregisterEvent("PLAYER_ENTERING_WORLD")

		-- Run migration FIRST, before any settings are applied
		MigrateSavedVariables()

		-- Capture the default minimap dimensions before we modify anything
		defaultMinimapWidth = Minimap:GetWidth()
		defaultMinimapHeight = Minimap:GetHeight()

		CreateFrame("Frame", "CMinimapBackdrop", Minimap)
		CMinimapBackdrop:SetFrameStrata("BACKGROUND")
		CMinimapBackdrop:SetFrameLevel(CMinimapBackdrop:GetFrameLevel() - 1)
		CMinimapBackdrop:SetPoint("CENTER", Minimap, "CENTER")
		CMinimapBackdrop:SetWidth(Minimap:GetWidth())
		CMinimapBackdrop:SetHeight(Minimap:GetHeight())
		-- Set an explicit default backdrop so the frame never renders as green
		-- (a frame with no backdrop set can render as solid green in WoW 1.12)
		CMinimapBackdrop:SetBackdrop({
			bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
			edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
			tile = false,
			edgeSize = 16,
			insets = { left = 4, right = 4, top = 4, bottom = 4 }
		})
		CMinimapBackdrop:SetBackdropColor(0, 0, 0, 1)
		CMinimapBackdrop:SetBackdropBorderColor(1, 1, 1, 1)

		CHideAnnoyingStuff()
		CUpdateZoneText()
		CUpdateMinimapButtonsVisibility()
		CUpdateMinimapVisibility()
		CApplyMapScale()
		CLoadArtwork()
		SlashCmdList["CMAP"] = CMap_SlashCmdHandler
		SLASH_CMAP1 = "/cm"
		SLASH_CMAP2 = "/sm"
		SLASH_CMAP3 = "/sexymap"
	end
end


function CMap_OnUpdate()
	timeElapsed = timeElapsed + arg1
	if timeElapsed >= updateTime then
		for k,v in pairs(CRotateTextures) do
			CRotateTexture(_G[k], v * timeElapsed)
		end
		timeElapsed = 0
	end
end


function CMap_SlashCmdHandler(msg)
	if not CMinimapOptionFrame.menuState then
		CInitiateMenu()
	else
		CHideMenu()
	end
end


function CHideAnnoyingStuff()
	-- Hide the default minimap border art (we draw our own borders)
	MinimapBorder:Hide()
	MinimapBorderTop:Hide()
	-- Hide the X button (minimap toggle) — we now control this from the GUI
	MinimapToggleButton:Hide()
	-- Hide zoom buttons ( SexyMap handles its own zoom or user doesn't need them )
	MinimapZoomIn:Hide()
	MinimapZoomOut:Hide()
	-- Style the zone text font
	MinimapZoneText:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
end

-- ============================================
-- Minimap visibility (replaces the X button)
-- ============================================
-- In vanilla WoW 1.12, MinimapToggleButton calls ToggleMinimap() which
-- hides/shows just the Minimap frame. We replace this with a full
-- MinimapCluster hide/show controlled from the GUI, and hook Show/Hide
-- to prevent anything from re-showing the minimap when it should be hidden.

local function MinimapClusterHookedShow(self)
	if not SexyMap2DB.showMinimap then
		return  -- Block the Show, minimap stays hidden
	end
	if minimapClusterOrigShow then
		minimapClusterOrigShow(self)
	end
end

local function MinimapHookedShow(self)
	if not SexyMap2DB.showMinimap then
		return  -- Block the Show, minimap stays hidden
	end
	if minimapOrigShow then
		minimapOrigShow(self)
	end
end

function CUpdateMinimapVisibility()
	if SexyMap2DB.showMinimap then
		-- Show the minimap cluster - unhook first
		if minimapClusterHooked then
			if MinimapCluster then
				MinimapCluster.Show = minimapClusterOrigShow
			end
			if Minimap then
				Minimap.Show = minimapOrigShow
			end
			minimapClusterHooked = false
			minimapClusterOrigShow = nil
			minimapOrigShow = nil
		end
		-- Show everything
		if MinimapCluster then MinimapCluster:Show() end
		if Minimap then Minimap:Show() end
		-- Re-apply our hidden stuff (borders, zoom buttons, X button stay hidden)
		CHideAnnoyingStuff()
		-- Re-apply button visibility settings
		CUpdateMinimapButtonsVisibility()
	else
		-- Hide the entire minimap cluster and hook Show to prevent re-appearance
		if MinimapCluster then
			if not minimapClusterHooked then
				minimapClusterOrigShow = MinimapCluster.Show
				MinimapCluster.Show = MinimapClusterHookedShow
				minimapOrigShow = Minimap.Show
				Minimap.Show = MinimapHookedShow
				minimapClusterHooked = true
			end
			MinimapCluster:Hide()
		end
	end
end

-- ============================================
-- Minimap buttons visibility
-- ============================================

local function MinimapBtnHookedShow(self)
	if not SexyMap2DB.showMinimapButtons then
		return  -- Block Show while buttons should be hidden
	end
	-- When buttons are allowed, call the original Show
	local orig = hiddenBtnOrigShows[self]
	if orig then
		orig(self)
	end
end

-- Save a frame's current anchor points so we can restore them later
local function SaveButtonPoints(frame)
	local points = {}
	local numPoints = frame:GetNumPoints()
	if numPoints and numPoints > 0 then
		for i = 1, numPoints do
			local point, relativeTo, relativePoint, xOfs, yOfs = frame:GetPoint(i)
			if point then
				table.insert(points, {
					point = point,
					relativeTo = relativeTo,
					relativePoint = relativePoint,
					xOfs = xOfs,
					yOfs = yOfs,
				})
			end
		end
	end
	-- Only store if we actually got points; don't overwrite existing saved points with empty
	if table.getn(points) > 0 then
		hiddenBtnOrigPoints[frame] = points
	end
end

-- Restore a frame's saved anchor points
local function RestoreButtonPoints(frame)
	local points = hiddenBtnOrigPoints[frame]
	if points and table.getn(points) > 0 then
		frame:ClearAllPoints()
		for _, pt in ipairs(points) do
			frame:SetPoint(pt.point, pt.relativeTo, pt.relativePoint, pt.xOfs, pt.yOfs)
		end
	end
	-- Clean up saved points for this frame
	hiddenBtnOrigPoints[frame] = nil
end

function CUpdateMinimapButtonsVisibility()
	if SexyMap2DB.showMinimapButtons then
		-- Show only the buttons we previously hid (preserves original visibility)
		for _, btn in ipairs(hiddenMinimapButtons) do
			if btn then
				-- Restore original Show
				local orig = hiddenBtnOrigShows[btn]
				if orig then
					btn.Show = orig
					hiddenBtnOrigShows[btn] = nil
				end
				-- Restore saved anchor points (preserves button positions across hide/show)
				RestoreButtonPoints(btn)
				btn:Show()
			end
		end
		hiddenMinimapButtons = {}
	else
		-- Find and hide only VISIBLE minimap buttons, hook their Show
		hiddenMinimapButtons = {}
		local children = { Minimap:GetChildren() }
		for idx, frame in ipairs(children) do
			local objType = frame:GetObjectType()
			if objType == "Button" or objType == "CheckButton" then
				local name = frame:GetName() or ""
				-- Skip our own frames and WoW native controls we already hide
				if name ~= "MinimapZoomIn" and name ~= "MinimapZoomOut"
				   and name ~= "MinimapToggleButton" and name ~= "GameTimeFrame"
				   and name ~= "CMinimapBackdrop" then
					-- Only hide buttons that are currently visible
					-- (buttons already hidden by other addons should stay hidden)
					if frame:IsVisible() then
						-- Save anchor points before hiding so we can restore them later
						SaveButtonPoints(frame)
						-- Hook Show to prevent re-appearance
						if not hiddenBtnOrigShows[frame] then
							hiddenBtnOrigShows[frame] = frame.Show
							frame.Show = MinimapBtnHookedShow
						end
						frame:Hide()
						table.insert(hiddenMinimapButtons, frame)
					end
				end
			end
		end
		-- Also hide known WoW minimap frames by name (only if visible)
		local knownFrames = {
			"MiniMapTracking",
			"MiniMapTrackingButton",
			"MiniMapTrackingFrame",
			"MiniMapMailFrame",
			"MiniMapMailBorder",
			"MiniMapBattlefieldFrame",
			"MiniMapMeetingStoneFrame",
			"MiniMapWorldMapButton",
			"MinimapVoiceChatFrame",
			"MiniMapLFGFrame",
		}
		for _, frameName in ipairs(knownFrames) do
			local frame = _G[frameName]
			if frame and frame:IsVisible() then
				-- Save anchor points before hiding so we can restore them later
				SaveButtonPoints(frame)
				if not hiddenBtnOrigShows[frame] then
					hiddenBtnOrigShows[frame] = frame.Show
					frame.Show = MinimapBtnHookedShow
				end
				frame:Hide()
				table.insert(hiddenMinimapButtons, frame)
			end
		end
	end
end


function CApplyMapScale()
	local scale = SexyMap2DB.mapScale or 1.0
	if scale < 0.3 then scale = 0.3 end
	if scale > 1.0 then scale = 1.0 end
	SexyMap2DB.mapScale = scale

	-- Apply scale to the MinimapCluster which contains everything
	MinimapCluster:SetScale(scale)

	-- Re-anchor to TOPRIGHT - the anchor point is TOPRIGHT so when scaled down,
	-- the right side and top stay put, while the left and bottom move inward
	MinimapCluster:ClearAllPoints()
	MinimapCluster:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -14, 10)

	-- Update backdrop to match current minimap size
	CMinimapBackdrop:SetWidth(Minimap:GetWidth())
	CMinimapBackdrop:SetHeight(Minimap:GetHeight())

	-- Update border textures to match new minimap size
	CUpdateBackdrop()
end

function CUpdateZoneText()
	MinimapCluster:ClearAllPoints()
	-- Map position: always anchored TOPRIGHT so scale keeps right/top fixed
	MinimapCluster:SetPoint("TOPRIGHT", UIParent, "TOPRIGHT", -14, 10)
	MinimapZoneText:ClearAllPoints()
	if SexyMap2DB.showZoneText then
		MinimapZoneText:Show()
		-- Zone name positioned below the minimap
		MinimapZoneText:SetPoint("TOP", Minimap, "BOTTOM", 0, -20)
	else
		-- Only hide the zone text; map and border do NOT move
		MinimapZoneText:Hide()
	end
end

-- ============================================
-- Set Defaults - resets all settings to factory
-- ============================================
function CSetDefaults()
	-- Reset all SavedVariables to defaults
	SexyMap2DB.borders = deepCopyHash(borderPresets["Diablo by Zork"].borders)
	SexyMap2DB.backdrop = deepCopyHash(borderPresets["Diablo by Zork"].backdrop)
	SexyMap2DB.shape = "Round"
	SexyMap2DB.userPresets = {}
	SexyMap2DB.showZoneText = true
	SexyMap2DB.showMinimapButtons = true
	SexyMap2DB.mapScale = 1.0
	SexyMap2DB.showMinimap = true
	SexyMap2DB.version = ADDON_VERSION

	-- Reapply everything
	CHideAnnoyingStuff()
	CUpdateZoneText()
	CUpdateMinimapButtonsVisibility()
	CUpdateMinimapVisibility()
	CApplyMapScale()
	CClearArtwork()
	CLoadArtwork()

	-- Refresh the GUI if it's open
	if CMinimapOptionFrame and CMinimapOptionFrame.menuState then
		CUpdatePresetList()
		CUpdateShapeList()
		CTexture_Update()
		CZoneTextToggle_Update()
		CMinimapButtonsToggle_Update()
		CMinimapToggle_Update()
		CMapScaleSlider_Update()
		CVariable_Update()
	end

	print("[SexyMap] All settings reset to defaults.")
end

function CClearArtwork()
	for k,v in pairs(CTextures) do
		_G[k]:SetTexCoord(0,1,0,1)
		_G[k]:Hide()
	end
	destroyTable(CTextures)
	CTextures = {}
	destroyTable(CRotateTextures)
	CRotateTextures = {}
end


function CLoadArtwork()
	if SexyMap2DB.shape then
		CApplyShape(SexyMap2DB.shape)
	end
	for k,v in ipairs(SexyMap2DB.borders) do
		CCreateBorder(v)
	end
	CUpdateBackdrop()
end


function CUpdateBackdrop()
	if not SexyMap2DB.backdrop or not SexyMap2DB.backdrop.show then
		CMinimapBackdrop:Hide()
	else
		local backdrop = CMinimapBackdrop
		backdrop:Show()
		backdrop:SetWidth(Minimap:GetWidth() * (SexyMap2DB.backdrop.scale or 1))
		backdrop:SetHeight(Minimap:GetHeight() * (SexyMap2DB.backdrop.scale or 1))

		-- Validate the backdrop settings before applying.
		-- If edgeFile or bgFile references are invalid, WoW 1.12 renders the frame
		-- as solid green (missing texture fallback). We check that the paths exist
		-- in the settings table before calling SetBackdrop.
		local settings = SexyMap2DB.backdrop.settings
		if settings then
			-- Ensure edgeFile doesn't have .blp extension (WoW 1.12 auto-appends it)
			if settings.edgeFile and string.find(settings.edgeFile, "%.blp$") then
				settings.edgeFile = string.gsub(settings.edgeFile, "%.blp$", "")
			end
			if settings.bgFile and string.find(settings.bgFile, "%.blp$") then
				settings.bgFile = string.gsub(settings.bgFile, "%.blp$", "")
			end
			backdrop:SetBackdrop(settings)
		end

		-- Set backdrop color with safe defaults for empty/missing color tables
		local c = SexyMap2DB.backdrop.textureColor
		if c and (c.r or c.g or c.b or c.a) then
			backdrop:SetBackdropColor(c.r or 0, c.g or 0, c.b or 0, c.a or 1)
		else
			-- Default: black background, full opacity
			backdrop:SetBackdropColor(0, 0, 0, 1)
		end

		-- Set border color with safe defaults for empty/missing color tables
		c = SexyMap2DB.backdrop.borderColor
		if c and (c.r or c.g or c.b or c.a) then
			backdrop:SetBackdropBorderColor(c.r or 1, c.g or 1, c.b or 1, c.a or 1)
		else
			-- Default: white border, full opacity
			backdrop:SetBackdropBorderColor(1, 1, 1, 1)
		end
	end
end
