-- ------------------------------------------------------------------------------ --
--                                TradeSkillMaster                                --
--                          https://tradeskillmaster.com                          --
--    All Rights Reserved - Detailed license information included with addon.     --
-- ------------------------------------------------------------------------------ --

local _, TSM = ...
local Operations = TSM.MainUI:NewPackage("Operations")
local L = TSM.Include("Locale").GetTable()
local Log = TSM.Include("Util.Log")
local Theme = TSM.Include("Util.Theme")
local Money = TSM.Include("Util.Money")
local TempTable = TSM.Include("Util.TempTable")
local Settings = TSM.Include("Service.Settings")
local UIElements = TSM.Include("UI.UIElements")
local private = {
	settings = nil,
	moduleNames = {},
	moduleCollapsed = {},
	moduleCallbacks = {},
	currentModule = nil,
	currentOperationName = nil,
	playerList = {},
	linkMenuEntries = {},
}



-- ============================================================================
-- Module Functions
-- ============================================================================

function Operations.OnInitialize()
	private.settings = Settings.NewView()
		:AddKey("global", "mainUIContext", "operationsDividedContainer")
		:AddKey("global", "mainUIContext", "operationsSummaryScrollingTable")
	TSM.MainUI.RegisterTopLevelPage(L["Operations"], private.GetOperationsFrame)
end

function Operations.RegisterModule(name, callback)
	tinsert(private.moduleNames, name)
	private.moduleCallbacks[name] = callback
end

function Operations.ShowOperationSettings(baseFrame, moduleName, operationName)
	baseFrame:SetSelectedNavButton(L["Operations"], true)
	baseFrame:GetElement("content.operations.selection.operationTree"):SetSelectedOperation(moduleName, operationName)
end

function Operations.GetOperationManagementElements(moduleName, operationName)
	local operation = TSM.Operations.GetSettings(private.currentModule, private.currentOperationName)
	wipe(private.playerList)
	for factionrealm in TSM.db:GetConnectedRealmIterator("factionrealm") do
		for _, character in TSM.db:FactionrealmCharacterIterator(factionrealm) do
			tinsert(private.playerList, character.." - "..factionrealm)
		end
	end
	return UIElements.New("Frame", "management")
		:SetLayout("VERTICAL")
		:AddChild(Operations.CreateExpandableSection(moduleName, "managementOptions", L["Management Options"], L["Below you can ignore this operation on certain characters or realms."])
			:AddChild(Operations.CreateSettingLine("ignoreFactionRealms", L["Ignore operation on faction-realms"])
				:SetLayout("VERTICAL")
				:SetHeight(48)
				:SetMargin(0, 0, 0, 12)
				:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
					:SetHeight(24)
					:SetItems(TSM.db:GetScopeKeys("factionrealm"), TSM.db:GetScopeKeys("factionrealm"))
					:SetSelectionText(L["No Faction-Realms"], L["%d Faction-Realms"], L["All Faction-Realms"])
					:SetSettingInfo(operation, "ignoreFactionrealm")
				)
			)
			:AddChild(Operations.CreateSettingLine("ignoreCharacters", L["Ignore operation on characters"])
				:SetLayout("VERTICAL")
				:SetHeight(48)
				:AddChild(UIElements.New("MultiselectionDropdown", "dropdown")
					:SetHeight(24)
					:SetItems(private.playerList, private.playerList)
					:SetSelectionText(L["No Characters"], L["%d Characters"], L["All Characters"])
					:SetSettingInfo(operation, "ignorePlayer")
				)
			)
		)
		:AddChild(Operations.CreateExpandableSection(moduleName, "groupManagement", L["Group Management"], L["Here you can add/remove what groups this operation is attached to."])
			:AddChild(Operations.CreateSettingLine("applyNewGroup", L["Apply operation to group"])
				:SetLayout("VERTICAL")
				:SetHeight(48)
				:AddChild(UIElements.New("GroupSelector", "group")
					:SetHintText(L["Add operation to groups"])
					:SetScript("OnSelectionChanged", private.GroupSelectionChanged)
				)
			)
			:AddChildrenWithFunction(private.AddOperationGroups)
		)
end

function Operations.CreateExpandableSection(moduleName, id, text, description)
	return UIElements.New("CollapsibleContainer", id)
		:SetLayout("VERTICAL")
		:SetMargin(0, 0, 0, 8)
		:SetContextTable(private.moduleCollapsed, moduleName..text)
		:SetHeadingText(text)
		:AddChild(UIElements.New("Text", "description")
			:SetHeight(20)
			:SetMargin(0, 0, 0, 12)
			:SetFont("BODY_BODY3")
			:SetText(description)
		)
end

function Operations.CreateLinkedSettingLine(settingKey, labelText, disabled, alternateName)
	local relationshipSet = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
	return UIElements.New("Frame", alternateName or settingKey)
		:SetLayout("HORIZONTAL")
		:SetHeight(24)
		:SetMargin(0, 0, 0, 4)
		:AddChild(UIElements.New("Frame", "line")
			:SetLayout("HORIZONTAL")
			:AddChild(UIElements.New("Text", "label")
				:SetWidth("AUTO")
				:SetFont("BODY_BODY2_MEDIUM")
				:SetTextColor((relationshipSet or disabled) and "TEXT_DISABLED" or "TEXT")
				:SetText(labelText)
			)
			:AddChild(private.CreateLinkButton(disabled, settingKey))
			:AddChild(UIElements.New("Spacer", "spacer"))
		)
end

function Operations.CreateSettingLine(id, labelText, disabled)
	return UIElements.New("Frame", id)
		:SetLayout("HORIZONTAL")
		:SetHeight(24)
		:SetMargin(0, 0, 0, 4)
		:AddChild(UIElements.New("Text", "label")
			:SetFont("BODY_BODY2_MEDIUM")
			:SetTextColor(disabled and "TEXT_DISABLED" or "TEXT")
			:SetText(labelText)
		)
end

function Operations.CreateLinkedPriceInput(settingKey, label, height, validate, defaultValue)
	local isDisabled = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
	local operation = TSM.Operations.GetSettings(private.currentModule, private.currentOperationName)
	local value = operation[settingKey]
	if defaultValue ~= nil and (not value or value == "") then
		isDisabled = true
		value = defaultValue
	end
	local validateFunc, validateContext = nil, nil
	if type(validate) == "table" then
		validateFunc = "CUSTOM_PRICE"
		validateContext = validate
	elseif type(validate) == "function" then
		validateFunc = validate
	elseif validate == nil then
		validateFunc = "CUSTOM_PRICE"
	else
		error("Invalid validate: "..tostring(validate))
	end
	return Operations.CreateLinkedSettingLine(settingKey, label)
		:SetLayout("VERTICAL")
		:SetHeight(height)
		:AddChild(UIElements.New("MultiLineInput", "input")
			:SetHeight(height - 24)
			:SetDisabled(isDisabled)
			:SetValidateFunc(validateFunc, validateContext)
			:SetSettingInfo(operation, settingKey)
			:SetValue(Money.ToString(value) or value)
		)
end



-- ============================================================================
-- Operations UI
-- ============================================================================

function private.GetOperationsFrame()
	TSM.UI.AnalyticsRecordPathChange("main", "operations")
	local frame = UIElements.New("DividedContainer", "operations")
		:SetSettingsContext(private.settings, "operationsDividedContainer")
		:SetMinWidth(250, 250)
		:SetLeftChild(UIElements.New("Frame", "selection")
			:SetLayout("VERTICAL")
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("Input", "search")
				:SetHeight(24)
				:SetMargin(8, 8, 8, 16)
				:SetIconTexture("iconPack.18x18/Search")
				:SetClearButtonEnabled(true)
				:AllowItemInsert(true)
				:SetHintText(L["Search Operations"])
				:SetScript("OnValueChanged", private.OperationSearchOnValueChanged)
			)
			:AddChild(UIElements.New("OperationTree", "operationTree")
				:SetScript("OnOperationAdded", private.OperationTreeOnOperationAdded)
				:SetScript("OnOperationDeleted", private.OperationTreeOnOperationConfirmDelete)
				:SetScript("OnOperationSelected", private.OperationTreeOnOperationSelected)
			)
		)
		:SetRightChild(UIElements.New("ViewContainer", "content")
			:SetNavCallback(private.GetOperationsContent)
			:AddPath("none", true)
			:AddPath("summary")
			:AddPath("operation")
		)
	return frame
end

function private.GetOperationsContent(_, path)
	if path == "none" then
		return UIElements.New("Frame", "settings")
			:SetLayout("VERTICAL")
			:SetWidth("EXPAND")
			:SetBackgroundColor("PRIMARY_BG")
			:AddChild(UIElements.New("Frame", "title")
				:SetLayout("HORIZONTAL")
				:SetHeight(40)
				:SetPadding(8)
				:AddChild(UIElements.New("Texture", "icon")
					:SetMargin(0, 8, 0, 0)
					:SetTextureAndSize(TSM.UI.TexturePacks.GetColoredKey("iconPack.18x18/Operation", "TEXT"))
				)
				:AddChild(UIElements.New("Text", "text")
					:SetFont("BODY_BODY1_BOLD")
					:SetText(L["No Operation Selected"])
				)
			)
			:AddChild(UIElements.New("Spacer", "spacer"))
	elseif path == "summary" then
		return UIElements.New("Frame", "settings")
			:SetLayout("VERTICAL")
			:SetWidth("EXPAND")
			:SetBackgroundColor("PRIMARY_BG")
			:AddChild(UIElements.New("Frame", "title")
				:SetLayout("HORIZONTAL")
				:SetHeight(40)
				:SetPadding(8)
				:AddChild(UIElements.New("Text", "text")
					:SetWidth("AUTO")
					:SetFont("BODY_BODY1_BOLD")
				)
				:AddChild(UIElements.New("Spacer"))
				:AddChild(UIElements.New("Button", "addBtn")
					:SetWidth("AUTO")
					:SetMargin(12, 12, 0, 0)
					:SetFont("BODY_BODY2_MEDIUM")
					:SetIcon("iconPack.14x14/Add/Circle", "LEFT")
					:SetText(L["Create New"])
					:SetScript("OnClick", private.CreateNewOperationOnClick)
				)
			)
			:AddChild(UIElements.New("Frame", "content")
				:SetLayout("VERTICAL")
				-- will be filled in by the operation selection callback
			)
	elseif path == "operation" then
		return UIElements.New("Frame", "settings")
			:SetLayout("VERTICAL")
			:SetWidth("EXPAND")
			:SetBackgroundColor("PRIMARY_BG")
			:AddChild(UIElements.New("Frame", "title")
				:SetLayout("HORIZONTAL")
				:SetHeight(40)
				:SetPadding(8)
				:AddChild(UIElements.New("Texture", "icon")
					:SetMargin(0, 8, 0, 0)
					:SetTextureAndSize(TSM.UI.TexturePacks.GetColoredKey("iconPack.18x18/Operation", "TEXT"))
				)
				:AddChild(UIElements.New("EditableText", "text")
					:SetWidth("AUTO")
					:AllowItemInsert(true)
					:SetFont("BODY_BODY1_BOLD")
					:SetText(L["No Operation Selected"])
					:SetScript("OnValueChanged", private.OperationNameChanged)
					:SetScript("OnEditingChanged", private.NameOnEditingChanged)
				)
				:AddChild(UIElements.New("Spacer"))
				:AddChild(UIElements.New("Button", "renameBtn")
					:SetWidth("AUTO")
					:SetMargin(12, 12, 0, 0)
					:SetFont("BODY_BODY2_MEDIUM")
					:SetIcon("iconPack.14x14/Edit", "LEFT")
					:SetText(L["Rename"])
					:SetScript("OnClick", private.RenameOperationOnClick)
				)
				:AddChild(UIElements.New("Button", "resetBtn")
					:SetWidth("AUTO")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2_MEDIUM")
					:SetIcon("iconPack.14x14/Reset", "LEFT")
					:SetText(L["Reset"])
					:SetScript("OnClick", private.ResetOperationOnClick)
				)
			)
			:AddChild(UIElements.New("Frame", "content")
				:SetLayout("VERTICAL")
				-- will be filled in by the operation selection callback
			)
	else
		error("Invalid path: "..tostring(path))
	end
end

function private.GetSummaryContent()
	local query = TSM.Operations.CreateQuery()
		:Equal("moduleName", private.currentModule)
		:VirtualField("numGroups", "number", private.NumGroupsVirtualField)
		:VirtualField("numItems", "number", private.NumItemsVirtualField)
		:OrderBy("operationName", true)
	local mostGroupsName, mostGroupsValue = "---", -math.huge
	local leastGroupsName, leastGroupsValue = "---", math.huge
	local mostItemsName, mostItemsValue = "---", -math.huge
	local leastItemsName, leastItemsValue = "---", math.huge
	for _, row in query:Iterator() do
		local operationName, numGroups, numItems = row:GetFields("operationName", "numGroups", "numItems")
		if numGroups > mostGroupsValue then
			mostGroupsValue = numGroups
			mostGroupsName = operationName
		end
		if numGroups < leastGroupsValue then
			leastGroupsValue = numGroups
			leastGroupsName = operationName
		end
		if numItems > mostItemsValue then
			mostItemsValue = numItems
			mostItemsName = operationName
		end
		if numItems < leastItemsValue then
			leastItemsValue = numItems
			leastItemsName = operationName
		end
	end
	return UIElements.New("Frame", "summary")
		:SetLayout("VERTICAL")
		:SetBackgroundColor("PRIMARY_BG")
		:AddChild(UIElements.New("Frame", "summary")
			:SetLayout("HORIZONTAL")
			:SetHeight(48)
			:SetMargin(8, 8, 0, 16)
			:SetBackgroundColor("PRIMARY_BG_ALT", true)
			:AddChild(UIElements.New("Frame", "groups")
				:SetLayout("VERTICAL")
				:SetPadding(8, 8, 2, 2)
				:AddChild(UIElements.New("Frame", "most")
					:SetLayout("HORIZONTAL")
					:SetHeight(20)
					:SetMargin(0, 0, 0, 4)
					:AddChild(UIElements.New("Text", "label")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetTextColor("ACTIVE_BG_ALT")
						:SetText(L["MOST GROUPS"])
					)
					:AddChild(UIElements.New("Spacer", "spacer"))
					:AddChild(UIElements.New("Text", "value")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetJustifyH("RIGHT")
						:SetText(mostGroupsName)
					)
				)
				:AddChild(UIElements.New("Frame", "least")
					:SetLayout("HORIZONTAL")
					:SetHeight(20)
					:AddChild(UIElements.New("Text", "label")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetTextColor("ACTIVE_BG_ALT")
						:SetText(L["LEAST GROUPS"])
					)
					:AddChild(UIElements.New("Spacer", "spacer"))
					:AddChild(UIElements.New("Text", "value")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetJustifyH("RIGHT")
						:SetText(leastGroupsName)
					)
				)
			)
			:AddChild(UIElements.New("Texture", "line1")
				:SetWidth(1)
				:SetTexture("ACTIVE_BG")
			)
			:AddChild(UIElements.New("Frame", "items")
				:SetLayout("VERTICAL")
				:SetPadding(8, 8, 2, 2)
				:AddChild(UIElements.New("Frame", "most")
					:SetLayout("HORIZONTAL")
					:SetHeight(20)
					:SetMargin(0, 0, 0, 4)
					:AddChild(UIElements.New("Text", "label")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetTextColor("ACTIVE_BG_ALT")
						:SetText(L["MOST ITEMS"])
					)
					:AddChild(UIElements.New("Spacer", "spacer"))
					:AddChild(UIElements.New("Text", "value")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetJustifyH("RIGHT")
						:SetText(mostItemsName)
					)
				)
				:AddChild(UIElements.New("Frame", "least")
					:SetLayout("HORIZONTAL")
					:SetHeight(20)
					:AddChild(UIElements.New("Text", "label")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetTextColor("ACTIVE_BG_ALT")
						:SetText(L["LEAST ITEMS"])
					)
					:AddChild(UIElements.New("Spacer", "spacer"))
					:AddChild(UIElements.New("Text", "value")
						:SetWidth("AUTO")
						:SetFont("TABLE_TABLE1")
						:SetJustifyH("RIGHT")
						:SetText(leastItemsName)
					)
				)
			)
		)
		:AddChild(UIElements.New("SelectionScrollingTable", "list")
			:SetSettingsContext(private.settings, "operationsSummaryScrollingTable")
			:GetScrollingTableInfo()
				:NewColumn("name")
					:SetTitle(L["Operation"])
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("LEFT")
					:SetTextInfo("operationName")
					:SetSortInfo("operationName")
					:SetActionIconInfo(1, 12, private.GetConfigureIcon, true)
					:SetActionIconClickHandler(private.OnConfigureIconClick)
					:DisableHiding()
					:Commit()
				:NewColumn("groups")
					:SetTitle(L["Groups Using"])
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("LEFT")
					:SetTextInfo("numGroups")
					:SetSortInfo("numGroups")
					:Commit()
				:NewColumn("items")
					:SetTitle(L["Items Using"])
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("LEFT")
					:SetTextInfo("numItems", private.GetNumItemsText)
					:SetSortInfo("numItems")
					:SetTooltipInfo("numItems", private.GetNumItemsTooltip)
					:Commit()
				:Commit()
			:SetQuery(query)
			:SetContext(query)
			:SetAutoReleaseQuery(true)
			:SetScript("OnSelectionChanged", private.OperationListOnSelectionChanged)
		)
		:AddChild(UIElements.New("Texture", "line")
			:SetHeight(2)
			:SetTexture("ACTIVE_BG")
		)
		:AddChild(UIElements.New("Frame", "footer")
			:SetLayout("HORIZONTAL")
			:SetHeight(40)
			:SetPadding(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("ActionButton", "deleteSelected")
				:SetHeight(24)
				:SetMargin(0, 8, 0, 0)
				:SetDisabled(true)
				:SetText(L["Delete Operations"])
				:SetScript("OnClick", private.DeleteSelectedOnClick)
			)
			:AddChild(UIElements.New("Button", "selectAll")
				:SetSize("AUTO", 20)
				:SetMargin(0, 8, 0, 0)
				:SetFont("BODY_BODY3_MEDIUM")
				:SetText(L["Select All"])
				:SetScript("OnClick", private.SelectAllOnClick)
			)
			:AddChild(UIElements.New("Texture", "line")
				:SetSize(2, 20)
				:SetMargin(0, 8, 0, 0)
				:SetTexture("ACTIVE_BG")
			)
			:AddChild(UIElements.New("Button", "clearAll")
				:SetSize("AUTO", 20)
				:SetFont("BODY_BODY3_MEDIUM")
				:SetText(L["Clear All"])
				:SetDisabled(true)
				:SetScript("OnClick", private.ClearAllOnClick)
			)
		)
end

function private.AddOperationGroups(frame)
	for _, groupPath in TSM.Operations.GroupIterator(private.currentModule, private.currentOperationName, true) do
		frame:AddChild(private.CreateGroupOperationLine(groupPath))
	end
end

function private.CreateGroupOperationLine(groupPath)
	local groupName = groupPath == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.GetName(groupPath)
	local level = select('#', strsplit(TSM.CONST.GROUP_SEP, groupPath))
	return UIElements.New("Frame", "group")
		:SetLayout("HORIZONTAL")
		:SetHeight(20)
		:SetMargin(2, 0, 0, 0)
		:AddChild(UIElements.New("Text", "text")
			:SetWidth("AUTO")
			:SetFont("BODY_BODY2")
			:SetTextColor(Theme.GetGroupColor(level))
			:SetText(groupName)
		)
		:AddChild(UIElements.New("Button", "viewBtn")
			:SetMargin(2, 2, 0, 0)
			:SetBackgroundAndSize("iconPack.14x14/Groups")
			:SetContext(groupPath)
			:SetScript("OnClick", private.ViewGroupOnClick)
		)
		:AddChild(UIElements.New("Button", "removeBtn")
			:SetBackgroundAndSize("iconPack.14x14/Close/Default")
			:SetContext(groupPath)
			:SetScript("OnClick", private.RemoveOperationGroupOnClick)
		)
		:AddChild(UIElements.New("Spacer", "spacer"))
end

function private.CreateLinkButton(disabled, settingKey)
	local relationshipSet = TSM.Operations.HasRelationship(private.currentModule, private.currentOperationName, settingKey)
	local linkTexture = nil
	if disabled and relationshipSet then
		linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR_DISABLED")
	elseif disabled then
		linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT_DISABLED")
	elseif relationshipSet then
		linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "INDICATOR")
	else
		linkTexture = TSM.UI.TexturePacks.GetColoredKey("iconPack.14x14/Link", "TEXT")
	end
	return UIElements.New("Button", "linkBtn")
		:SetMargin(4, 4, 0, 0)
		:SetBackgroundAndSize(linkTexture)
		:SetDisabled(disabled)
		:SetContext(settingKey)
		:SetScript("OnClick", private.LinkBtnOnClick)
end



-- ============================================================================
-- Local Script Handlers
-- ============================================================================

function private.OperationSearchOnValueChanged(input)
	local filter = strlower(input:GetValue())
	input:GetElement("__parent.operationTree"):SetOperationNameFilter(filter)
end

function private.OperationTreeOnOperationAdded(operationTree, moduleName, operationName, copyOperationName)
	-- clear the filter
	operationTree:GetElement("__parent.search")
		:SetValue("")
		:Draw()
	operationTree:SetOperationNameFilter("")
	TSM.Operations.Create(moduleName, operationName)
	if copyOperationName then
		TSM.Operations.Copy(moduleName, operationName, copyOperationName)
	end
end

function private.OperationTreeOnOperationConfirmDelete(self, moduleName, operationName)
	self:GetBaseElement():ShowConfirmationDialog(L["Delete Operation?"], L["Are you sure you want to delete this operation?"], private.OperationTreeOnOperationDeleted, self, moduleName, operationName)
end

function private.OperationTreeOnOperationDeleted(self, moduleName, operationName)
	TSM.Operations.Delete(moduleName, operationName)
	local operationTree = self:GetElement("__parent.operationTree")
	operationTree:SetSelectedOperation(moduleName, nil)
		:Draw()
end

function private.OperationTreeOnOperationSelected(self, moduleName, operationName)
	private.currentModule = moduleName
	private.currentOperationName = operationName

	local viewContainer = self:GetParentElement():GetParentElement():GetElement("content")
	if moduleName and operationName then
		TSM.Operations.Update(moduleName, operationName)
		viewContainer:SetPath("operation")
		viewContainer:GetElement("settings.title.text"):SetText(operationName)
		local contentFrame = viewContainer:GetElement("settings.content")
		contentFrame:ReleaseAllChildren()
		contentFrame:AddChild(private.moduleCallbacks[moduleName](operationName))
	elseif moduleName then
		local numOperations = 0
		for _ in TSM.Operations.OperationIterator(moduleName) do
			numOperations = numOperations + 1
		end
		TSM.UI.AnalyticsRecordPathChange("main", "operations", "summary")
		viewContainer:SetPath("summary")
		viewContainer:GetElement("settings.title.text"):SetText(format(L["%s %s Operations"], Theme.GetColor("INDICATOR"):ColorText(numOperations), moduleName))
		local contentFrame = viewContainer:GetElement("settings.content")
		contentFrame:ReleaseAllChildren()
		contentFrame:AddChild(private.GetSummaryContent())
	else
		TSM.UI.AnalyticsRecordPathChange("main", "operations", "none")
		viewContainer:SetPath("none")
		viewContainer:GetElement("settings.title.text"):SetText(L["No Operation Selected"])
	end
	viewContainer:Draw()
end

function private.CreateNewOperationOnClick(button)
	local operationName = "New Operation"
	local num = 1
	while TSM.Operations.Exists(private.currentModule, operationName.." "..num) do
		num = num + 1
	end
	operationName = operationName .. " " .. num
	TSM.Operations.Create(private.currentModule, operationName)
	button:GetElement("__parent.__parent.__parent.__parent.selection.operationTree")
		:SetSelectedOperation(private.currentModule, operationName)
		:Draw()
end

function private.OperationNameChanged(text, newValue)
	newValue = strtrim(newValue)
	if newValue == private.currentOperationName then
		-- didn't change
		text:Draw()
	elseif newValue == "" then
		Log.PrintUser(L["Invalid operation name."])
		text:Draw()
	elseif TSM.Operations.Exists(private.currentModule, newValue) then
		Log.PrintUser(L["Group already exists."])
		text:Draw()
	else
		TSM.Operations.Rename(private.currentModule, private.currentOperationName, newValue)
		text:GetElement("__parent.__parent.__parent.__parent.selection.operationTree")
			:SetSelectedOperation(private.currentModule, newValue)
			:Draw()
	end
end

function private.NameOnEditingChanged(text, editing)
	if editing then
		text:GetElement("__parent.renameBtn"):Hide()
	else
		text:GetElement("__parent.renameBtn"):Show()
	end
end

function private.RenameOperationOnClick(button)
	button:GetElement("__parent.text"):SetEditing(true)
end

function private.ResetOperationOnClick(button)
	button:GetBaseElement():ShowConfirmationDialog(L["Reset Operation?"], L["Resetting the operation will return all inputs back to default and cannot be unddone. Click confirm to reset."], private.ConfirmResetOnClick, button)
end

function private.ConfirmResetOnClick(button)
	TSM.Operations.Reset(private.currentModule, private.currentOperationName)
	local settingsFrame = button:GetBaseElement():GetElement("content.operations.content.settings")
	local contentFrame = settingsFrame:GetElement("content")
	contentFrame:ReleaseAllChildren()
	TSM.Operations.Update(private.currentModule, private.currentOperationName)
	contentFrame:AddChild(private.moduleCallbacks[private.currentModule](private.currentOperationName))
	button:GetBaseElement():HideDialog()
	settingsFrame:Draw()
	Log.PrintfUser(L["%s - %s has been reset to default values."], private.currentModule, Theme.GetColor("INDICATOR_ALT"):ColorText(private.currentOperationName))
end

function private.GroupSelectionChanged(groupSelector)
	for groupPath in groupSelector:SelectedGroupIterator() do
		if not TSM.Operations.GroupHasOperation(private.currentModule, groupPath, private.currentOperationName) then
			local parentElement = groupSelector:GetParentElement():GetParentElement()
			if groupPath ~= TSM.CONST.ROOT_GROUP_PATH then
				TSM.Groups.SetOperationOverride(groupPath, private.currentModule, true)
			end
			local numOperations = 0
			local lastOperationName = nil
			for _, groupOperationName in TSM.Groups.OperationIterator(groupPath, private.currentModule) do
				lastOperationName = groupOperationName
				numOperations = numOperations + 1
			end
			if numOperations == TSM.Operations.GetMaxNumber(private.currentModule) then
				-- replace the last operation since we're already at the max number of operations
				TSM.Groups.RemoveOperation(groupPath, private.currentModule, numOperations)
				Log.PrintfUser(L["%s previously had the max number of operations, so removed %s."], Log.ColorUserAccentText(TSM.Groups.Path.Format(groupPath)), Log.ColorUserAccentText(lastOperationName))
			end
			TSM.Groups.AppendOperation(groupPath, private.currentModule, private.currentOperationName)
			Log.PrintfUser(L["Added %s to %s."], Log.ColorUserAccentText(private.currentOperationName), Log.ColorUserAccentText(groupPath == TSM.CONST.ROOT_GROUP_PATH and L["Base Group"] or TSM.Groups.Path.Format(groupPath)))
			parentElement:AddChild(private.CreateGroupOperationLine(groupPath))
		end
	end
	groupSelector:ClearSelectedGroups(true)
	groupSelector:GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():GetParentElement():Draw()
end

function private.ViewGroupOnClick(button)
	local baseFrame = button:GetBaseElement()
	TSM.MainUI.Groups.ShowGroupSettings(baseFrame, button:GetContext())
end

function private.RemoveOperationGroupOnClick(self)
	local groupPath = self:GetContext()
	TSM.Groups.RemoveOperationByName(groupPath, private.currentModule, private.currentOperationName)

	-- remove the line for this group
	local removeElement = self:GetParentElement()
	local removeElementParent = removeElement:GetParentElement()
	removeElementParent:RemoveChild(removeElement)
	removeElement:Release()
	removeElementParent:GetParentElement():GetParentElement():GetParentElement():Draw()
end

function private.LinkBtnOnClick(button)
	local settingKey = button:GetContext()
	wipe(private.linkMenuEntries)
	for _, operationName in TSM.Operations.OperationIterator(private.currentModule) do
		if operationName ~= private.currentOperationName and not TSM.Operations.IsCircularRelationship(private.currentModule, private.currentOperationName, settingKey) then
			tinsert(private.linkMenuEntries, operationName)
		end
	end
	sort(private.linkMenuEntries)
	button:GetBaseElement():ShowDialogFrame(UIElements.New("PopupFrame", "linkDialog")
		:SetLayout("VERTICAL")
		:SetSize(263, 243)
		:AddAnchor("TOPRIGHT", button:_GetBaseFrame(), "BOTTOM", 22, -16)
		:AddChild(UIElements.New("Frame", "titleFrame")
			:SetLayout("VERTICAL")
			:SetHeight(37)
			:AddChild(UIElements.New("Text", "title")
				:SetFont("BODY_BODY2_MEDIUM")
				:SetJustifyH("CENTER")
				:SetText(L["Link to Another Operation"])
			)
		)
		:AddChild(UIElements.New("Texture", "line")
			:SetHeight(2)
			:SetTexture("TEXT")
		)
		:AddChild(UIElements.New("SelectionList", "list")
			:SetContext(settingKey)
			:SetMargin(2, 2, 0, 3)
			:SetEntries(private.linkMenuEntries, TSM.Operations.GetRelationship(private.currentModule, private.currentOperationName, settingKey))
			:SetScript("OnEntrySelected", private.ListOnEntrySelected)
		)
	)
end

function private.ListOnEntrySelected(list, operationName)
	local settingKey = list:GetContext()
	local previousValue = TSM.Operations.GetRelationship(private.currentModule, private.currentOperationName, settingKey)
	if operationName == previousValue then
		TSM.Operations.SetRelationship(private.currentModule, private.currentOperationName, settingKey, nil)
	else
		TSM.Operations.SetRelationship(private.currentModule, private.currentOperationName, settingKey, operationName)
	end

	local baseFrame = list:GetBaseElement()
	baseFrame:HideDialog()
	Operations.ShowOperationSettings(baseFrame, private.currentModule, private.currentOperationName)
end

function private.OperationListOnSelectionChanged(scrollingTable)
	local selectionCleared = scrollingTable:IsSelectionCleared()
	local numSelected = 0
	for _ in scrollingTable:SelectionIterator() do
		numSelected = numSelected + 1
	end
	local footer = scrollingTable:GetElement("__parent.footer")
	footer:GetElement("deleteSelected")
		:SetText(numSelected > 0 and format(L["Delete %d Operations"], numSelected) or L["Delete Operations"])
		:SetDisabled(selectionCleared)
	footer:GetElement("selectAll")
		:SetDisabled(scrollingTable:IsAllSelected())
	footer:GetElement("clearAll")
		:SetDisabled(selectionCleared)
	footer:Draw()
end

function private.SelectAllOnClick(button)
	button:GetElement("__parent.__parent.list"):SelectAll()
end

function private.ClearAllOnClick(button)
	button:GetElement("__parent.__parent.list"):ClearSelection()
end

function private.DeleteSelectedOnClick(button)
	local scrollingTable = button:GetElement("__parent.__parent.list")
	button:GetBaseElement():ShowConfirmationDialog(L["Delete Operations?"], L["Are you sure you want to delete the selected operations?"], private.DeleteSelectedOperations, scrollingTable)
end

function private.DeleteSelectedOperations(scrollingTable)
	local toDelete = TempTable.Acquire()
	for _, row in scrollingTable:SelectionIterator() do
		local moduleName, operationName = row:GetFields("moduleName", "operationName")
		assert(moduleName == private.currentModule)
		tinsert(toDelete, operationName)
	end
	TSM.Operations.DeleteList(private.currentModule, toDelete)
	TempTable.Release(toDelete)
	scrollingTable:UpdateData(true)
	private.OperationListOnSelectionChanged(scrollingTable)
	scrollingTable:GetElement("__parent.__parent.__parent.__parent.__parent.selection.operationTree"):UpdateData(true)
end



-- ============================================================================
-- Private Helper Functions
-- ============================================================================

function private.NumGroupsVirtualField(row)
	local num = 0
	for _ in TSM.Operations.GroupIterator(row:GetField("moduleName"), row:GetField("operationName")) do
		num = num + 1
	end
	return num
end

function private.NumItemsVirtualField(row)
	local includesBaseGroup = false
	local num = 0
	for _, groupPath in TSM.Operations.GroupIterator(row:GetField("moduleName"), row:GetField("operationName")) do
		if groupPath == TSM.CONST.ROOT_GROUP_PATH then
			includesBaseGroup = true
		else
			num = num + TSM.Groups.GetNumItems(groupPath)
		end
	end
	if includesBaseGroup then
		num = num + 0.9
	end
	return num
end

function private.GetConfigureIcon(_, _, iconIndex)
	assert(iconIndex == 1)
	return true, "iconPack.12x12/Popout", false
end

function private.OnConfigureIconClick(scrollingTable, data, iconIndex)
	assert(iconIndex == 1)
	local operationName = scrollingTable:GetContext():GetResultRowByUUID(data):GetField("operationName")
	scrollingTable:GetElement("__parent.__parent.__parent.__parent.__parent.selection.operationTree")
		:SetSelectedOperation(private.currentModule, operationName)
end

function private.GetNumItemsText(numItems)
	if numItems == floor(numItems) then
		return numItems
	else
		return floor(numItems).."*"
	end
end

function private.GetNumItemsTooltip(numItems)
	if numItems == floor(numItems) then
		return nil
	end
	return L["This operation is applied to the base group which includes every item not in another group."]
end
