--Show a list of Dreamseed when approaching Emerald Bounty Soil.
--Show checkmark if the Plant's achievement criteria is complete
--10 yd range: Plant Seed

--Mechanism Explained:
--  Fire "VIGNETTE_MINIMAP_UPDATED" when an Emerald Bounty (growing or not growing) enters/leaves the minimap
--  Fire "UPDATE_UI_WIDGET" after planting a seed (Can be triggered by seeds planted across the entire map)
--  Cast hidden spell "Dreamseed (425856)" when you get to 6 yd, where you get dismounted and your cursor becomes "Investigate"
--  Taking off on Dragon with "Skyward Ascent (372610)" doesn't trigger "PLAYER_STARTED_MOVING", so we need to watch "UNIT_SPELLCAST_SUCCEEDED"

local _, addon = ...
local API = addon.API;
local GetPlayerMapCoord = API.GetPlayerMapCoord;
local GetCreatureIDFromGUID = API.GetCreatureIDFromGUID;
local QuickSlot = addon.QuickSlot;

local MAPID_EMRALD_DREAM = 2200;
local VIGID_BOUNTY = 5971;
local RANGE_PLANT_SEED = 10;
local FORMAT_ITEM_COUNT_ICON = "%s|T%s:0:0:0:0:64:64:0:64:0:64|t";
local SEED_ITEM_IDS = {208047, 208067, 208066};     --Gigantic, Plump, Small Dreamseed
local SEED_SPELL_IDS = {417508, 417645, 417642};
local QUICKSLOT_NAME = "dreamseed";

local math = math;
local sqrt = math.sqrt;
local format = string.format;
local pairs = pairs;
local ipairs = ipairs;
local C_VignetteInfo = C_VignetteInfo;
local GetVignetteInfo = C_VignetteInfo.GetVignetteInfo;
local GetVignettes = C_VignetteInfo.GetVignettes
local GetStatusBarWidgetVisualizationInfo = C_UIWidgetManager.GetStatusBarWidgetVisualizationInfo;
local GetItemDisplayVisualizationInfo = C_UIWidgetManager.GetItemDisplayVisualizationInfo;
local GetAllWidgetsBySetID = C_UIWidgetManager.GetAllWidgetsBySetID;
local IsFlying = IsFlying;
local IsMounted = IsMounted;
local GetAchievementCriteriaInfoByID = GetAchievementCriteriaInfoByID;
local UIParent = UIParent;
local GetItemCount = C_Item.GetItemCount;
local GetItemIconByID = C_Item.GetItemIconByID;
local GetBestMapForUnit = C_Map.GetBestMapForUnit;
local time = time;
local GetTime = GetTime;
local UnitChannelInfo = UnitChannelInfo;

local IS_SEED_SPELL = {};
do
	for _, spellID in ipairs(SEED_SPELL_IDS) do
		IS_SEED_SPELL[spellID] = true;
	end
end

local function GetVisibleEmeraldBountyGUID()
	local vignetteGUIDs = GetVignettes();
	local info;

	for i, vignetteGUID in ipairs(vignetteGUIDs) do
		info = GetVignetteInfo(vignetteGUID);
		if info and info.vignetteID == VIGID_BOUNTY then
			if info.onMinimap then
				return vignetteGUID, info.objectGUID
			end
		end
	end
end

local DataProvider = {};
local EL = CreateFrame("Frame", nil, UIParent);

local QUICKSLOT_DATA = {
	buttons = {
		{actionType = "item", itemID = 208047, spellID = 417508},   --Gigantic
		{actionType = "item", itemID = 208067, spellID = 417645},   --Plump
		{actionType = "item", itemID = 208066, spellID = 417642},   --Small Dreamseed
	},
	systemName = QUICKSLOT_NAME,
	spellcastType = 2,      --Channel
};

function EL:IsTrackedPlantGrowing()
	return self.trackedObjectGUID and DataProvider:IsPlantGrowing(self.trackedObjectGUID);
end

function EL:AttemptShowUI()
	if self:IsTrackedPlantGrowing() then
		return
	end

	self:RegisterEvent("UPDATE_UI_WIDGET");
	self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player");
	self:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player");

	self.isChanneling = nil;

	QuickSlot:SetButtonData(QUICKSLOT_DATA);
	QuickSlot:ShowUI();

	if self.trackedObjectGUID then
		local plantName, criteriaComplete = DataProvider:GetPlantNameAndProgress(self.trackedObjectGUID);
		if plantName then
			--plantName = "|cff808080"..plantName.."|r";  --DISABLED_FONT_COLOR
			if criteriaComplete then
				plantName = "|TInterface/AddOns/Plumber/Art/Button/Checkmark-Green-Shadow:16:16:-4:-2|t"..plantName;  --"|A:common-icon-checkmark:0:0:-4:-2|a" |TInterface/AddOns/Plumber/Art/Button/Checkmark-Green:0:0:-4:-2|t
			end
			QuickSlot:SetHeaderText(plantName, true);
			QuickSlot:SetDefaultHeaderText(plantName);
		end
	else
		QuickSlot:SetDefaultHeaderText(nil);
	end

	return true
end

function EL:CloseUI()
	self:UnregisterEvent("UPDATE_UI_WIDGET");
	self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_START");
	self:UnregisterEvent("UNIT_SPELLCAST_CHANNEL_STOP");

	QuickSlot:RequestCloseUI(QUICKSLOT_NAME);
end

function EL:GetMapPointsDistance(x1, y1, x2, y2)
	local x = self.mapWidth * (x1 - x2);
	local y = self.mapHeight * (y1 - y2);

	return sqrt(x*x + y*y)
end

function EL:OnEvent(event, ...)
	if event == "VIGNETTE_MINIMAP_UPDATED" then
		local vignetteGUID, onMinimap = ...
		if vignetteGUID == self.trackedVignetteGUID then
			self:StopTrackingPosition();
		elseif onMinimap then
			local info = GetVignetteInfo(vignetteGUID);
			if info and info.vignetteID == VIGID_BOUNTY then
				self.trackedObjectGUID = info.objectGUID;
				self:UpdateTargetLocation(vignetteGUID);
			end
		end

	elseif event == "PLAYER_STARTED_MOVING" then
		--Fires like crazy when channeling seed (Repeat START/STOP Moving)
		--Doesn't fire when taking off on a dragon
		self.isPlayerMoving = true;
	elseif event == "PLAYER_STOPPED_MOVING" then
		self.isPlayerMoving = false;
		if not self.isChanneling then
			self:CalculatePlayerToTargetDistance();
		end
	elseif event == "PLAYER_MOUNT_DISPLAY_CHANGED" or event == "UNIT_SPELLCAST_SUCCEEDED" then
		self:CalculatePlayerToTargetDistance();
		if IsMounted() then
			self.isPlayerMoving = true;
		end
		if event == "UNIT_SPELLCAST_SUCCEEDED" then
			local _, _, spellID = ...
			if spellID and IS_SEED_SPELL[spellID] then
				--This event fires when start Channeling (and we don't have a SPELLCAST_CHANNEL equivalent, it doesn't make sense)
				--UNIT_SPELLCAST_CHANNEL_START fires before this event
				--UNIT_SPELLCAST_CHANNEL_STOP fires regardless of finishing cast or not
				--So we check if the the channel stops after endTime
				local name, text, texture, startTime, endTime, isTradeSkill = UnitChannelInfo("player");
				self.channelEndTime = endTime;
			else
				self.channelEndTime = nil;
			end
		end
	elseif event == "UPDATE_UI_WIDGET" then
		local widgetInfo = ...
		if DataProvider:IsValuableWidget(widgetInfo.widgetID) then
			if self:IsTrackedPlantGrowing() then
				self:StopTrackingPosition();
			end
		end
	elseif event == "UNIT_SPELLCAST_CHANNEL_START" then
		self.isChanneling = true;

	elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" then
		self.isChanneling = nil;
		if self.channelEndTime then
			local t = GetTime();
			t = t * 1000;
			local diff = t - self.channelEndTime;
			if diff < 200 and diff > -200 then
				--Natural Complete
				C_Timer.After(0.2, function()
					DataProvider:MarkNearestPlantContributed();
				end);
			else
				--Interrupted
			end
		end
	end
end
EL:SetScript("OnEvent", EL.OnEvent);

function EL:UpdateTargetLocation(vignetteGUID)
	local position, facing = C_VignetteInfo.GetVignettePosition(vignetteGUID, MAPID_EMRALD_DREAM);
	self.trackedVignetteGUID = vignetteGUID;
	if position and not self:IsTrackedPlantGrowing() then
		self.targetX, self.targetY = position.x, position.y;
		self:StartTrackingPosition();
	else
		self:StopTrackingPosition();
	end
end

function EL:UpdateTrackedVignetteInfo()
	local vignetteGUID, objectGUID = GetVisibleEmeraldBountyGUID();
	self.trackedObjectGUID = objectGUID;

	if vignetteGUID then
		if vignetteGUID ~= self.trackedVignetteGUID  then
			self:UpdateTargetLocation(vignetteGUID);
		end
	else
		self:StopTrackingPosition();
	end
end

function EL:OnUpdate(elapsed)
	self.t = self.t + elapsed;
	if self.t > self.t0 then
		self.t = 0;
		if self.isPlayerMoving then
			self:CalculatePlayerToTargetDistance();
		end
	end
end

function EL:StartTrackingPosition()
	self:RegisterEvent("PLAYER_STARTED_MOVING");
	self:RegisterEvent("PLAYER_STOPPED_MOVING");
	self:RegisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED");     --In case player landing right on the soil
	self:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player");
	self.t = 0;
	self:CalculatePlayerToTargetDistance();
	self:SetScript("OnUpdate", self.OnUpdate);
end

function EL:StopTrackingPosition()
	if self.trackedVignetteGUID then
		self.trackedVignetteGUID = nil;
		self.trackedObjectGUID = nil;
		self.isPlayerMoving = nil;
		self.isChanneling = nil;
		self.isInRange = nil;
		self:UnregisterEvent("PLAYER_STARTED_MOVING");
		self:UnregisterEvent("PLAYER_STOPPED_MOVING");
		self:UnregisterEvent("PLAYER_MOUNT_DISPLAY_CHANGED");
		self:UnregisterEvent("UNIT_SPELLCAST_SUCCEEDED");
		self:SetScript("OnUpdate", nil);
		self:OnLeavingSoil();
	end
end

function EL:UpdateMap()
	self.mapWidth, self.mapHeight = C_Map.GetMapWorldSize(MAPID_EMRALD_DREAM);
end
EL.mapWidth = 7477.1201171875;
EL.mapHeight = 4983.330078125;


function EL:CalculatePlayerToTargetDistance()
	self.playerX, self.playerY = GetPlayerMapCoord(MAPID_EMRALD_DREAM);
	if self.playerX and self.playerY then
		local d = self:GetMapPointsDistance(self.playerX, self.playerY, self.targetX, self.targetY);
		--print(format("Distance: %.1f yd", d));

		--Change update frequency dynamically
		if d <= 10 then
			self.t0 = 0.2;
		elseif d < 50 then
			self.t0 = 0.5;
		else
			self.t0 = 1;
		end

		if d <= RANGE_PLANT_SEED and not IsFlying() then
			if not self.isInRange then
				self.isInRange = true;
				self:OnApproachingSoil();
			end
		elseif self.isInRange then
			self.isInRange = false;
			self:OnLeavingSoil();
		end
	end
end

function EL:OnApproachingSoil()
	local success = self:AttemptShowUI();
	--Frame not shown if Growth Cycle has already begun
end

function EL:OnLeavingSoil()
	self:CloseUI();
end


local ZoneTriggerModule;

local function EnableModule(state)
	if state then
		if not ZoneTriggerModule then
			local module = API.CreateZoneTriggeredModule("quickslotseed");
			ZoneTriggerModule = module;
			module:SetValidZones(MAPID_EMRALD_DREAM);
			EL:UpdateMap();

			local function OnEnterZoneCallback()
				EL:RegisterEvent("VIGNETTE_MINIMAP_UPDATED");
				EL:UpdateTrackedVignetteInfo();
			end

			local function OnLeaveZoneCallback()
				EL:UnregisterEvent("VIGNETTE_MINIMAP_UPDATED");
				EL:StopTrackingPosition();
			end

			module:SetEnterZoneCallback(OnEnterZoneCallback);
			module:SetLeaveZoneCallback(OnLeaveZoneCallback);
		end
		ZoneTriggerModule:SetEnabled(true);
		ZoneTriggerModule:Update();
	else
		if ZoneTriggerModule then
			ZoneTriggerModule:SetEnabled(false);
		end
		EL:UnregisterEvent("VIGNETTE_MINIMAP_UPDATED");
		EL:StopTrackingPosition();
		EL:CloseUI();
	end
end

do
	local moduleData = {
		name = addon.L["ModuleName EmeraldBountySeedList"],
		dbKey = "EmeraldBountySeedList",
		description = addon.L["ModuleDescription EmeraldBountySeedList"],
		toggleFunc = EnableModule,
		categoryID = 1002,
		uiOrder = 1,
		categoryKeys = {
			"Old",
		},
	};

	addon.ControlCenter:AddModule(moduleData);
end


local PLANT_DATA = {
	--17 types of Plant + 1 Tutorial
	--Kudos to @patf0rd on Twitter!

	--from VigInfo.ObjectGUID
	--/dump C_UIWidgetManager.GetStatusBarWidgetVisualizationInfo(5136) (bag max 180(3 min))
	--C_UIWidgetManager.GetItemDisplayVisualizationInfo()
	--C_UIWidgetManager.GetTextWithStateWidgetVisualizationInfo()
	--shownState = 1, itemInfo = {showAsEarned = true}
	--AchievementID = 19013;    --I Dream of Seeds
	--https://warcraft.wiki.gg/wiki/UPDATE_UI_WIDGET
	--C_UIWidgetManager.GetAllWidgetsBySetID
	--DBC: Vignette - VisibleTrackingQuestID to find 3 chest for each plant

	--[creatureID] = {criteriaID, widgetID(Growth Cycle),   3 Nurture Widgets,  6 Reward Widgets(There are actually 12 item widgets but only need 6)(Small, Plump, Gigantic Bounty, Small, Medium, Large Bloom(Shared by 3 rarities)),   3 DreamseedChest[VigID] (based on quality)} --Purple/Blue/Green, 3 widgetSetIDs
	--          1      2        3     4     5        6     7     8     9     10    11       12    13    14       15   16   17
	[208443] = {62028, 5084,    4994, 5087, 5088,    5183, 5181, 5182, 5184, 5245, 5247,    5769, 5856, 5857,    869, 918, 919}, --"Ysera's Clover"
	[208511] = {62029, 5122,    4995, 5089, 5090,    5186, 5188, 5187, 5185, 5248, 5249,    5772, 5854, 5855,    870, 920, 921}, --"Chiming Foxglove"
	[208556] = {62030, 5123,    4996, 5091, 5092,    5179, 5172, 5180, 5173, 5250, 5251,    5773, 5853, 5853,    871, 922, 923}, --"Dragon's Daffodil"
	[208563] = {62031, 5125,    5000, 5095, 5096,    5194, 5195, 5196, 5193, 5254, 5255,    5775, 5848, 5849,    873, 926, 927}, --"Singing Weedling"
	[208605] = {62032, 5126,    5001, 5097, 5098,    5198, 5200, 5199, 5197, 5256, 5257,    5776, 5846, 5847,    879, 928, 929}, --"Fuzzy Licorice"
	[208606] = {62039, 5127,    5002, 5099, 5100,    5202, 5204, 5203, 5201, 5258, 5259,    5777, 5844, 5845,    875, 930, 931}, --"Lofty Lupin"
	[208607] = {62038, 5128,    5003, 5101, 5102,    5206, 5208, 5207, 5205, 5260, 5261,    5778, 5842, 5843,    876, 932, 933}, --"Ringing Rose"  (ok)
	[208615] = {62037, 5129,    5004, 5103, 5104,    5210, 5212, 5211, 5209, 5262, 5263,    5779, 5840, 5841,    877, 934, 935}, --"Dreamer's Daisy"  (ok)
	[208616] = {62035, 5130,    5005, 5105, 5106,    5214, 5216, 5215, 5213, 5264, 5265,    5780, 5838, 5839,    878, 936, 937}, --"Viridescent Sprout"
	[208617] = {62041, 5124,    4999, 5093, 5094,    5191, 5189, 5190, 5192, 5252, 5253,    5774, 5850, 5851,    872, 924, 925}, --"Belligerent Begonias"  --Sometimes not visible due to phasing?
	[209583] = {62027, 5131,    5075, 5108, 5109,    5218, 5220, 5219, 5217, 5266, 5267,    5782, 5783, 5784,    897, 938, 939}, --"Lavatouched Lilies" (ok)
	[209599] = {62040, 5132,    5076, 5107, 5110,    5222, 5224, 5223, 5221, 5268, 5269,    5787, 5788, 5789,    941, 898, 940}, --"Lullaby Lavender" (ok)
	[209880] = {62036, 5133,    5077, 5111, 5112,    5226, 5228, 5227, 5225, 5270, 5271,    5790, 5791, 5792,    899, 942, 943}, --"Glade Goldenrod"  (ok)
	[210723] = {62185, 5134,    5113, 5114, 5115,    5230, 5232, 5231, 5229, 5272, 5273,    5793, 5862, 5863,    944, 946, 945}, --"Comfy Chamomile"
	[210724] = {62186, 5135,    5116, 5117, 5118,    5234, 5236, 5235, 5233, 5274, 5275,    5864, 5865, 5866,    947, 948, 949}, --"Moon Tulip"
	[210725] = {62189, 5136,    5119, 5120, 5121,    5238, 5240, 5239, 5237, 5276, 5277,    5867, 5868, 5869,    950, 951, 952}, --"Flourishing Scurfpea"
	[211059] = {62397, 5149,    5146, 5147, 5148,    5242, 5244, 5243, 5241, 5278, 5279,    5876, 5877, 5878,    970, 971, 972}, --"Whisperbloom Sapling" ! (not spawning due to Superbloom phasing)

	--Ageless Blossom (criteriaID: 62396)
};

function DataProvider:IsValuableWidget(widgetID)
	if not self.valuableWidgets then
		self.valuableWidgets = {};
		for _, data in pairs(PLANT_DATA) do
			if data[2] then
				self.valuableWidgets[ data[2] ] = true
			end
		end
	end

	return widgetID and self.valuableWidgets[widgetID]
end

function DataProvider:GetGrowthTimesByCreatureID(creatureID)
	if creatureID and PLANT_DATA[creatureID] then
		local widgetID = PLANT_DATA[creatureID][2];
		local info = widgetID and GetStatusBarWidgetVisualizationInfo(widgetID);
		if info then
			return info.barValue, info.barMax
		end
	end
end

function DataProvider:GetGrowthTimes(objectGUID)
	local creatureID = GetCreatureIDFromGUID(objectGUID);
	return self:GetGrowthTimesByCreatureID(creatureID)
end

function DataProvider:IsPlantGrowingByCreatureID(creatureID)
	local remainingTime = self:GetGrowthTimesByCreatureID(creatureID)
	return remainingTime and remainingTime > 0
end

function DataProvider:IsPlantGrowing(objectGUID)
	local remainingTime = self:GetGrowthTimes(objectGUID);
	return remainingTime and remainingTime > 0
end

function DataProvider:GetPlantNameByCreatureID(creatureID)
	if creatureID and PLANT_DATA[creatureID] then
		local criteriaString = GetAchievementCriteriaInfoByID(19013, PLANT_DATA[creatureID][1]);
		return criteriaString
	end
end

function DataProvider:GetPlantNameAndProgress(objectGUID, isCreatureID)
	local id;
	if isCreatureID then
		id = objectGUID;
	else
		id = GetCreatureIDFromGUID(objectGUID);
	end
	if id and PLANT_DATA[id] then
		local criteriaString, criteriaType, completed = GetAchievementCriteriaInfoByID(19013, PLANT_DATA[id][1]);
		return criteriaString, completed
	end
end

function DataProvider.GetActiveDreamseedGrowthTimes()
	--This function shares between modules. (additional "Growth Cycle Timer" on PlayerChoiceFrame)
	--If this module is disabled "trackedVignetteGUID" will be nil and we to obtain it

	local vignetteGUID = EL.trackedVignetteGUID or DataProvider.lastVignetteGUID;

	if not vignetteGUID then
		vignetteGUID = GetVisibleEmeraldBountyGUID();
		DataProvider.lastVignetteGUID = vignetteGUID;
	end

	if vignetteGUID then
		local info = GetVignetteInfo(vignetteGUID);
		if info and info.vignetteID == VIGID_BOUNTY then
			if info.onMinimap then
				return DataProvider:GetGrowthTimes(info.objectGUID)
			end
		end
	end
end

function DataProvider:GetNurtureProgress(objectGUID, convertToString)
	local creatureID = GetCreatureIDFromGUID(objectGUID);
	if creatureID and PLANT_DATA[creatureID] then
		local widgetID = PLANT_DATA[creatureID][3];
		local info = widgetID and GetStatusBarWidgetVisualizationInfo(widgetID);
		if info and info.barValue and info.barMax then
			local barValue, barMax = info.barValue, info.barMax;
			if not (barValue and barMax) then return end;

			if barValue > barMax then
				barValue = barMax;
			end

			if convertToString then
				local str = info.text;
				if str then
					str = str..": ".. barValue .. "/" ..barMax
				else
					str = barValue .. "/" ..barMax
				end
				return str
			else
				return barValue, barMax
			end
		end
	end
end

function DataProvider:GetRewardTierByCreatureID(creatureID)
	local seedTier, bloomTier = 0, 0;
	if creatureID and PLANT_DATA[creatureID] then
		local info, widgetID, itemID;
		for i = 6, 8 do
			widgetID = PLANT_DATA[creatureID][i];
			info = widgetID and GetItemDisplayVisualizationInfo(widgetID);
			if info and info.shownState == 1 and info.itemInfo and info.itemInfo.showAsEarned then
				itemID = info.itemInfo.itemID;
				if itemID == 210217 then
					seedTier = 1;   --Small Dreamy Bounty
				elseif itemID == 210218 then
					seedTier = 2;   --Plump Dreamy Bounty
				elseif itemID == 210219 then
					seedTier = 3;   --Gigantic Dreamy Bounty
				end
				break
			end
		end
		for i = 9, 11 do
			widgetID = PLANT_DATA[creatureID][i];
			info = widgetID and GetItemDisplayVisualizationInfo(widgetID);
			if info and info.shownState == 1 and info.itemInfo and info.itemInfo.showAsEarned then
				itemID = info.itemInfo.itemID;
				if itemID == 210224 then
					bloomTier = 1;  --Small     <50
				elseif itemID == 210225 then
					bloomTier = 2;  --Medium    <100
				elseif itemID == 210226 then
					bloomTier = 3;  --Large     =100
				end
				break
			end
		end
	end
	return seedTier, bloomTier
end

function DataProvider:GetRewardTier(objectGUID)
	local creatureID = GetCreatureIDFromGUID(objectGUID);
	return self:GetRewardTierByCreatureID(creatureID)
end

function DataProvider:GetGrowthStateChanged(creatureID, growthRemainingSeconds)
	if not self.growthEndTimes then
		self.growthEndTimes = {};
	end

	if growthRemainingSeconds <= 0 then
		if self.growthEndTimes[creatureID] then
			return true
		else
			return false
		end
	end

	local isChanged = false;
	local currentTime = time();

	if self.growthEndTimes[creatureID] then
		if currentTime > self.growthEndTimes[creatureID] then
			isChanged = true;
		end
	else
		isChanged = true;
	end

	self.growthEndTimes[creatureID] = currentTime + growthRemainingSeconds;

	return isChanged
end

function DataProvider:Debug_ConstructWidgetID()
	--3 Bounty, 3 Blooms
	local REWARD_ITEMS = {210217, 210218, 210219, 210224, 210225, 210226};

	for creatureID in pairs(PLANT_DATA) do
		local info, widgetSetID, setWidgets;
		local itemIDxWidgetID = {};

		for i = 15, 17 do
			widgetSetID = PLANT_DATA[creatureID][i];
			setWidgets = GetAllWidgetsBySetID(widgetSetID);
			if setWidgets then
				local numItems = 0;
				for _, widgetInfo in ipairs(setWidgets) do
					if widgetInfo.widgetType == 27 then
						numItems = numItems + 1;
						local widgetID = widgetInfo.widgetID;
						info = GetItemDisplayVisualizationInfo(widgetID);
						local itemID = info.itemInfo.itemID;
						if not itemIDxWidgetID[itemID] then
							itemIDxWidgetID[itemID] = widgetID;
						end
					end
				end
			end
		end

		local output;
		for i, itemID in ipairs(REWARD_ITEMS) do
			local widgetID = itemIDxWidgetID[itemID];
			if output then
				output = output..", "..widgetID;
			else
				output = widgetID;
			end
		end
		API.SaveDataUnderKey(creatureID, output);
	end
end

--[[
function DataProvider:HasAnyRewardByCreatureID(creatureID)
	--We now use a more RAM friendly (not getting all the widgets in a wigetSet), but less robust approach
	if creatureID and PLANT_DATA[creatureID] then
		if self:IsDreamseedChestAvailableByCreatureID(creatureID) then
			return true, true
		end
		local creatureData = PLANT_DATA[creatureID];
		local info;
		for i = 6, 11 do
			info = GetItemDisplayVisualizationInfo(creatureData[i]);
			if info and info.shownState == 1 and info.itemInfo and info.itemInfo.showAsEarned then
				return true, false
			end
		end
	end
	return false, false
end
--]]

function DataProvider:HasAnyReward(objectGUID)
	local creatureID = GetCreatureIDFromGUID(objectGUID);
	return self:HasAnyRewardByCreatureID(creatureID);
end

function DataProvider:GetResourcesText()
	--208066, 208067, 208047
	local info = C_CurrencyInfo.GetCurrencyInfo(2650);  --Emerald Dewdrop
	local anyNonZero = false;
	local text;

	if info then
		local quantity = info.quantity;
		if quantity > 0 then
			anyNonZero = true;
			if quantity > 9999 then
				quantity = "9999+";
			end
		else
			quantity = "|cff8080800|r";
		end
		text = format(FORMAT_ITEM_COUNT_ICON, quantity, info.iconFileID).."  ";
	else
		return
	end

	local count, icon;

	for _, itemID in ipairs(SEED_ITEM_IDS) do
		count = GetItemCount(itemID);
		icon = GetItemIconByID(itemID);
		if count == 0 then
			count = "|cff8080800|r";
		else
			anyNonZero = true;
		end
		text = text.."  "..format(FORMAT_ITEM_COUNT_ICON, count, icon);
	end

	if anyNonZero then
		return text
	else
		--we don't show this text if player has none of the required resources
	end
end

function DataProvider:GetChestOwnerCreatureIDs()
	local tbl = {};
	local vignetteID;
	for creatureID, data in pairs(PLANT_DATA) do
		for i = 12, 14 do
			vignetteID = data[i];
			tbl[vignetteID] = creatureID;
		end
	end
	return tbl
end

function DataProvider:SetChestStateByCreatureID(creatureID, state, objectGUID, x, y)
	if not creatureID then return end;

	if not self.dreamseedChestStates then
		self.dreamseedChestStates = {};
	end

	--local plantName = self:GetPlantNameAndProgress(creatureID, true);
	if state and not self.dreamseedChestStates[creatureID] then
		self.dreamseedChestStates[creatureID] = {objectGUID, x, y};
		if self.BackupCreaturePositions[creatureID] then
			self.BackupCreaturePositions[creatureID] = {x, y};  --overwrite our database in case Blizzard moves things
		end
	else
		self.dreamseedChestStates[creatureID] = nil;
	end

	if not state then
		--Player looted the chest
		self:SetPlantContributedByCreatureID(creatureID, nil);
	end
end

function DataProvider:SetChestState(objectGUID, state, x, y)
	local creatureID = GetCreatureIDFromGUID(objectGUID);
	self:SetChestStateByCreatureID(creatureID, state, objectGUID, x, y)
end

function DataProvider:TryGetChestInfoByCreatureID(creatureID)
	if self.dreamseedChestStates then
		return self.dreamseedChestStates[creatureID]
	end
end

function DataProvider:GetPlantCreatureIDs()
	local tbl = {};
	for creatureID in pairs(PLANT_DATA) do
		tbl[creatureID] = false;
	end
	return tbl
end

function DataProvider:GetBackupLocation(creatureID)
	return creatureID and self.BackupCreaturePositions[creatureID]
end

function DataProvider:EnumerateSpawnLocations()
	return pairs(self.BackupCreaturePositions)
end

function DataProvider:GetNearbyPlantInfo()
	local uiMapID = GetBestMapForUnit("player");
	if uiMapID ~= MAPID_EMRALD_DREAM then return end;

	local x, y = GetPlayerMapCoord(MAPID_EMRALD_DREAM);
	if x and y then
		EL:UpdateMap();
		local distance;
		for creatureID, position in self:EnumerateSpawnLocations() do
			distance = EL:GetMapPointsDistance(x, y, position[1], position[2]);
			if distance <= 15 then
				return creatureID, position[1], position[2]
			end
		end
	end
end

function DataProvider:BuildPlantIndexTable()
	local function SortByCriteriaID(a, b)
		return PLANT_DATA[a] < PLANT_DATA[b]
	end

	local plants = {};
	local n = 0;

	for creatureID, data in pairs(PLANT_DATA) do
		n = n + 1;
		plants[n] = creatureID;
	end

	table.sort(plants, SortByCriteriaID);

	local plantXIndex = {};
	for index, creatureID in ipairs(plants) do
		plantXIndex[creatureID] = index;
	end

	self.indexXPlant = plants;
	self.plantXIndex = plantXIndex;
end

function DataProvider:GetPlantIndexByCreatureID(creatureID)
	if not self.plantXIndex then
		self:BuildPlantIndexTable();
	end

	return self.plantXIndex[creatureID]
end

function DataProvider:GetPlantCreatureIDByIndex(index)
	if not self.indexXPlant then
		self:BuildPlantIndexTable();
	end

	return self.indexXPlant[index]
end

function DataProvider:SetPlantContributedByCreatureID(creatureID, chestFlag)
	--chestFlag:
	--nil (no chest)
	--1 Potential chest (data from WidgetInfo)
	--2 Chest (Player made contribution during current game session)
	if not self.plantContributed then
		self.plantContributed = {};
	end
	self.plantContributed[creatureID] = chestFlag
end

function DataProvider:HasAnyRewardByCreatureID(creatureID)
	--Alternative approach due to inconsistent data between actual spawn and widgetInfo
	--Check if player has successfully cast Dreamseed / made contributions using PlayerChoiceUI
	if creatureID and PLANT_DATA[creatureID] then
		if not self.plantContributed then
			self.plantContributed = {};
		end
		return self.plantContributed[creatureID] ~= nil
	end
	return false, false
end

function DataProvider:MarkNearestPlantContributed()
	local creatureID = self:GetNearbyPlantInfo();
	if creatureID then
		self:SetPlantContributedByCreatureID(creatureID, 2);
	end
end

function DataProvider:GetPlayerDistanceToTarget(playerX, playerY, targetX, targetY)
	return EL:GetMapPointsDistance(playerX, playerY, targetX, targetY);
end


---- Workaround for inconsistent Chest flag:
---- 1. When player logs in, query UIWidgetManager to get chests from previous game session
---- 2. We check if the chests actually exist when player gets to their approximate locations

function DataProvider:HasAnyPotentialRewardByCreatureID(creatureID)
	--We now use a more RAM friendly (not getting all the widgets in a wigetSet), but less robust approach
	if creatureID and PLANT_DATA[creatureID] then
		local creatureData = PLANT_DATA[creatureID];
		local info;
		for i = 6, 11 do
			info = GetItemDisplayVisualizationInfo(creatureData[i]);
			if info and info.shownState == 1 and info.itemInfo and info.itemInfo.showAsEarned then
				return true
			end
		end
	end
	return false
end

function DataProvider:GetChestOwnerCreatureID(vignetteID)
	if not self.chestOwnerCreatureIDs then
		self.chestOwnerCreatureIDs = self:GetChestOwnerCreatureIDs();
	end
	return vignetteID and self.chestOwnerCreatureIDs[vignetteID]
end

function DataProvider:IsDreamseedChest(vignetteID)
	return self:GetChestOwnerCreatureID(vignetteID) ~= nil
end

function DataProvider:RequestScanChests()
	if self.scanComplete then return end;

	if self.scanner then
		self.scanner.t = 0;
		self.scanner:Show();
		return
	end

	local anyPotentionReward;
	local targetList = {};

	local chestFlagAuto = 1;
	local numTargets = 0;

	for creatureID in pairs(PLANT_DATA) do
		if self:HasAnyPotentialRewardByCreatureID(creatureID) then
			anyPotentionReward = true;
			if self.BackupCreaturePositions[creatureID] then
				targetList[creatureID] = true;
				self:SetPlantContributedByCreatureID(creatureID, chestFlagAuto);
				numTargets = numTargets + 1;
			end
		end
	end

	--debugprint("numTargets", numTargets)


	--[[
		--Disable AB Testing (Dreamseed Chests Icon can still be stuck on the map, so we have to enable on-set scanning every game session)
	if PlumberDB then
		if PlumberDB.DreamseedChestABTesting == nil then
			PlumberDB.DreamseedChestABTesting = math.random(100) >= 50;
		end
		if not PlumberDB.DreamseedChestABTesting then
			self.scanComplete = true;
			return
		end
	end
	--]]

	if anyPotentionReward then
		if not self.scanner then
			self.scanner = CreateFrame("Frame");
			self.scanner.t = 0;
			self.scanner:SetScript("OnUpdate", function(f, elapsed)
				f.t = f.t + elapsed;
				if f.t > 3 then
					f.t = 0;
					local playerX, playerY = GetPlayerMapCoord(MAPID_EMRALD_DREAM);
					local distance;
					if playerX and playerY then
						local anyTarget, nearbyCreatureID, location;

						for creatureID in pairs(targetList) do
							anyTarget = true;
							location = self.BackupCreaturePositions[creatureID];
							if location then
								distance = self:GetPlayerDistanceToTarget(playerX, playerY, location[1], location[2]);
								if distance <= 200 then
									nearbyCreatureID = creatureID;
									break
								end
							end
						end

						if not anyTarget then
							f:SetScript("OnUpdate", nil);
							f.t = nil;
							self.scanComplete = true;
							--debugprint("Scan Complete");
						end

						if nearbyCreatureID then
							local vignetteGUIDs = GetVignettes();
							local info, ownerCreatureID;
							for i, vignetteGUID in ipairs(vignetteGUIDs) do
								info = GetVignetteInfo(vignetteGUID);
								if info then
									ownerCreatureID = self:GetChestOwnerCreatureID(info.vignetteID);
									if ownerCreatureID then
										--debugprint(ownerCreatureID, nearbyCreatureID)
									end
									if ownerCreatureID == nearbyCreatureID then
										targetList[ownerCreatureID] = nil;
										self:SetPlantContributedByCreatureID(nearbyCreatureID, 2);
										--debugprint("Found One");
										break
									end
								end
							end
							if self.plantContributed[nearbyCreatureID] == chestFlagAuto then
								if DataProvider:IsPlantGrowingByCreatureID(nearbyCreatureID) then
									--debugprint("Still Growing");
								else
									self:SetPlantContributedByCreatureID(nearbyCreatureID, nil);
									--debugprint("Doesn't Exist");
								end
							end
						end
					end
				end
			end);
		end
	else
		self.scanComplete = true;
	end
end

function DataProvider:PauseScanner()
	if self.scanComplete then return end;

	if self.scanner then
		self.scanner:Hide();
	end
end


API.GetActiveDreamseedGrowthTimes = DataProvider.GetActiveDreamseedGrowthTimes;
API.DreamseedUtil = DataProvider;




DataProvider.BackupCreaturePositions = {
	[208605] = {
		0.634965717792511,
		0.4709928631782532,
	},
	[209880] = {
		0.4074325561523438,
		0.4348400235176086,
	},
	[210725] = {
		0.4873448014259338,
		0.8045594692230225,
	},
	[208563] = {
		0.6302892565727234,
		0.5284437537193298,
	},
	[208443] = {
		0.5924164056777954,
		0.5876181125640869,
	},
	[208606] = {
		0.5665966272354126,
		0.4488694071769714,
	},
	[211059] = {
		0.5114631652832031,
		0.5863984823226929,
	},
	[208556] = {
		0.6395775675773621,
		0.6483616232872009,
	},
	[209583] = {
		0.4067537784576416,
		0.2478460669517517,
	},
	[209599] = {
		0.5651239156723022,
		0.3766665458679199,
	},
	[208615] = {
		0.4638132452964783,
		0.4048779606819153,
	},
	[208511] = {
		0.5459136962890625,
		0.6763055324554443,
	},
	[208617] = {
		0.4990068674087524,
		0.3544299006462097,
	},
	[208616] = {
		0.400239109992981,
		0.5268844366073608,
	},
	[210724] = {
		0.4264156222343445,
		0.740414023399353,
	},
	[208607] = {
		0.4916412830352783,
		0.4806915521621704,
	},
	[210723] = {
		0.3845012784004211,
		0.5920345783233643,
	},
};

---- Dev Tool
--[[
do
	function YeetWidget_StatusBar()
		local GetStatusBarWidgetVisualizationInfo = C_UIWidgetManager.GetStatusBarWidgetVisualizationInfo;
		local info;
		local n = 0;
		for widgetID = 5000, 5200 do
			info = GetStatusBarWidgetVisualizationInfo(widgetID);
			if info and info.barMax ~= 180 and info.barValue > 0 then
				n = n + 1;
				print("#"..n, widgetID, info.text);
			end
		end
	end

	function YeetWidgetInfo()
		for widgetID, widgetType in pairs(EL.widgetData) do
			print("ID:", widgetID, "  Type:", widgetType)
		end
	end

	function YeetVignette()
		local vignetteGUIDs = C_VignetteInfo.GetVignettes();
		local info, position;

		local vignettesGUIDs = {};
		local total = 0;

		for i, guid in ipairs(vignetteGUIDs) do
			info = C_VignetteInfo.GetVignetteInfo(guid);
			if info and info.name then
				total = total + 1;
				vignettesGUIDs[total] = info.vignetteGUID;
				if info.name == "Dreamseed Chest" and true then
					print(total, format("#%s  type:%s  %s  WorldMap %s  Minimap %s  Unique %s  %s", info.vignetteID, info.type, info.name, tostring(info.onWorldMap), tostring(info.onMinimap), tostring(info.isUnique), info.vignetteGUID));
				end
			end
		end

		local bestUniqueVignetteIndex = C_VignetteInfo.FindBestUniqueVignette(vignettesGUIDs);
		print("Show ",bestUniqueVignetteIndex);
		if bestUniqueVignetteIndex then
			info = C_VignetteInfo.GetVignetteInfo( vignettesGUIDs[bestUniqueVignetteIndex] );
			print(info.atlasName);
		end
	end

	function YeetDistance()
		local uiMapID = C_Map.GetBestMapForUnit("player");
		local trueDistance = C_Navigation.GetDistance();

		local waypoint = C_Map.GetUserWaypoint();
		local x0, y0 = waypoint.position.x, waypoint.position.y;

		local playerPosition = C_Map.GetPlayerMapPosition(uiMapID, "player");
		local x1, y1 = playerPosition:GetXY();
		local width, height = C_Map.GetMapWorldSize(uiMapID);

		local distance = math.sqrt( ((x1 -x0)*width)^2 + ((y1 -y0)*height)^2 );
		print(trueDistance, distance);
	end

	function TestStatusBar()
		if not PlumberTestStatusBar then
			PlumberTestStatusBar = addon.CreateTimerFrame(UIParent);
			PlumberTestStatusBar:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
			PlumberTestStatusBar:SetStyle(2);
			PlumberTestStatusBar:SetWidth(192);
			PlumberTestStatusBar:UpdateMaxBarFillWidth();
			PlumberTestStatusBar:SetReverse(true);
			PlumberTestStatusBar:SetContinuous(false);
		end
		PlumberTestStatusBar:SetDuration(180);
	end

	function TestTinyBar()
		if not PlumberTestStatusBar then
			PlumberTestStatusBar = addon.CreateTinyStatusBar(UIParent);
			PlumberTestStatusBar:SetPoint("CENTER", UIParent, "CENTER", 0, 0);
			PlumberTestStatusBar:UpdateMaxBarFillWidth();
			PlumberTestStatusBar:SetReverse(true);
		end
		PlumberTestStatusBar:SetDuration(10);
	end
end
--]]

--[[
local UiWidgets_Reward_Bounty = {
	--{Small, Plump, Gigantic Bounty, Small, Medium, Large Bloom}
	{5179, 5172, 5180, 5173, 5245, 5247},
	{5183, 5181, 5182, 5184, 5248, 5249},
	{5186, 5188, 5187, 5185, 5250, 5251},
	{5191, 5189, 5190, 5192, 5252, 5253},
	{5194, 5195, 5196, 5193, 5254, 5255},
	{5198, 5200, 5199, 5197, 5256, 5257},
	{5202, 5204, 5203, 5201, 5258, 5259},
	{5206, 5208, 5207, 5205, 5260, 5261},
	{5210, 5212, 5211, 5209, 5262, 5263},
	{5214, 5216, 5215, 5213, 5264, 5265},
	{5218, 5220, 5219, 5217, 5266, 5267},
	{5222, 5224, 5223, 5221, 5268, 5269},
	{5226, 5228, 5227, 5225, 5270, 5271},
	{5230, 5232, 5231, 5229, 5272, 5273},
	{5234, 5236, 5235, 5233, 5274, 5275},
	{5238, 5240, 5239, 5237, 5276, 5277},
	{5242, 5244, 5243, 5241, 5278, 5279},
};
--]]
