local VERSION_TEXT = "1.9.0 d";
local VERSION_DATE = 1775400000;


local addonName, addon = ...

local L = {};       --Locale
local API = {};     --Custom APIs used by this addon
local DB;
local DB_PC;        --Per Character

PlumberGlobals = {};

addon.L = L;
addon.API = API;
addon.VERSION_TEXT = VERSION_TEXT;


local EL = CreateFrame("Frame");

local CallbackRegistry = {};
CallbackRegistry.events = {};
CallbackRegistry.addonLoadedCallbacks = {};
addon.CallbackRegistry = CallbackRegistry;

local tinsert = table.insert;
local tremove = table.remove;
local type = type;
local ipairs = ipairs;

do  --CallbackRegistry
	--[[
		callbackType:
			1. Function func(owner)
			2. Method owner:func()
	--]]

	function CallbackRegistry:Register(event, func, owner)
		if not self.events[event] then
			self.events[event] = {};
		end

		local callbackType;

		if type(func) == "string" then
			callbackType = 2;
		else
			callbackType = 1;
		end

		tinsert(self.events[event], {callbackType, func, owner})
	end
	CallbackRegistry.RegisterCallback = CallbackRegistry.Register;

	function CallbackRegistry:Trigger(event, ...)
		if self.events[event] then
			for _, cb in ipairs(self.events[event]) do
				if cb[1] == 1 then
					if cb[3] then
						cb[2](cb[3], ...);
					else
						cb[2](...);
					end
				else
					cb[3][cb[2]](cb[3], ...);
				end
			end
		end
	end

	function CallbackRegistry:RegisterSettingCallback(dbKey, func, owner)
		self:Register("SettingChanged."..dbKey, func, owner);
	end

	function CallbackRegistry:UnregisterCallback(event, callback, owner)
		if not owner then return end;

		if self.events[event] then
			local callbacks = self.events[event];
			local i = 1;
			local cb = callbacks[i];

			if type(callback) == "string" then
				while cb do
					if cb[1] == 2 and cb[2] == callback and cb[3] == owner then
						tremove(callbacks, i);
					else
						i = i + 1;
					end
					cb = callbacks[i];
				end
			else
				while cb do
					if cb[1] == 1 and cb[2] == callback and cb[3] == owner then
						tremove(callbacks, i);
					else
						i = i + 1;
					end
					cb = callbacks[i];
				end
			end
		end
	end

	function CallbackRegistry:UnregisterCallbackOwner(event, owner)
		if not owner then return end;

		if self.events[event] then
			local callbacks = self.events[event];
			local i = 1;
			local cb = callbacks[i];
			while cb do
				if cb[3] == owner then
					tremove(callbacks, i);
				else
					i = i + 1;
				end
				cb = callbacks[i];
			end
		end
	end

	function CallbackRegistry:RegisterAddOnLoadedCallback(name, callback)
		if C_AddOns.IsAddOnLoaded(name) then
			callback();
			return
		end

		if not self.addonLoadedCallbacks[name] then
			self.addonLoadedCallbacks[name] = {};
			EL:RegisterEvent("ADDON_LOADED");
		end

		self.addonLoadedCallbacks[name][callback] = true;
	end

	function CallbackRegistry:UnregisterAddOnLoadedCallback(name, callback)
		if self.addonLoadedCallbacks[name] then
			self.addonLoadedCallbacks[name][callback] = nil;
		end
	end
end


local function GetDBValue(dbKey)
	return DB[dbKey]
end
addon.GetDBValue = GetDBValue;

local function SetDBValue(dbKey, value, userInput)
	if DB then
		DB[dbKey] = value;
		addon.CallbackRegistry:Trigger("SettingChanged."..dbKey, value, userInput);
	end
end
addon.SetDBValue = SetDBValue;

local function GetDBBool(dbKey)
	return DB[dbKey] == true
end
addon.GetDBBool = GetDBBool;

local function FlipDBBool(dbKey)
	SetDBValue(dbKey, not GetDBBool(dbKey), true);
end
addon.FlipDBBool = FlipDBBool;


local function GetPersonalData(dbKey)
	--From SavedVariablesPerCharacter
	return DB_PC[dbKey]
end
addon.GetPersonalData = GetPersonalData;

local function SetPersonalData(dbKey, value, userInput)
	DB_PC[dbKey] = value;
end
addon.SetPersonalData = SetPersonalData;

local function GetValidOptionChoice(optionsTbl, dbKey)
	local index = GetDBValue(dbKey);
	if not (index and optionsTbl[index]) then
		index = 1;
	end
	return optionsTbl[index]
end
addon.GetValidOptionChoice = GetValidOptionChoice;


local DefaultValues = {
	AutoJoinEvents = true,
	BackpackItemTracker = true,
		HideZeroCountItem = true,
		ConciseTokenTooltip = true,
		TrackItemUpgradeCurrency = true,
		TrackHolidayItem = true,
		TrackerBarInsideSeparateBag = false,
	GossipFrameMedal = true,
	EmeraldBountySeedList = true,       --Show a list of Dreamseed when appoaching Emarad Bounty Soil
	WorldMapPinSeedPlanting = true,     --Aditional Map Pin: Dreamseed
	AlternativePlayerChoiceUI = true,   --Revamp PlayerChoiceFrame for Dreamseed Nurturing
	HandyLockpick = true,               --Right-click to lockpick inventory items (Rogue/Mechagnome)
	Technoscryers = true,               --Show Technoscryers on QuickSlot (Azerothian Archives World Quest)
	PlayerChoiceFrameToken = true,      --Add owned token count to PlayerChoiceFrame
	ExpansionLandingPage = true,        --Display extra info on the ExpansionLandingPage
	Delves_Dashboard = true,            --Show Great Vault Progress on DelvesDashboardFrame
	Delves_SeasonProgress = true,       --Display Seaonal Journey changes on a progress bar
	Delves_Automation = true,           --Auto select enhancement (PlayerChoiceFrame)
	WoWAnniversary = true,              --QuickSlot for Mount Maniac Event
		VotingResultsExpanded = true,
	BlizzFixFishingArtifact = true,     --Fix Fishing Artifact Traits Not Showing bug
	QuestItemDestroyAlert = true,       --Show related quest info when destroying a quest-starting item
	SpellcastingInfo = false,           --Show the spell info when hovering over target/focus cast bars. Logging target spells and displayed it on UnitPopupMenu
	ChatOptions = true,                 --Add Leave button to Channel Context Menu
	NameplateWidget = true,             --Show required items on nameplate widget set
	PartyInviterInfo = false,           --Show the inviter's level and class
		PartyInviter_Race = false,
		PartyInviter_Faction = false,
	PlayerTitleUI = false,              --Add search box and filter to TitleManagerPane
	Plunderstore = true,
		Plunderstore_HideCollected = true,
	BlizzardSuperTrack = false,         --Add timer to the SuperTrackedFrame when tracking a POI with time format
	ProfessionsBook = true,             --Show unspent points on ProfessionsBookFrame
	EditModeShowPlumberUI = true,
	SoftTargetName = false,             --Show object's name on SoftTargetFrame
		SoftTarget_TextOutline = false,
		SoftTarget_FontSize = 2,
		SoftTarget_IconSize = 2,
		SoftTarget_CastBar = true,
		SoftTarget_Objectives = false,
		SoftTarget_House_HideIcon = false,
		SoftTarget_House_HideName = false,
	AppearanceTab = false,              --Adjust Appearance Tab models to reduce GPU usage spike
		AppearanceTab_ModelCount = 1,
	ItemUpgradeUI = true,
	HolidayDungeon = true,              --Auto select holiday dungeons once
	StaticPopup_Confirm = true,         --Add a brief delay to purchase non-refundable items / Reduce item conversion confirm delay
	QueueStatus = false,                --Add a progress bar to LFG Eye
		QueueStatus_ShowTime = false,   --Relative Queue Time
		QueueStatus_TextPosition = 1,   --0:Center, 1-4:Clockwise
	InstanceDifficulty = false,         --Instance Difficulty Selector
	TransmogChatCommand = false,        --Adjust /outfit command behavior
	CraftSearchExtended = false,        --Show more search result, custom keywords
	SourceAchievementLink = true,       --Make Achievement name in MountJournal, DecorCatalog interactable
	TransmogOutfitSelect = true,        --Show Minimized Transmog Outfit Collection
	CatalystUI = true,					--Allow Ctrl-Click to preview items in Dressing Room for UIs that don't natively support this action
	HuntTable = true,					--Replace generic quest icons with difficulties and add achievement indicators.
	PreyQuestSuperTrack = true,			--During the final stage, clicking the Prey widget also super track the target location.


	--Tooltip
	TooltipChestKeys = true,            --Show keys that unlocked the current chest or door
	TooltipRepTokens = true,            --Show faction info for items that grant rep
	TooltipSnapdragonTreats = true,     --Show info on Snapdragon Treats (An item that changes this mount's color)
	TooltipItemReagents = false,        --For items with "use to combine": show the reagent count
	TooltipProfessionKnowledge = true,  --Show unspent points on GameTooltip
	TooltipDelvesItem = true,           --Show weekly Coffer Key cap on chest tooltip
	TooltipItemQuest = true,            --Show the quest of quest starting items in bags
	TooltipTransmogEnsemble = true,     --A Raid Ensemble now unlocks outfits (tints) from 4 difficulties, but the default UI only gives one
	TooltipRichSoil = true,             --Show QuickSlot for seeds when double click on Rich Soil (Midnight Herbalism)


	--Reduction
	BossBanner_MasterSwitch = false,
		BossBanner_HideLootWhenSolo = true,
		BossBanner_ValuableItemOnly = true,


	--New Expansion Landing Page
	NewExpansionLandingPage = true,
		LandingPage_Activity_HideCompleted = true,
		LandingPage_Raid_CollapsedAchievement = false,
		LandingPage_AdvancedTooltip = true,

		LandingButton_ShowButton = true,
		LandingButton_Unaffected = false,
		LandingButton_PrimaryUI = 1,
		LandingButton_SmartExpansion = false,
		LandingButton_ReduceSize = false,
		LandingButton_DarkColor = false,
		LandingButton_HideWhenIdle = false,
		--LandingButton_UseLibDBIcon = nil,     --Addon-dependant. Init on first load
		LandingButton_UseLibDBIcon_NoBorder = true, --Remove the golden button border if supported

		--LandingButton_Pos_X, LandingButton_Pos_Y


	--Custom Loot Window
	LootUI = false,
		LootUI_FontSize = 12,
		LootUI_FadeDelayPerItem = 0.25,
		LootUI_ItemsPerPage = 6,
		LootUI_BackgroundAlpha = 0.5,
		LootUI_ShowItemCount = false,
		LootUI_NewTransmogIcon = true,
		LootUI_UseCustomColor = false,
		LootUI_GrowUpwards = false,
		LootUI_WindowHide = false,
		LootUI_CombineItems = false,
		LootUI_LowFrameStrata = false,
		LootUI_HideTitle = false,
		LootUI_ShowReputation = false,
		LootUI_ShowAllMoneyChange = false,
		LootUI_ShowAllCurrencyChange = false,
		LootUI_ReplaceDefaultAlert = false,
		LootUI_ForceAutoLoot = true,
		LootUI_LootUnderMouse = false,
		LootUI_UseHotkey = true,
		LootUI_HotkeyName = "E",
		LootUI_UseStockUI = false,


	--Unified Map Pin System
	WorldMapPin_TWW = true,             --Master Switch for TWW Map Pins
		WorldMapPin_Size = 1,           --1: Default
		WorldMapPin_TWW_Delve = true,   --Show Bountiful Delves on continent map
		WorldMapPin_TWW_Quest = true,   --Show Special Assignment on continent map
		WorldMapPin_PlayerPing = true,


	--Modify default interface behavior:
	BlizzFixEventToast = true,          --Make Toast non-interactable
	MerchantPrice = false,              --Merchant Price (Alt Currency) Overview, gray insufficient items


	--In-game Navigation: Use waypoint (Super Tracking) to navigate players. Generally default to false, since it will mute WoW's own SuperTrackedFrame
	Navigator_MasterSwitch = true,      --Decide if using our SuperTrackedFrame or the default one
		Navigator_Dreamseed = false,


	--Talking Head Revamp
	TalkingHead_MasterSwitch = false,
		TalkingHead_FontSize = 100,         --% Multiply default QuestFont Height
		TalkingHead_InstantText = false,
		TalkingHead_TextOutline = false,
		TalkingHead_HideInInstance = false,
		TalkingHead_HideWorldQuest = false,
		TalkingHead_BelowWorldMap = false,


	--QuickSlot
		QuickSlotHighContrastMode = false,
		quickslotFromRadian = 0,


	--SpellFlyout DrawerMacro
		SpellFlyout_CloseAfterClick = true,
		SpellFlyout_SingleRow = false,
		SpellFlyout_HideUnusable = false,
		SpellFlyout_UpdateFrequently = false,


	--LegionRemix
	LegionRemix = true,
		LegionRemix_TraitSubIconStyle = 3,  --1:OFF, 2:Mini Icon, 3:Replace Icon
		LegionRemix_AutoUpgrade = true,
		LegionRemix_PaperDollTraitDetail = false,
	LegionRemix_HideWorldTier = true,
	LegionRemix_LFGSpam = true,


	EnableNewByDefault = false,             --Always enable newly added features
	SettingsPanel_AutoShowChangelog = false,
	SettingsPanel_ChangelogFontSize = 1,


	--Housing
	DecorModelScaleRef = true,
		DecorModelScaleRef_ShowBanana = false,
	Housing_Macro = true,
	Housing_DecorHover = true,
		Housing_DecorHover_EnableDupe = true,
		Housing_DecorHover_DuplicateKey = 2,    --1:Ctrl, 2:Alt
	Housing_CustomizeMode = true,
	Housing_Clock = true,
		Housing_Clock_AnalogClock = true,
	TooltipDyeDeez = true,                  --Show dyes on pigment tooltip
	Housing_CatalogSearch = false,
	Housing_ItemAcquiredAlert = true,       --Click AlertFrame to view decor model


	--Namaplte: Quest Indicator
	NameplateQuest = false,
		NameplateQuest_IconSize = 2,
		NameplateQuest_ShowPartyQuest = false,
		NameplateQuest_ShowTargetProgress = false,
		NameplateQuest_ShowProgressOnHover = false,
		NameplateQuest_ShowProgressOnKeyPress = false,
			NameplateQuest_ShowProgressModifierKey = "ALT",
		NameplateQuest_WidgetOffsetX = 0,
		NameplateQuest_WidgetOffsetY = 0,
		NameplateQuest_ProgressFormat = 1,
		NameplateQuest_ProgressShowIcon = false,
		NameplateQuest_TextOutline = true,
		--NameplateQuest_Side = "RIGHT",    --Initial value dedfined by detecting addon


	--Break Time Reminder
	BreakTime = false,
		BreakTime_Cycle = 30,
		BreakTime_Rest = 5,
		BreakTime_Delay = 5,
		BreakTime_FlashTaskbar = false,
		BreakTime_DNDCombat = true,
		BreakTime_DNDInstances = false,


	--Declared elsewhere:
		--DreamseedChestABTesting = math.random(100) >= 50


	--Deprecated:
	--DruidModelFix = true,                 --Fixed by Blizzard in 10.2.0
	--BlizzFixWardrobeTrackingTip = true,   --Hide Wardrobe tip that cannot be disabled   --Tip removed by Blizzard
	--MinimapMouseover = false,             --Ridden with compatibility issue
	--LandingPageSwitch = true,             --Right click on ExpansionLandingPageMinimapButton to open a menu to access mission report  --Merged into NewExpansionLandingPage
};


local NeverEnableByDefault = {
	AppearanceTab = true,
	NameplateQuest = true,
	BreakTime = true,
};


local function LoadDatabase()
	PlumberDB = PlumberDB or {};
	PlumberStorage = PlumberStorage or {};
	PlumberDB_PC = PlumberDB_PC or {};

	DB = PlumberDB;
	DB_PC = PlumberDB_PC;

	local alwaysEnableNew = DB.EnableNewByDefault or false;
	local newDBKeys = {};

	for dbKey, value in pairs(DefaultValues) do
		if DB[dbKey] == nil then
			DB[dbKey] = value;
			if alwaysEnableNew and type(value) == "boolean" and not NeverEnableByDefault[dbKey] then
				--Not all Booleans are the master switch of individual module
				--Send these new ones to ControlCenter
				--Test: /run PlumberDB = {EnableNewByDefault = true}
				newDBKeys[dbKey] = true;
			end
		end
	end

	for dbKey, value in pairs(DB) do
		CallbackRegistry:Trigger("SettingChanged."..dbKey, value);
	end

	if not DB.installTime or type(DB.installTime) ~= "number" then
		DB.installTime = VERSION_DATE;
	end

	if DB.lastVersionTime then
		if (VERSION_DATE - 1 > DB.lastVersionTime) and DB.SettingsPanel_AutoShowChangelog then
			CallbackRegistry:Trigger("ShowChangelog");
		end
	end
	DB.lastVersionTime = VERSION_DATE;

	DefaultValues = nil;

	CallbackRegistry:Trigger("NewDBKeysAdded", newDBKeys);
	CallbackRegistry:Trigger("DBLoaded", DB);


	PlumberStorage.CreatureSpells = nil;    --Store SpellcastingInfo, retired in  Midnight
end


EL:RegisterEvent("ADDON_LOADED");
EL:RegisterEvent("PLAYER_ENTERING_WORLD");
EL:RegisterEvent("PLAYER_LOGOUT");
EL:RegisterEvent("LOADING_SCREEN_DISABLED");

EL:SetScript("OnEvent", function(self, event, ...)
	if event == "ADDON_LOADED" then
		local name = ...
		if name == addonName then
			self.plumberLoaded = true;
			self:UnregisterEvent(event);
			LoadDatabase();
		elseif self.plumberLoaded then
			if CallbackRegistry.addonLoadedCallbacks[name] then
				local tbl = CallbackRegistry.addonLoadedCallbacks[name];
				CallbackRegistry.addonLoadedCallbacks[name] = nil;
				for callback in pairs(tbl) do
					callback();
				end
			end
		end
	elseif event == "PLAYER_ENTERING_WORLD" then
		self:UnregisterEvent(event);
		local seasonID = API.GetTimerunningSeason();
		if seasonID and seasonID > 0 then
			CallbackRegistry:Trigger("TimerunningSeason", seasonID);
		end
		addon.ControlCenter:InitializeModules();


		local isInitialLogin = ...

		local lastLoginTime = time();

		if type(DB.lastLogoutTime) ~= "number" then
			DB.lastLogoutTime = nil;
		end

		if type(DB.lastLoginTime) ~= "number" then
			DB.lastLoginTime = lastLoginTime;
		end

		if (not DB.lastLogoutTime) or (lastLoginTime > DB.lastLogoutTime + 600) then
			--If the player didn't log in for 10 min, consider it as a new game session
			DB.lastLoginTime = lastLoginTime;
			CallbackRegistry:Trigger("NewGameSessionBegin");
		end

	elseif event == "PLAYER_LOGOUT" then
		DB.lastLogoutTime = time();

	elseif event == "LOADING_SCREEN_DISABLED" then
		self:QueueEvent(event);
	end
end);

function EL:OnUpdate(elapsed)
	self.t = self.t + elapsed;
	if self.t > 2 then
		self.t = nil;
		self:SetScript("OnUpdate", nil);
		if self.eventQueue then
			local tbl = self.eventQueue;
			self.eventQueue = nil;
			for event in pairs(tbl) do
				CallbackRegistry:Trigger(event);
			end
		end
	end
end

function EL:QueueEvent(event)
	if not self.eventQueue then
		self.eventQueue = {};
	end
	self.eventQueue[event] = true;
	self.t = 0;
	self:SetScript("OnUpdate", self.OnUpdate);
end


do
	local tocVersion = select(4, GetBuildInfo());
	tocVersion = tonumber(tocVersion or 0);

	local function IsToCVersionEqualOrNewerThan(targetVersion)
		return tocVersion >= targetVersion
	end
	addon.IsToCVersionEqualOrNewerThan = IsToCVersionEqualOrNewerThan;

	addon.IS_MIDNIGHT = IsToCVersionEqualOrNewerThan(120000);

	addon.IS_CLASSIC = C_AddOns.GetAddOnMetadata(addonName, "X-Flavor") ~= "retail";

	addon.IS_MOP = C_AddOns.GetAddOnMetadata(addonName, "X-Expansion") == "MOP";


	function addon.GetLastLoginTime()
		return DB.lastLoginTime or time()
	end
end
