local _, addon = ...
local API = addon.API;
local L = addon.L;
local SetDBValue = addon.SetDBValue;
local GetDBValue = addon.GetDBValue;
local Mixin = API.Mixin;

local MapFrame = WorldMapFrame;
local TooltipFrame = GameTooltip;
local InCombatLockdown = InCombatLockdown;
local C_UIWidgetManager = C_UIWidgetManager;

local PIN_TEMPLATE_NAME = "PlumberWorldMapPinTemplate";

local PinController = CreateFrame("Frame");     --Events driven, updates Pin
addon.MapPinController = PinController;
local MapTracker = CreateFrame("Frame");        --Attach to WorldMapFrame to monitor changes to map, scale...
MapTracker:Hide();
local WorldMapDataProvider = CreateFromMixins(MapCanvasDataProviderMixin);
local BlizzardUIUtil = {};


do  --BlizzardUIUtil
	local QuestDetailsFrame = QuestMapFrame and QuestMapFrame.DetailsFrame;

	function BlizzardUIUtil:IsViewingQuestDetails()
		return QuestDetailsFrame and QuestDetailsFrame:IsVisible();
	end

	--Map Filter Menu
	function BlizzardUIUtil:HookIntoMenu()
		if not self.isMenuHooked then
			self.isMenuHooked = true;
		else
			return
		end

		self.isMenuHooked = true;

		if Menu and Menu.ModifyMenu then
			Menu.ModifyMenu("MENU_WORLD_MAP_TRACKING", function(owner, rootDescription, contextData)
				if not (PinController.isEnabled and PinController.pinEnabled) then return end;

				local options = PinController:GetFilterOptionsForCurrentMap();
				if options then
					rootDescription:CreateDivider();
					rootDescription:CreateTitle("Plumber");
					for _, option in ipairs(options) do
						--rootDescription:CreateButton("Button", function() print("Text here!" end);
						local function IsSelected()
							return GetDBValue(option.dbKey);
						end

						local function SetSelected()
							local newState = not GetDBValue(option.dbKey);
							SetDBValue(option.dbKey, newState);
							PinController:EnableMapDataProvider(option.dbKey, newState);
							PinController:RequestUpdate();
						end

						local checkbox = rootDescription:CreateCheckbox(option.name, IsSelected, SetSelected);

						checkbox:AddInitializer(function(button, description, menu)
							local rightTexture = button:AttachTexture();
							rightTexture:SetPoint("RIGHT");
							option.iconSetupFunc(rightTexture);

							local fontString = button.fontString;
							fontString:SetPoint("RIGHT", rightTexture, "LEFT");
							fontString:SetTextColor(1, 1, 1);

							local pad = 20;
							local width = pad + fontString:GetUnboundedStringWidth() + rightTexture:GetWidth();
							local height = 20;

							return width, height;
						end);
					end

					local submenu = rootDescription:CreateButton(L["Pin Size"]);

					local function IsSelected(index)
						return index == PinController:GetPinSizeIndex()
					end

					local function SetSelected(index)
						SetDBValue("WorldMapPin_Size", index, true);
						return (MenuResponse and MenuResponse.Refresh) or 2
					end

					for index, text in ipairs(PinController:GetPinSizeChoices()) do
						submenu:CreateRadio(text, IsSelected, SetSelected, index);
					end
				end
			end);
		end
	end
end


do  --PinController
	PinController.pins = {};
	PinController.mapDataProviders = {};
	PinController.dbKeyXdataprovider = {};

	local tinsert = table.insert;
	local ipairs = ipairs;

	function PinController:AddPin(pin)
		tinsert(self.pins, pin);
	end

	function PinController:AddMapDataProvider(mapID, dataProvider)
		if not self.mapDataProviders[mapID] then
			self.mapDataProviders[mapID] = {};
		end
		tinsert(self.mapDataProviders[mapID], dataProvider);

		if not self.dbKeyXdataprovider[dataProvider.OptionData.dbKey] then
			self.dbKeyXdataprovider[dataProvider.OptionData.dbKey] = dataProvider;
		else
			print(string.format("Plumber: Duplicated MapDataProvider Key %s", dataProvider.OptionData.dbKey));
		end
	end

	function PinController:InitializeSubModules()
		local anyEnabled;

		if self.mapDataProviders then
			local masterState = GetDBValue("WorldMapPin_TWW");
			self.pinEnabled = masterState;
			for mapID, dataProviders in pairs(self.mapDataProviders) do
				for _, dataProvider in ipairs(dataProviders) do
					dataProvider.enabled = masterState and GetDBValue(dataProvider.OptionData.dbKey);
					if dataProvider.enabled then
						anyEnabled = true;
					end
				end
			end
		end

		if self.mapProcessors then
			for processors in pairs(self.mapProcessors) do
				processors.enabled = GetDBValue(processors.dbKey);
				if processors.enabled then
					anyEnabled = true;
				end
			end
		end

		if anyEnabled then
			MapTracker:Attach();
			self.isEnabled = true;
			BlizzardUIUtil:HookIntoMenu();
		else
			MapTracker:Detach();
			self.isEnabled = false;
			self:ListenEvents(false);
		end
	end

	function PinController:EnableMapDataProvider(dbKey, state)
		local dataProvider = self.dbKeyXdataprovider[dbKey];
		if dataProvider then
			dataProvider.enabled = state == true or state == nil;
		end
	end

	function PinController:DoesMapHaveData(mapID)
		if mapID and self.mapDataProviders[mapID] then
			for _, dataProvider in ipairs(self.mapDataProviders[mapID]) do
				if dataProvider.enabled then
					return true
				end
			end
		end
	end

	function PinController:UpdatePinForMap(mapID)
		local pin, pinData;
		local anyPins = false;
		local pinSizeScale = self:GetPinSizeScale();

		for i, dataProvider in ipairs(self.mapDataProviders[mapID]) do
			if dataProvider.enabled then
				pinData = dataProvider:GetPinDataForMap(mapID);
				if pinData then
					for _, data in ipairs(pinData) do
						pin = MapFrame:AcquirePin(PIN_TEMPLATE_NAME, data);
						if pin then
							anyPins = true;
							pin:SetSizeScale(pinSizeScale);
						end
					end
				end
			end
		end

		return anyPins
	end

	function PinController:GetFilterOptionsForCurrentMap()
		local mapID = MapTracker:GetMapID();
		if mapID and self.mapDataProviders[mapID] then
			local options;
			local n = 0;
			for _, dataProvider in ipairs(self.mapDataProviders[mapID]) do
				if not options then
					options = {};
				end
				n = n + 1;
				options[n] = dataProvider.OptionData;
				--option = {name=, iconSetupFunc=, enabled=}
			end
			return options
		end
	end

	function PinController:OnUpdate(elapsed)
		self.t = self.t + elapsed;
		if self.t >= 0.2 then
			self.t = nil;
			self:SetScript("OnUpdate", nil);
			MapTracker:OnMapChanged();
		end
	end

	function PinController:RequestUpdate()
		--Called by sub module when data is recieved
		self.t = 0;
		self:SetScript("OnUpdate", self.OnUpdate);
	end

	function PinController:ListenEvents(state)
		if state and not self.listening then
			self.listening = true;
			self:RegisterEvent("AREA_POIS_UPDATED");
			self:SetScript("OnEvent", self.OnEvent);
		elseif (not state) and self.listening then
			self.listening = false;
			self:UnregisterEvent("AREA_POIS_UPDATED");
			self:SetScript("OnEvent", nil);
		end
	end

	function PinController:OnEvent(event, ...)
		if event == "AREA_POIS_UPDATED" then
			self:RequestUpdate();
		end
	end

	function PinController:UpdatePinScale()

	end


	--Custom Pin Size
	local PinSizeIndexSizeScale = {
		--[pinSizeIndex] = scale,
		[1] = 1.0,
		[2] = 1.25,
		[3] = 1.5,
	};

	function PinController:GetPinSizeChoices()
		local tbl = {};
		for i, scale in ipairs(PinSizeIndexSizeScale) do
			tbl[i] = string.format("%.0f%%", scale * 100);
		end
		return tbl
	end

	function PinController:GetPinSizeIndex()
		return self.pinSizeIndex or 1
	end

	function PinController:GetPinSizeScale()
		return self.pinSizeScale or 1
	end

	function PinController:SetPinSizeIndex(index, useInput)
		if not (index and PinSizeIndexSizeScale[index]) then
			index = 1;
		end
		self.pinSizeIndex = index;
		self.pinSizeScale = PinSizeIndexSizeScale[index];
		if useInput then
			MapTracker:OnMapChanged();
		end
	end
	addon.CallbackRegistry:Register("SettingChanged.WorldMapPin_Size", PinController.SetPinSizeIndex, PinController);


	--Generic Map Triggers
	function PinController:AddMapProcessor(processor)
		if not self.mapProcessors then
			self.mapProcessors = {};
		end
		self.mapProcessors[processor] = true;
	end

	function PinController:TriggerMapProcessor(uiMapID)
		if self.mapProcessors then
			for processor in pairs(self.mapProcessors) do
				processor:OnMapChanged(uiMapID, MapTracker.mapScale);
			end
		end
	end

	function PinController:TriggerModifierStateChanged()
		if self.mapProcessors then
			for processor in pairs(self.mapProcessors) do
				if processor.OnModifiderStateChanged then
					processor:OnModifiderStateChanged();
				end
			end
		end
	end

	function PinController:TriggerCanvasScaleChanged(scale)
		if self.mapProcessors then
			for processor in pairs(self.mapProcessors) do
				if processor.OnCanvasScaleChanged then
					processor:OnCanvasScaleChanged(scale);
				end
			end
		end
	end

	function PinController:RequestLoadSettings()
		if not self.pauseUpdate then
			self.pauseUpdate = true;
			C_Timer.After(0, function()
				self.pauseUpdate = nil;
				self:InitializeSubModules();
			end);
		end
	end
end


do  --WorldMapDataProvider
	function WorldMapDataProvider:GetPinTemplate()
		return PIN_TEMPLATE_NAME
	end

	function WorldMapDataProvider:OnShow()

	end

	function WorldMapDataProvider:OnHide()
		PinController:ListenEvents(false);
	end

	function WorldMapDataProvider:OnEvent(event, ...)
		--This significantly increase RAM usage count
		--So we monitor Events using another frame (PinController)
	end

	function WorldMapDataProvider:RemoveAllIfNeeded()
		if self.anyPins then
			self:RemoveAllData();
		end
	end

	function WorldMapDataProvider:RemoveAllData()
		self.anyPins = false;
		MapFrame:RemoveAllPinsByTemplate(PIN_TEMPLATE_NAME);
	end

	function WorldMapDataProvider:RefreshAllData(fromOnShow)
		self:RemoveAllIfNeeded();
		self:ShowAllPins();
	end

	function WorldMapDataProvider:RefreshAllDataIfPossible()
		self:RemoveAllIfNeeded();
		if not BlizzardUIUtil:IsViewingQuestDetails() then
			self:ShowAllPins();
		end
	end

	function WorldMapDataProvider:ShowAllPins()
		local mapID = MapTracker:GetMapID();
		self.anyPins = PinController:UpdatePinForMap(mapID);
	end

	function WorldMapDataProvider:OnCanvasScaleChanged()
		--Fires multiple times when opening WorldMapFrame
		PinController:UpdatePinScale();
	end
end


do
	local MapScrollContainer = WorldMapFrame.ScrollContainer;

	function MapTracker:Attach()
		if not self.attached then
			self.attached = true;
			self:SetParent(MapFrame);
			self:EnableScripts();
			self:Show();
		end
	end

	function MapTracker:Detach()
		if self.attached then
			self.attached = nil;
			self.mapID = nil;
			self:SetParent(nil);
			self:DisableScripts();
			self:Hide();
			WorldMapDataProvider:RemoveAllIfNeeded();
			WorldMapDataProvider:OnHide();
		end
	end

	function MapTracker:OnUpdate(elapsed)
		self.t1 = self.t1 + elapsed;
		self.t2 = self.t2 + elapsed;

		if self.t1 > 0.016 then
			self.t1 = 0;

			self.newMapID = MapFrame.mapID;
			if self.newMapID ~= self.mapID then
				self.mapID = self.newMapID;
				self:OnMapChanged(self.mapID);
				self.anyChange = true;
			end

			self.newScale = MapScrollContainer.targetScale;
			if self.newScale ~= self.mapScale then
				self.mapScale = self.newScale;
				self:OnCanvasScaleChanged(self.mapScale);
			end

			self.newMaximized = MapFrame.isMaximized;
			if self.newMaximized ~= self.maximized then
				self.maximized = self.newMaximized;
				self.anyChange = true;
			end
		end

		if self.t2 > 0.1 then
			self.t2 = 0;
			self.detailsVisiblity = BlizzardUIUtil:IsViewingQuestDetails();
			if self.detailsVisiblity ~= self.isViewingDetails then
				self.isViewingDetails = self.detailsVisiblity;
				self:OnViewingQuestDetailsChanged();
			end
		end

		if self.anyChange then
			self.anyChange = nil;
			PinController:TriggerMapProcessor(self.mapID);
		end
	end

	function MapTracker:OnShow()
		self.mapID = nil;
		self.mapScale = nil;
		self.isViewingDetails = BlizzardUIUtil:IsViewingQuestDetails();
		self.t1 = 1;
		self:RegisterEvent("MODIFIER_STATE_CHANGED");
	end

	function MapTracker:OnHide()
		WorldMapDataProvider:OnHide();
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");
	end

	function MapTracker:OnEvent(event, ...)
		if event == "MODIFIER_STATE_CHANGED" then
			local key, down = ...
			if down == 1 and (key == "LALT" or key == "RALT") then
				PinController:TriggerModifierStateChanged();
			end
		end
	end

	function MapTracker:EnableScripts()
		self.t1 = 0;
		self.t2 = 0;
		self:SetScript("OnShow", self.OnShow);
		self:SetScript("OnHide", self.OnHide);
		self:SetScript("OnUpdate", self.OnUpdate);
		self:SetScript("OnEvent", self.OnEvent);
	end

	function MapTracker:DisableScripts()
		self:SetScript("OnShow", nil);
		self:SetScript("OnHide", nil);
		self:SetScript("OnUpdate", nil);
		self:SetScript("OnEvent", nil);
		self:UnregisterEvent("MODIFIER_STATE_CHANGED");
	end

	function MapTracker:GetMapID()
		return self.mapID
	end

	function MapTracker:OnMapChanged(mapID)
		if not mapID then
			mapID = self:GetMapID();
		end

		if not mapID then return end;

		if PinController:DoesMapHaveData(mapID) then
			PinController:ListenEvents(true);
			WorldMapDataProvider:RefreshAllDataIfPossible();
		else
			PinController:ListenEvents(false);
			WorldMapDataProvider:RemoveAllIfNeeded();
		end
	end

	function MapTracker:OnCanvasScaleChanged(scale)
		WorldMapDataProvider:OnCanvasScaleChanged();
		PinController:TriggerCanvasScaleChanged(scale);
	end

	function MapTracker:OnViewingQuestDetailsChanged()
		if self.isViewingDetails then

		else
			self:OnMapChanged();
		end
	end
end


do
	PlumberWorldMapPinMixin = CreateFromMixins(MapCanvasPinMixin);

	local function Dummy_SetPassThroughButtons()
	end

	function PlumberWorldMapPinMixin:OnCreated()
		--When frame being created
		self.originalSetPassThroughButtons = self.SetPassThroughButtons;
		self.SetPassThroughButtons = Dummy_SetPassThroughButtons;
		self:AllowPassThroughRightButton(true);
		PinController:AddPin(self);
	end

	function PlumberWorldMapPinMixin:OnLoad()
		--newPin (see MapCanvasMixin:AcquirePin)
		self:SetScalingLimits(1, 1.0, 1.2);
		self.pinFrameLevelType = "PIN_FRAME_LEVEL_GROUP_MEMBER";    --PIN_FRAME_LEVEL_VIGNETTE  PIN_FRAME_LEVEL_AREA_POI   PIN_FRAME_LEVEL_WAYPOINT_LOCATION  PIN_FRAME_LEVEL_GROUP_MEMBER
		self.pinFrameLevelIndex = 1;
		self:SetTexture("Interface/AddOns/Plumber/Art/MapPin/SeedPlanting-Empty-Distant");
	end

	function PlumberWorldMapPinMixin:AllowPassThroughRightButton(unpackedPrimitiveType)
		--Original "SetPassThroughButtons" (see SimpleScriptRegionAPI for details) has chance to taint when called
		--So we overwrite it
		if (not self.isRightButtonAllowed) and (not InCombatLockdown()) then
			self.isRightButtonAllowed = true;
			if self.originalSetPassThroughButtons then
				self.originalSetPassThroughButtons(self, "RightButton");
			end
		end
	end

	function PlumberWorldMapPinMixin:SetTexture(texture, sampling)
		sampling = sampling or "LINEAR";
		self.Texture:SetTexture(texture, nil, nil, sampling);
		self.HighlightTexture:SetTexture(texture, nil, nil, sampling);
		self.HighlightTexture:SetVertexColor(0.4, 0.4, 0.4);
	end

	function PlumberWorldMapPinMixin:SetTexCoord(l, r, t, b)
		self.Texture:SetTexCoord(l, r, t, b);
		self.HighlightTexture:SetTexCoord(l, r, t, b);
	end

	function PlumberWorldMapPinMixin:OnMouseLeave()
		--BaseMapPoiPinMixin.OnMouseLeave(self);
		TooltipFrame:Hide();
	end

	function PlumberWorldMapPinMixin:SetClickable(state)
		if state ~= self.isClickable then
			self.isClickable = state;
		end
		self:SetMouseClickEnabled(state);
	end

	function PlumberWorldMapPinMixin:OnAcquired(data)
		if self.mixin ~= data.mixin then
			self.mixin = data.mixin;
			Mixin(self, data.mixin);
		end

		self:SetClickable(data.clickable);

		self.data = data;
		self:UpdatePosition();
		self:Update();
	end

	function PlumberWorldMapPinMixin:UpdatePosition()
		if self.data then
			self:SetPosition(self.data.x, self.data.y);
		end
	end

	function PlumberWorldMapPinMixin:OnMouseEnter()
		self.UpdateTooltip = nil;
		self:AllowPassThroughRightButton(true);
		self:PostMouseEnter();
	end

	function PlumberWorldMapPinMixin:TriggerMouseReEnter()
		self.UpdateTooltip = self.OnMouseEnter;
	end

	local function GetTextColorForEnabledState(enabledState)
		local Stats = Enum.WidgetEnabledState;

		if enabledState == Stats.Disabled then
			return DISABLED_FONT_COLOR;
		elseif enabledState == Stats.Red then
			return RED_FONT_COLOR;
		elseif enabledState == Stats.White then
			return HIGHLIGHT_FONT_COLOR;
		elseif enabledState == Stats.Green then
			return GREEN_FONT_COLOR;
		elseif enabledState == Stats.Artifact then
			return ARTIFACT_GOLD_COLOR;
		elseif enabledState == Stats.Black then
			return BLACK_FONT_COLOR;
		elseif enabledState == Stats.BrightBlue then
			return BRIGHTBLUE_FONT_COLOR;
		else
			return NORMAL_FONT_COLOR;
		end
	end

	function PlumberWorldMapPinMixin:AttachWidgetSetToTooltip(tooltip, widgetSetID, textRule)
		local widgets = C_UIWidgetManager.GetAllWidgetsBySetID(widgetSetID);

		--We only show TextWithState
		if widgets then
			for _, info in ipairs(widgets) do
				if info.widgetType == 8 then
					local widgetInfo = C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo(info.widgetID);
					if widgetInfo and widgetInfo.shownState ~= 0 and widgetInfo.text then
						if (not textRule) or (textRule(widgetInfo.text)) then
							local color = GetTextColorForEnabledState(widgetInfo.enabledState);
							local r, g, b = color:GetRGB();
							tooltip:AddLine(widgetInfo.text, r, g, b, true);
						end
					end
				end
			end
		end
	end

	function PlumberWorldMapPinMixin:AddQuestTimeToTooltip(tooltip, questID)
		if not questID then return end;

		local formattedTime, color, secondsRemaining = WorldMap_GetQuestTimeForTooltip(questID);
		if formattedTime and color then
			GameTooltip_AddColoredLine(tooltip, formattedTime, color);
		end
	end


	--Overwrite by module
	function PlumberWorldMapPinMixin:PostMouseEnter()

	end

	function PlumberWorldMapPinMixin:IsMouseClickEnabled()
		return true
	end

	function PlumberWorldMapPinMixin:OnMouseClickAction(mouseButton)

	end

	function PlumberWorldMapPinMixin:Update()

	end
end


do  --Master Switch
	local function EnableMapPinSystem(state)
		PinController:RequestLoadSettings();
	end

	local moduleData = {
		name = addon.L["ModuleName WorldMapPin_TWW"],
		dbKey = "WorldMapPin_TWW",
		description = string.format(L["ModuleDescription WorldMapPin_TWW"], L["Bountiful Delve"], L["Special Assignment"]);
		toggleFunc = EnableMapPinSystem,
		categoryID = 1,
		uiOrder = 1101,
		moduleAddedTime = 1721730000,
		categoryKeys = {
			"Map",
		},
	};

	addon.ControlCenter:AddModule(moduleData);
end


do  --Dev Tool
	local DevTool;

	local function SavePOIPosition(poiData)
		if not DevTool then
			DevTool = CreateFrame("Frame");
		end

		DevTool.poiData = poiData;
		DevTool.index = 0;
		DevTool.t = 0;

		DevTool:SetScript("OnUpdate", function(self, elapsed)
			self.t = self.t + elapsed;
			if self.t > 0.25 then
				self.t = 0;
			else
				return
			end

			self.index = self.index + 1;
			local data = self.poiData[self.index];
			if not data then
				self:SetScript("OnUpdate", nil);
				return
			end

			local mapID = data[1];
			local poiID = data[2];
			local poiInfo = C_AreaPoiInfo.GetAreaPOIInfo(mapID, poiID);

			if poiInfo then
				if not data.name then
					data.name = poiInfo.name;
				end

				local x, y = poiInfo.position:GetXY();
				API.ConvertMapPositionToContinentPosition(mapID, x, y, poiID);
			else
				print("Missing POI", poiID);
			end
		end);
	end
	addon.SavePOIPosition = SavePOIPosition;
end
