-- CORE UTILITY METHODS
function Icon:set(settingName, value, toggleState, setAdditional)
	local settingDetail = self._settingsDictionary[settingName]
	assert(settingDetail ~= nil, ("setting '%s' does not exist"):format(settingName))

	-- Update the settings value
	if type(toggleState) == "string" then
		toggleState = toggleState:lower()
	end
	local previousValue = self:get(settingName, toggleState)
	local settingType = settingDetail.type
	if settingType == "toggleable" then
		local valuesToSet = {}
		if toggleState == "deselected" or toggleState == "selected" then
			table.insert(valuesToSet, toggleState)
		else
			table.insert(valuesToSet, "deselected")
			table.insert(valuesToSet, "selected")
			toggleState = nil
		end
		for i, v in pairs(valuesToSet) do
			settingDetail.values[v] = value
			if setAdditional ~= "_ignorePrevious" then
				settingDetail.additionalValues["previous_"..v] = previousValue
			end
			if type(setAdditional) == "string" then
				settingDetail.additionalValues[setAdditional.."_"..v] = previousValue
			end
		end
	else
		settingDetail.value = value
		if type(setAdditional) == "string" then
			if setAdditional ~= "_ignorePrevious" then
				settingDetail.additionalValues["previous"] = previousValue
			end
			settingDetail.additionalValues[setAdditional] = previousValue
		end
	end

	-- Check previous and new are not the same
	if previousValue == value then
		return self, "Value was already set"
	end

	-- Update appearances of associated instances
	local currentToggleState = self:getToggleState()
	if not self._updateAfterSettingAll and settingDetail.instanceNames and (currentToggleState == toggleState or toggleState == nil) then
		self:_update(settingName, currentToggleState, true)
	end

	-- Call any methods present
	if settingDetail.callMethods then
		for _, callMethod in pairs(settingDetail.callMethods) do
			callMethod(self, value, toggleState)
		end
	end
	
	-- Call any signals present
	if settingDetail.callSignals then
		for _, callSignal in pairs(settingDetail.callSignals) do
			callSignal:Fire()
		end
	end
	return self
end

function Icon:get(settingName, toggleState, getAdditional)
	local settingDetail = self._settingsDictionary[settingName]
	assert(settingDetail ~= nil, ("setting '%s' does not exist"):format(settingName))
	local settingType = settingDetail.type
	if settingType == "toggleable" then
		toggleState = toggleState or self:getToggleState()
		local additionalValue = type(getAdditional) == "string" and settingDetail.additionalValues[getAdditional.."_"..toggleState]
		return settingDetail.values[toggleState], additionalValue
	end
	local additionalValue = type(getAdditional) == "string" and settingDetail.additionalValues[getAdditional]
	return settingDetail.value, additionalValue
end

function Icon:getToggleState(isSelected)
	isSelected = isSelected or self.isSelected
	return (isSelected and "selected") or "deselected"
end

function Icon:_update(settingName, toggleState, applyInstantly)
	local settingDetail = self._settingsDictionary[settingName]
	assert(settingDetail ~= nil, ("setting '%s' does not exist"):format(settingName))
	toggleState = toggleState or self:getToggleState()
	local value = settingDetail.value or (settingDetail.values and settingDetail.values[toggleState])
	if value == nil then return end
	local tweenInfo = (applyInstantly and TweenInfo.new(0)) or self._settings.action.toggleTransitionInfo.value
	local propertyName = settingDetail.propertyName
	local invalidPropertiesTypes = {
		["string"] = true,
		["NumberSequence"] = true,
		["Text"] = true,
		["EnumItem"] = true,
		["ColorSequence"] = true,
	}
	local uniqueSetting = self._uniqueSettingsDictionary[settingName]
	for _, instanceName in pairs(settingDetail.instanceNames) do
		local instance = self.instances[instanceName]
		local propertyType = typeof(instance[propertyName])
		local cannotTweenProperty = invalidPropertiesTypes[propertyType]
		if uniqueSetting then
			uniqueSetting(settingName, instance, propertyName, value)
		elseif cannotTweenProperty then
			instance[propertyName] = value
		else
			tweenService:Create(instance, tweenInfo, {[propertyName] = value}):Play()
		end
		--
		if settingName == "iconSize" and instance[propertyName] ~= value then
			self.updated:Fire()
		end
		--
	end
end

function Icon:_updateAll(toggleState, applyInstantly)
	for settingName, settingDetail in pairs(self._settingsDictionary) do
		if settingDetail.instanceNames then
			self:_update(settingName, toggleState, applyInstantly)
		end
	end
end

function Icon:_updateStateOverlay(transparency, color)
	local stateOverlay = self.instances.iconOverlay
	stateOverlay.BackgroundTransparency = transparency or 1
	stateOverlay.BackgroundColor3 = color or Color3.new(1, 1, 1)
end

function Icon:setTheme(theme, updateAfterSettingAll)
	self._updateAfterSettingAll = updateAfterSettingAll
	for settingsType, settingsDetails in pairs(theme) do
		if settingsType == "toggleable" then
			for settingName, settingValue in pairs(settingsDetails.deselected) do
				self:set(settingName, settingValue, "both")
			end
			for settingName, settingValue in pairs(settingsDetails.selected) do
				self:set(settingName, settingValue, "selected")
			end
		else
			for settingName, settingValue in pairs(settingsDetails) do
				self:set(settingName, settingValue)
			end
		end
	end
	self._updateAfterSettingAll = nil
	if updateAfterSettingAll then
		self:_updateAll()
	end
	return self
end

function Icon:setEnabled(bool)
	self.enabled = bool
	self.instances.iconContainer.Visible = bool
	self.updated:Fire()
	return self
end

function Icon:setName(string)
	self.name = string
	self.instances.iconContainer.Name = string
	return self
end

function Icon:_playClickSound()
	local clickSound = self.instances.clickSound
	if clickSound.SoundId ~= nil and #clickSound.SoundId > 0 and clickSound.Volume > 0 then
		local clickSoundCopy = clickSound:Clone()
		clickSoundCopy.Parent = clickSound.Parent
		clickSoundCopy:Play()
		debris:AddItem(clickSoundCopy, clickSound.TimeLength)
	end
end

function Icon:select(byIcon)
	if self.locked then return self end
	self.isSelected = true
	self:_setToggleItemsVisible(true, byIcon)
	self:_updateNotice()
	self:_updateAll()
	self:_playClickSound()
	if #self.dropdownIcons > 0 or #self.menuIcons > 0 then
		IconController:_updateSelectionGroup()
	end
    self.selected:Fire()
    self.toggled:Fire(self.isSelected)
	return self
end

function Icon:deselect(byIcon)
	if self.locked then return self end
	self.isSelected = false
	self:_setToggleItemsVisible(false, byIcon)
	self:_updateNotice()
	self:_updateAll()
	self:_playClickSound()
	if #self.dropdownIcons > 0 or #self.menuIcons > 0 then
		IconController:_updateSelectionGroup()
	end
    self.deselected:Fire()
    self.toggled:Fire(self.isSelected)
	return self
end

function Icon:notify(clearNoticeEvent, noticeId)
	coroutine.wrap(function()
		if not clearNoticeEvent then
			clearNoticeEvent = self.deselected
		end
		if self._parentIcon then
			self._parentIcon:notify(clearNoticeEvent)
		end
		
		local notifComplete = Signal.new()
		local endEvent = self._endNotices:Connect(function()
			notifComplete:Fire()
		end)
		local customEvent = clearNoticeEvent:Connect(function()
			notifComplete:Fire()
		end)
		
		noticeId = noticeId or httpService:GenerateGUID(true)
		self.notices[noticeId] = {
			completeSignal = notifComplete,
			clearNoticeEvent = clearNoticeEvent,
		}
		self.totalNotices += 1
		self:_updateNotice()

		self.notified:Fire(noticeId)
		notifComplete:Wait()
		
		endEvent:Disconnect()
		customEvent:Disconnect()
		notifComplete:Disconnect()
		
		self.totalNotices -= 1
		self.notices[noticeId] = nil
		self:_updateNotice()
	end)()
	return self
end

function Icon:_updateNotice()
	local enabled = true
	if self.totalNotices < 1 then
		enabled = false
	end
	-- Deselect
	if not self.isSelected then
		if (#self.dropdownIcons > 0 or #self.menuIcons > 0) and self.totalNotices > 0 then
			enabled = true
		end
	end
	-- Select
	if self.isSelected then
		if #self.dropdownIcons > 0 or #self.menuIcons > 0 then
			enabled = false
		end
	end
	local value = (enabled and 0) or 1
	self:set("noticeImageTransparency", value)
	self:set("noticeTextTransparency", value)
	self.instances.noticeLabel.Text = (self.totalNotices < 100 and self.totalNotices) or "99+"
end

function Icon:clearNotices()
	self._endNotices:Fire()
	return self
end

function Icon:disableStateOverlay(bool)
	if bool == nil then
		bool = true
	end
	local stateOverlay = self.instances.iconOverlay
	stateOverlay.Visible = not bool
	return self
end