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

local _, TSM = ...
local Inbox = TSM.UI.MailingUI:NewPackage("Inbox")
local L = TSM.Include("Locale").GetTable()
local Delay = TSM.Include("Util.Delay")
local Event = TSM.Include("Util.Event")
local Money = TSM.Include("Util.Money")
local Sound = TSM.Include("Util.Sound")
local FSM = TSM.Include("Util.FSM")
local Math = TSM.Include("Util.Math")
local Theme = TSM.Include("Util.Theme")
local ItemInfo = TSM.Include("Service.ItemInfo")
local MailTracking = TSM.Include("Service.MailTracking")
local Settings = TSM.Include("Service.Settings")
local UIElements = TSM.Include("UI.UIElements")
local private = {
	settings = nil,
	fsm = nil,
	frame = nil,
	view = nil,
	inboxQuery = nil,
	itemsQuery = nil,
	selectedMail = nil,
	nextUpdate = nil,
	filterText = ""
}
local PLAYER_NAME = UnitName("player")
local MAIL_REFRESH_TIME = TSM.IsWowClassic() and 60 or 15



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

function Inbox.OnInitialize()
	private.settings = Settings.NewView()
		:AddKey("global", "mailingUIContext", "mailsScrollingTable")
		:AddKey("global", "mailingOptions", "openMailSound")
	private.FSMCreate()
	TSM.UI.MailingUI.RegisterTopLevelPage(INBOX, private.GetInboxFrame)
end

function Inbox.IsMailOpened()
	if not private.view or not private.view:GetElement("view") then
		return
	end

	return private.view:GetElement("view"):GetPath() == "items"
end



-- ============================================================================
-- Inbox UI
-- ============================================================================

function private.GetInboxFrame()
	TSM.UI.AnalyticsRecordPathChange("mailing", "inbox")
	local frame = UIElements.New("Frame", "frame")
		:SetLayout("VERTICAL")
		:AddChild(UIElements.New("ViewContainer", "view")
			:SetNavCallback(private.GetViewContentFrame)
			:AddPath("mails", true)
			:AddPath("items")
		)

	private.view = frame

	return frame
end

function private.GetViewContentFrame(viewContainer, path)
	if path == "mails" then
		private.selectedMail = nil
		return private.GetInboxMailsFrame()
	elseif path == "items" then
		return private.GetInboxItemsFrame()
	else
		error("Unexpected path: "..tostring(path))
	end
end

function private.GetInboxMailsFrame()
	private.inboxQuery = private.inboxQuery or TSM.Mailing.Inbox.CreateQuery()
	private.inboxQuery:ResetFilters()
	private.inboxQuery:ResetOrderBy()
	private.inboxQuery:OrderBy("index", true)

	private.filterText = ""

	local frame = UIElements.New("Frame", "inbox")
		:SetLayout("VERTICAL")
		:AddChild(UIElements.New("Frame", "top")
			:SetLayout("HORIZONTAL")
			:SetPadding(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("Input", "filterInput")
				:SetHeight(24)
				:SetMargin(0, 8, 0, 0)
				:AllowItemInsert()
				:SetIconTexture("iconPack.18x18/Search")
				:SetClearButtonEnabled(true)
				:SetHintText(L["Search items in mailbox"])
				:SetDisabled(false)
				:SetScript("OnValueChanged", private.SearchOnValueChanged)
			)
			:AddChild(UIElements.New("ActionButton", "reload")
				:SetSize(110, 24)
				:SetFormattedText(L["Reload UI (%d)"], MAIL_REFRESH_TIME)
				:SetScript("OnClick", ReloadUI)
			)
		)
		:AddChild(UIElements.New("QueryScrollingTable", "mails")
			:SetSettingsContext(private.settings, "mailsScrollingTable")
			:GetScrollingTableInfo()
				:NewColumn("items")
					:SetTitle(L["Items"])
					:SetFont("ITEM_BODY3")
					:SetJustifyH("LEFT")
					:SetTextInfo(nil, private.FormatItem)
					:SetTooltipInfo("itemString")
					:SetActionIconInfo(1, 12, private.GetMailTypeIcon)
					:SetTooltipLinkingDisabled(true)
					:DisableHiding()
					:Commit()
				:NewColumn("sender")
					:SetTitle(L["Sender"])
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("LEFT")
					:SetTextInfo("sender")
					:Commit()
				:NewColumn("expires")
					:SetTitle(L["Expires"])
					:SetFont("BODY_BODY3_MEDIUM")
					:SetJustifyH("RIGHT")
					:SetTextInfo("expires", private.FormatDaysLeft)
					:Commit()
				:NewColumn("money")
					:SetTitle(L["Gold"])
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("RIGHT")
					:SetTextInfo(nil, private.FormatMoney)
					:Commit()
				:Commit()
			:SetQuery(private.inboxQuery)
			:SetSelectionDisabled(true)
			:SetScript("OnRowClick", private.QueryOnRowClick)
			:SetScript("OnDataUpdated", private.InboxOnDataUpdated)
		)
		:AddChild(UIElements.New("Texture", "line")
			:SetHeight(2)
			:SetTexture("ACTIVE_BG")
		)
		:AddChild(UIElements.New("Frame", "footer")
			:SetLayout("HORIZONTAL")
			:SetHeight(26)
			:SetBackgroundColor("PRIMARY_BG")
			:AddChild(UIElements.New("Spacer", "spacer"))
			:AddChild(UIElements.New("Text", "mailNum")
				:SetWidth(200)
				:SetFont("BODY_BODY2_MEDIUM")
				:SetJustifyH("RIGHT")
			)
			:AddChild(UIElements.New("Texture", "vline")
				:SetWidth(1)
				:SetMargin(8, 8, 3, 3)
				:SetTexture("ACTIVE_BG_ALT")
			)
			:AddChild(UIElements.New("Text", "itemNum")
				:SetWidth(130)
				:SetFont("BODY_BODY2_MEDIUM")
				:SetJustifyH("RIGHT")
				:SetText(format(L["%s Items Total"], Theme.GetFeedbackColor("GREEN"):ColorText(0)))
			)
			:AddChild(UIElements.New("Texture", "vline")
				:SetWidth(1)
				:SetMargin(8, 8, 3, 3)
				:SetTexture("ACTIVE_BG_ALT")
			)
			:AddChild(UIElements.New("Frame", "content")
				:SetLayout("HORIZONTAL")
				:SetWidth(190)
				:AddChild(UIElements.New("Spacer", "spacer"))
				:AddChild(UIElements.New("Text", "totalText")
					:SetWidth("AUTO")
					:SetFont("BODY_BODY2_MEDIUM")
					:SetJustifyH("RIGHT")
					:SetText(L["Total Gold"]..": ")
				)
				:AddChild(UIElements.New("Text", "gold")
					:SetWidth("AUTO")
					:SetMargin(0, 8, 0, 0)
					:SetFont("TABLE_TABLE1")
					:SetJustifyH("RIGHT")
					:SetText(Money.ToString(0))
				)
			)
		)
		:AddChild(UIElements.New("Texture", "line")
			:SetHeight(2)
			:SetTexture("ACTIVE_BG")
		)
	if TSM.IsWowClassic() then
		frame:AddChild(UIElements.New("Frame", "bottom")
			:SetLayout("VERTICAL")
			:SetHeight(74)
			:SetPadding(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("ActionButton", "openAllMail")
				:SetHeight(26)
				:SetText(L["Open Mail"])
				:SetScript("OnClick", private.OpenBtnOnClick)
				:SetModifierText(L["Open All Mail"], "SHIFT")
				:SetModifierText(L["Open Mail Without Money"], "CTRL")
				:SetModifierText(L["Open All Mail Without Money"], "SHIFT", "CTRL")
				:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes and CTRL to not open mail with money attached."])
			)
			:AddChild(UIElements.New("Frame", "buttons")
				:SetLayout("HORIZONTAL")
				:SetHeight(24)
				:SetMargin(0, 0, 8, 0)
				:AddChild(UIElements.New("ActionButton", "openAllSales")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["Sold"])
					:SetContext("SALE")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["All Sold"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllBuys")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["Bought"])
					:SetContext("BUY")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["All Bought"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllCancels")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["Cancelled"])
					:SetContext("CANCEL")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["All Cancelled"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllExpires")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["Expired"])
					:SetContext("EXPIRE")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["All Expired"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllOthers")
					:SetFont("BODY_BODY2")
					:SetText(L["Other"])
					:SetContext("OTHER")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["All Other"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to continue after the inbox refreshes"])
				)
			)
		)
	else
		frame:AddChild(UIElements.New("Frame", "bottom")
			:SetLayout("VERTICAL")
			:SetHeight(74)
			:SetPadding(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("ActionButton", "openAllMail")
				:SetHeight(26)
				:SetText(L["Open All Mail"])
				:SetScript("OnClick", private.OpenBtnOnClick)
				:SetModifierText(L["Open Mail"], "SHIFT")
				:SetModifierText(L["Open All Mail Without Money"], "CTRL")
				:SetModifierText(L["Open Mail Without Money"], "SHIFT", "CTRL")
				:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes and CTRL to not open mail with money attached."])
			)
			:AddChild(UIElements.New("Frame", "buttons")
				:SetLayout("HORIZONTAL")
				:SetHeight(24)
				:SetMargin(0, 0, 8, 0)
				:AddChild(UIElements.New("ActionButton", "openAllSales")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["All Sold"])
					:SetContext("SALE")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["Sold"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllBuys")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["All Bought"])
					:SetContext("BUY")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["Bought"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllCancels")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["All Cancelled"])
					:SetContext("CANCEL")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["Cancelled"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllExpires")
					:SetMargin(0, 8, 0, 0)
					:SetFont("BODY_BODY2")
					:SetText(L["All Expired"])
					:SetContext("EXPIRE")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["Expired"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes"])
				)
				:AddChild(UIElements.New("ActionButton", "openAllOthers")
					:SetFont("BODY_BODY2")
					:SetText(L["All Other"])
					:SetContext("OTHER")
					:SetScript("OnClick", private.OpenBtnOnClick)
					:SetModifierText(L["Other"], "SHIFT")
					:SetTooltip(L["Hold SHIFT to not continue after the inbox refreshes"])
				)
			)
		)
	end
	frame:SetScript("OnUpdate", private.InboxFrameOnUpdate)
	frame:SetScript("OnHide", private.InboxFrameOnHide)

	private.frame = frame

	return frame
end

function private.GetInboxItemsFrame()
	private.itemsQuery = private.itemsQuery or MailTracking.CreateMailItemQuery()
	private.itemsQuery:ResetFilters()
		:Equal("index", private.selectedMail)
		:OrderBy("itemIndex", true)

	local _, _, _, isTakeable = GetInboxText(private.selectedMail)
	local _, _, _, _, _, _, _, _, _, _, textCreated = GetInboxHeaderInfo(private.selectedMail)
	local frame = UIElements.New("Frame", "items")
		:SetLayout("VERTICAL")
		:AddChild(UIElements.New("Frame", "content")
			:SetLayout("VERTICAL")
			:SetBackgroundColor("PRIMARY_BG")
			:AddChild(UIElements.New("Frame", "top")
				:SetLayout("HORIZONTAL")
				:SetHeight(40)
				:SetPadding(8)
				:SetBackgroundColor("PRIMARY_BG_ALT")
				:AddChild(UIElements.New("Frame", "back")
					:SetLayout("VERTICAL")
					:SetWidth(100)
					:AddChild(UIElements.New("ActionButton", "button")
						:SetWidth(64)
						:SetMargin(0, 36, 0, 0)
						:SetIcon("iconPack.14x14/Chevron/Right@180")
						:SetText(BACK)
						:SetScript("OnClick", private.ViewBackButtonOnClick)
					)
				)
				:AddChild(UIElements.New("Text", "title")
					:SetFont("BODY_BODY1_BOLD")
					:SetJustifyH("CENTER")
					:SetText(L["Open Mail"])
				)
				:AddChild(UIElements.New("Frame", "copy")
					:SetLayout("VERTICAL")
					:SetWidth(100)
					:AddChild(UIElements.New("Button", "button")
						:SetFont("BODY_BODY2_MEDIUM")
						:SetIcon("iconPack.14x14/Duplicate", "LEFT")
						:SetText(L["Copy Letter"])
						:SetDisabled(not isTakeable or textCreated)
						:SetScript("OnClick", private.CopyLetterButtonOnClick)
					)
				)
			)
			:AddChild(UIElements.New("Frame", "header")
				:SetLayout("VERTICAL")
				:SetPadding(12, 12, 4, 6)
				:SetBackgroundColor("PRIMARY_BG_ALT")
				:AddChild(UIElements.New("Frame", "top")
					:SetLayout("VERTICAL")
					:SetHeight(20)
					:SetMargin(0, 0, 0, 6)
					:AddChild(UIElements.New("Frame", "left")
						:SetLayout("HORIZONTAL")
						:AddChild(UIElements.New("Text", "label")
							:SetWidth("AUTO")
							:SetFont("BODY_BODY2_MEDIUM")
							:SetText(L["From"]..": ")
						)
						:AddChild(UIElements.New("Text", "sender")
							:SetFont("BODY_BODY2")
						)
						:AddChild(UIElements.New("Spacer", "spacer"))
						:AddChild(UIElements.New("ActionButton", "report")
							:SetSize(124, 20)
							:SetFont("BODY_BODY2_MEDIUM")
							:SetText(L["Report Spam"])
							:SetScript("OnClick", private.ReportSpamOnClick)
						)
					)
				)
				:AddChild(UIElements.New("Frame", "bottom")
					:SetLayout("VERTICAL")
					:SetHeight(20)
					:AddChild(UIElements.New("Frame", "left")
						:SetLayout("HORIZONTAL")
						:AddChild(UIElements.New("Text", "label")
							:SetWidth("AUTO")
							:SetFont("BODY_BODY2_MEDIUM")
							:SetText(L["Subject"]..": ")
						)
						:AddChild(UIElements.New("Text", "subject")
							:SetFont("BODY_BODY2")
						)
					)
				)
			)
			:AddChild(UIElements.New("Texture", "line")
				:SetHeight(2)
				:SetTexture("ACTIVE_BG")
			)
			:AddChild(UIElements.New("Frame", "body")
				:SetLayout("VERTICAL")
				:SetPadding(4, 4, 8, 12)
				:AddChild(UIElements.New("MultiLineInput", "input")
					:SetBackgroundColor("PRIMARY_BG")
					:SetFont("BODY_BODY2")
					:SetValue("")
				)
			)
			:AddChild(UIElements.New("Frame", "attachments")
				:SetLayout("VERTICAL")
				:SetHeight(144)
				:SetPadding(12, 12, 0, 8)
				:SetBackgroundColor("PRIMARY_BG")
				:AddChild(UIElements.New("Frame", "header")
					:SetLayout("HORIZONTAL")
					:SetHeight(20)
					:AddChild(UIElements.New("Text", "text")
						:SetWidth("AUTO")
						:SetFont("BODY_BODY2_MEDIUM")
						:SetText(L["Attachments"])
					)
					:AddChild(UIElements.New("Button", "takeAll")
						:SetWidth("AUTO")
						:SetMargin(8, 0, 0, 0)
						:SetFont("BODY_BODY3")
						:SetTextColor("INDICATOR")
						:SetText(L["Move all to Bags"])
						:SetScript("OnClick", private.TakeAllOnClick)
					)
					:AddChild(UIElements.New("Spacer", "spacer"))
					:AddChild(UIElements.New("Text", "cod")
						:SetMargin(0, 5, 0, 0)
						:SetFont("BODY_BODY3_MEDIUM")
						:SetJustifyH("RIGHT")
						:SetText(L["COD"]..":")
					)
					:AddChild(UIElements.New("Text", "codAmount")
						:SetWidth("AUTO")
						:SetMargin(0, 8, 0, 0)
						:SetFont("ITEM_BODY2")
						:SetJustifyH("RIGHT")
					)
				)
				:AddChild(UIElements.New("QueryScrollingTable", "items")
					:SetHeaderHidden(true)
					:GetScrollingTableInfo()
						:NewColumn("items")
							:SetFont("ITEM_BODY3")
							:SetJustifyH("LEFT")
							:SetTextInfo(nil, private.FormatInboxItem)
							:SetIconSize(12)
							:SetIconInfo("itemLink", ItemInfo.GetTexture)
							:SetTooltipInfo(nil, private.GetInboxItemsTooltip)
							:SetTooltipLinkingDisabled(true)
							:Commit()
						:Commit()
					:SetQuery(private.itemsQuery)
					:SetSelectionDisabled(true)
					:SetScript("OnRowClick", private.ItemQueryOnRowClick)
				)
			)
		)
		:AddChild(UIElements.New("Texture", "line")
			:SetHeight(2)
			:SetTexture("ACTIVE_BG")
		)
		:AddChild(UIElements.New("Frame", "footer")
			:SetLayout("HORIZONTAL")
			:SetHeight(42)
			:SetPadding(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:AddChild(UIElements.New("ActionButton", "reply")
				:SetMargin(0, 8, 0, 0)
				:SetFont("BODY_BODY2_MEDIUM")
				:SetText(L["Reply"])
				:SetScript("OnClick", private.ReplyOnClick)
			)
			:AddChild(UIElements.New("ActionButton", "return/send")
				:SetWidth(144)
				:SetFont("BODY_BODY2_MEDIUM")
				:SetText(MAIL_RETURN)
				:SetScript("OnClick", private.DeleteMailOnClick)
			)
		)

	private.UpdateInboxItemsFrame(frame)

	return frame
end

function private.ViewBackButtonOnClick(button)
	private.filterText = ""
	button:GetElement("__parent.__parent.__parent.__parent.__parent"):SetPath("mails", true)
end

function private.CopyLetterButtonOnClick(button)
	TakeInboxTextItem(private.selectedMail)
	button:SetDisabled(true)
		:Draw()
end

function private.GetMailTypeIcon(_, data, iconIndex, isMouseOver)
	if iconIndex == 1 then
		local mailType = private.inboxQuery:GetResultRowByUUID(data):GetField("icon")
		local icon = ""
		if mailType == "SALE" then
			icon = "iconPack.12x12/Bid"
		elseif mailType == "BUY" then
			icon = "iconPack.12x12/Shopping"
		elseif mailType == "CANCEL" then
			icon = "iconPack.12x12/Close/Circle"
		elseif mailType == "EXPIRE" then
			icon = "iconPack.12x12/Clock"
		elseif mailType == "OTHER" then
			icon = "iconPack.12x12/Mailing"
		else
			assert(mailType == "")
		end
		return true, icon, true
	else
		error("Invalid index: "..tostring(iconIndex))
	end
end

function private.ItemQueryOnRowClick(scrollingTable, row)
	local index = row:GetField("index")
	local _, _, _, _, _, cod = GetInboxHeaderInfo(index)
	if cod > 0 then
		scrollingTable:GetBaseElement():ShowConfirmationDialog(L["Accept COD?"], format(L["Accepting this item will cost: %s"], Money.ToString(cod)), private.TakeInboxItem, scrollingTable, row)
		return
	end

	private.TakeInboxItem(scrollingTable, row)
end

function private.TakeInboxItem(scrollingTable, row)
	local index = row:GetField("index")
	local itemIndex = row:GetField("itemIndex")
	if private.itemsQuery:Count() == 1 then
		if itemIndex == 0 then
			TakeInboxMoney(index)
		else
			TakeInboxItem(index, row:GetField("itemIndex"))
		end
		scrollingTable:GetElement("__parent.__parent.__parent.__parent"):SetPath("mails", true)
	else
		if itemIndex == 0 then
			TakeInboxMoney(index)
		else
			TakeInboxItem(index, row:GetField("itemIndex"))
		end
	end
end

function private.ReportSpamOnClick(button)
	if not CanComplainInboxItem(private.selectedMail) then
		return
	end
	local _, _, sender = GetInboxHeaderInfo(private.selectedMail)
	local dialog = StaticPopup_Show("CONFIRM_REPORT_SPAM_MAIL", sender)
	if dialog then
		dialog.data = private.selectedMail
	end
end

function private.TakeAllOnClick(button)
	local _, _, _, _, _, cod = GetInboxHeaderInfo(private.selectedMail)
	if cod > 0 then
		button:GetBaseElement():ShowConfirmationDialog(L["Accept COD?"], format(L["Accepting this item will cost: %s"], Money.ToString(cod)), private.AutoLootMailItem, button)
	else
		private.AutoLootMailItem(button)
	end
end

function private.AutoLootMailItem(button)
	-- marks the mail as read
	GetInboxText(private.selectedMail)
	AutoLootMailItem(private.selectedMail)
	button:GetElement("__parent.__parent.__parent.__parent.__parent"):SetPath("mails", true)
end

function private.ReplyOnClick(button)
	local _, _, sender = GetInboxHeaderInfo(private.selectedMail)
	TSM.UI.MailingUI.Send.SetSendRecipient(sender)
	TSM.UI.MailingUI.SetSelectedTab(L["Send"], true)
end

function private.DeleteMailOnClick(button)
	local _, _, _, _, money, _, _, itemCount = GetInboxHeaderInfo(private.selectedMail)
	if InboxItemCanDelete(private.selectedMail) then
		if itemCount and itemCount > 0 then
			return
		elseif money and money > 0 then
			return
		else
			DeleteInboxItem(private.selectedMail)
		end
	else
		ReturnInboxItem(private.selectedMail)
	end
	button:GetElement("__parent.__parent.__parent"):SetPath("mails", true)
end

function private.UpdateInboxItemsFrame(frame)
	local _, _, sender, subject, money, cod, _, itemCount, _, _, _, canReply, isGM = GetInboxHeaderInfo(private.selectedMail)
	frame:GetElement("content.header.top.left.sender"):SetText(sender or UNKNOWN)
	frame:GetElement("content.header.bottom.left.subject"):SetText(subject)

	if isGM then
		frame:GetElement("content.header.top.left.sender"):SetTextColor(Theme.GetBlizzardColor())
			:Draw()
	else
		frame:GetElement("content.header.top.left.sender"):SetTextColor("TEXT_ALT")
			:Draw()
	end

	if CanComplainInboxItem(private.selectedMail) then
		frame:GetElement("content.header.top.left.report"):Show()
	else
		frame:GetElement("content.header.top.left.report"):Hide()
	end

	if cod and cod > 0 then
		frame:GetElement("content.attachments.header.cod"):Show()
		frame:GetElement("content.attachments.header.codAmount"):SetText(Money.ToString(cod, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()))
		frame:GetElement("content.attachments.header.codAmount"):Show()
	else
		frame:GetElement("content.attachments.header.cod"):Hide()
		frame:GetElement("content.attachments.header.codAmount"):Hide()
	end

	if itemCount and itemCount > 0 then
		frame:GetElement("content.attachments.header.takeAll"):Show()
	else
		frame:GetElement("content.attachments.header.takeAll"):Hide()
	end

	if not sender or not canReply or sender == PLAYER_NAME then
		frame:GetElement("footer.reply"):SetDisabled(true)
	else
		frame:GetElement("footer.reply"):SetDisabled(false)
	end

	if InboxItemCanDelete(private.selectedMail) then
		frame:GetElement("footer.return/send"):SetText(DELETE)
		if (itemCount and itemCount > 0) or (money and money > 0) then
			frame:GetElement("footer.return/send"):SetDisabled(true)
		else
			frame:GetElement("footer.return/send"):SetDisabled(false)
		end
	else
		frame:GetElement("footer.return/send"):SetText(MAIL_RETURN)
		frame:GetElement("footer.return/send"):SetDisabled(false)
	end

	local body, _, _, _, isInvoice = GetInboxText(private.selectedMail)
	if isInvoice then
		local invoiceType, itemName, playerName, bid, buyout, deposit, consignment, _, etaHour, etaMin = GetInboxInvoiceInfo(private.selectedMail)
		playerName = playerName or (invoiceType == "buyer" and AUCTION_HOUSE_MAIL_MULTIPLE_SELLERS or AUCTION_HOUSE_MAIL_MULTIPLE_BUYERS)
		local purchaseType = bid == buyout and BUYOUT or HIGH_BIDDER
		if invoiceType == "buyer" then
			body = ITEM_PURCHASED_COLON.." "..itemName.."\n"..SOLD_BY_COLON.." "..playerName.." ("..purchaseType..")".."\n\n"..AMOUNT_PAID_COLON.." "..Money.ToString(bid)
		elseif invoiceType == "seller" then
			body = ITEM_SOLD_COLON.." "..itemName.."\n"..PURCHASED_BY_COLON.." "..playerName.." ("..purchaseType..")".."\n\n"..L["Sale Price"]..": "..Money.ToString(bid).."\n"..L["Deposit"]..": +"..Money.ToString(deposit).."\n"..L["Auction House Cut"]..": -"..Money.ToString(consignment, Theme.GetFeedbackColor("RED"):GetTextColorPrefix()).."\n\n"..AMOUNT_RECEIVED_COLON.." "..Money.ToString(bid + deposit - consignment)
		elseif invoiceType == "seller_temp_invoice" then
			body = ITEM_SOLD_COLON.." "..itemName.."\n"..PURCHASED_BY_COLON.." "..playerName.." ("..purchaseType..")".."\n\n"..AUCTION_INVOICE_PENDING_FUNDS_COLON.." "..Money.ToString(bid + deposit - consignment).."\n"..L["Estimated deliver time"]..": "..GameTime_GetFormattedTime(etaHour, etaMin, true)
		end
	end
	frame:GetElement("content.body.input")
		:SetValue(body or "")
		:Draw()

	if not body then
		frame:GetElement("content.body.input"):Hide()
		frame:GetElement("content.attachments.header.text"):SetText(L["Attachments"])
		frame:GetElement("content.attachments")
			:SetHeight(nil)
			:Draw()
	elseif private.itemsQuery:Count() == 0 then
		frame:GetElement("content.body.input"):Show()
		frame:GetElement("content.attachments.header.text"):SetText(L["No Attachments"])
		frame:GetElement("content.attachments")
			:SetHeight(48)
			:Draw()
	else
		frame:GetElement("content.body.input"):Show()
		frame:GetElement("content.attachments.header.text"):SetText(L["Attachments"])
		frame:GetElement("content.attachments")
			:SetHeight(144)
			:Draw()
	end
end

function private.FormatInboxItem(row)
	local itemIndex = row:GetField("itemIndex")
	if itemIndex == 0 then
		return L["Gold"]..": "..Money.ToString(row:GetField("itemLink"), Theme.GetFeedbackColor("GREEN"):GetTextColorPrefix())
	end

	local coloredItem = TSM.UI.GetColoredItemName(row:GetField("itemLink")) or ""
	local quantity = row:GetField("quantity")

	local item = ""
	if quantity > 1 then
		item = coloredItem..Theme.GetFeedbackColor("YELLOW"):ColorText(" (x"..quantity..")")
	else
		item = coloredItem
	end

	return item
end


function private.GetInboxItemsTooltip(row)
	return row:GetField("itemIndex") ~= 0 and row:GetField("itemLink")
end



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

function private.InboxFrameOnUpdate(frame)
	frame:SetScript("OnUpdate", nil)
	private.UpdateCountDown(true)
	Delay.AfterTime("mailUpdateCounter", 0, private.UpdateCountDown, 1)
	private.fsm:ProcessEvent("EV_FRAME_SHOW", frame, private.filterText)
end

function private.InboxFrameOnHide(frame)
	assert(frame == private.frame)
	private.frame = nil

	private.fsm:ProcessEvent("EV_FRAME_HIDE")
end

function private.InboxOnDataUpdated()
	if not private.frame then
		return
	end

	private.fsm:ProcessEvent("EV_MAIL_DATA_UPDATED", private.filterText)
end

function private.OpenBtnOnClick(button)
	local filterType = button:GetContext()
	button:SetPressed(true)
	local openAll = nil
	if TSM.IsWowClassic() then
		openAll = IsShiftKeyDown()
	else
		openAll = not IsShiftKeyDown()
	end
	private.fsm:ProcessEvent("EV_BUTTON_CLICKED", openAll, not filterType and IsControlKeyDown(), private.filterText, filterType)
end

function private.QueryOnRowClick(scrollingTable, row, button)
	if button ~= "LeftButton" then
		return
	end

	if IsShiftKeyDown() then
		local index = row:GetField("index")
		local _, _, _, _, _, cod = GetInboxHeaderInfo(index)
		if cod <= 0 then
			-- marks the mail as read
			GetInboxText(index)
			AutoLootMailItem(index)
		end
	else
		TSM.Mailing.Open.KillThread()
		private.selectedMail = row:GetField("index")
		scrollingTable:GetElement("__parent.__parent"):SetPath("items", true)
	end
end

function private.SearchOnValueChanged(input)
	local text = input:GetValue()
	if text == private.filterText then
		return
	end
	private.filterText = text

	private.inboxQuery:ResetFilters()
		:Or()
			:Matches("itemList", private.filterText)
			:Matches("subject", private.filterText)
		:End()

	input:GetElement("__parent.__parent.mails"):UpdateData(true)
end

function private.FormatItem(row)
	private.itemsQuery = private.itemsQuery or MailTracking.CreateMailItemQuery()

	private.itemsQuery:ResetFilters()
		:Equal("index", row:GetField("index"))
		:GreaterThan("itemIndex", 0)
		:ResetOrderBy()
		:OrderBy("itemIndex", true)

	local items = ""
	local item = nil
	local same = true
	local qty = 0
	for _, itemsRow in private.itemsQuery:Iterator() do
		local coloredItem = TSM.UI.GetColoredItemName(itemsRow:GetField("itemLink")) or ""
		local quantity = itemsRow:GetField("quantity")

		if not item then
			item = coloredItem
		end

		if quantity > 1 then
			items = items..coloredItem..Theme.GetFeedbackColor("YELLOW"):ColorText(" (x"..quantity..")")..", "
		else
			items = items..coloredItem..", "
		end

		if item == coloredItem then
			qty = qty + quantity
		else
			same = false
		end
	end
	items = strtrim(items, ", ")

	if same and item then
		if qty > 1 then
			items = item..Theme.GetFeedbackColor("YELLOW"):ColorText(" (x"..qty..")")
		else
			items = item
		end
	end

	if not items or items == "" then
		local subject = row:GetField("subject")
		if subject ~= "" then
			items = gsub(row:GetField("subject"), strtrim(AUCTION_SOLD_MAIL_SUBJECT, "%s.*"), "") or "--"
		else
			local _, _, sender = GetInboxHeaderInfo(row:GetField("index"))
			items = sender or UNKNOWN
		end
	end

	return items
end

function private.FormatDaysLeft(timeLeft)
	local color = nil
	if timeLeft >= 1 then
		if timeLeft <= 5 then
			color = Theme.GetFeedbackColor("YELLOW")
			timeLeft = floor(timeLeft).." "..DAYS
		else
			color = Theme.GetFeedbackColor("GREEN")
			timeLeft = floor(timeLeft).." "..DAYS
		end
	else
		color = Theme.GetFeedbackColor("RED")
		local hoursLeft = floor(timeLeft * 24)
		if hoursLeft > 1 then
			timeLeft = hoursLeft.." "..L["Hrs"]
		elseif hoursLeft == 1 then
			timeLeft = hoursLeft.." "..L["Hr"]
		else
			timeLeft = floor(hoursLeft / 60).." "..L["Min"]
		end
	end
	return color:ColorText(timeLeft)
end

function private.FormatMoney(row)
	local money = row:GetField("money")
	local cod = row:GetField("cod")

	local gold = nil
	if cod > 0 then
		gold = Money.ToString(cod, Theme.GetFeedbackColor("RED"):GetTextColorPrefix())
	elseif money > 0 then
		gold = Money.ToString(money, Theme.GetFeedbackColor("GREEN"):GetTextColorPrefix())
	end

	return gold or "--"
end



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

function private.UpdateCountDown(force)
	if not private.frame then
		return
	end

	local nextUpdate = MAIL_REFRESH_TIME - (time() - TSM.Mailing.Open.GetLastCheckTime())
	if nextUpdate <= 0 then
		nextUpdate = MAIL_REFRESH_TIME
	end

	if nextUpdate ~= private.nextUpdate or force then
		private.frame:GetElement("top.reload"):SetFormattedText(L["Reload UI (%02d)"], Math.Round(nextUpdate))
			:Draw()
		private.nextUpdate = nextUpdate
	end
end



-- ============================================================================
-- FSM
-- ============================================================================

function private.FSMCreate()
	local fsmContext = {
		frame = nil,
		opening = false
	}

	Event.Register("PLAYER_REPORT_SUBMITTED", function()
		if not private.view or not private.view:GetElement("view") then
			return
		end
		private.view:GetElement("view")
			:SetPath("mails", true)
	end)

	local function UpdateText(context, filterText)
		local text = nil
		local numMail, totalMail = GetInboxNumItems()
		if filterText == "" then
			if totalMail > numMail then
				text = L["Showing %s of %d Mails"]
			elseif (numMail == 0 and totalMail == 0) or (numMail == 1 and totalMail == 1) then
				text = L["Showing %s Mail"]
			else
				text = L["Showing all %s Mails"]
			end
		else
			numMail = private.inboxQuery:Count()
			if (numMail == 0 and totalMail == 0) or (numMail == 1 and totalMail == 1) then
				text = L["Showing %s of %d Mail"]
			else
				text = L["Showing %s of %d Mails"]
			end
		end

		local totalItems, totalGold = 0, 0
		for _, row in private.inboxQuery:Iterator() do
			totalItems = totalItems + row:GetField("itemCount")
			totalGold = totalGold + row:GetField("money")
		end

		context.frame:GetElement("footer.mailNum")
			:SetText(format(text, Theme.GetFeedbackColor("GREEN"):ColorText(tostring(numMail)), totalMail))
			:Draw()
		context.frame:GetElement("footer.itemNum")
			:SetText(format(L["%s Items Total"], Theme.GetFeedbackColor("GREEN"):ColorText(totalItems)))
		context.frame:GetElement("footer.content.gold")
			:SetText(Money.ToString(totalGold))

		context.frame:GetElement("footer")
			:Draw()
	end

	local function UpdateButtons(context)
		if not context.frame or not context.frame:GetElement("top.filterInput") then
			return
		end

		if context.opening then
			context.frame:GetElement("bottom.openAllMail")
				:SetDisabled(true)
			context.frame:GetElement("bottom.buttons.openAllSales")
				:SetDisabled(true)
			context.frame:GetElement("bottom.buttons.openAllBuys")
				:SetDisabled(true)
			context.frame:GetElement("bottom.buttons.openAllCancels")
				:SetDisabled(true)
			context.frame:GetElement("bottom.buttons.openAllExpires")
				:SetDisabled(true)
			context.frame:GetElement("bottom.buttons.openAllOthers")
				:SetDisabled(true)

			context.frame:GetElement("bottom")
				:Draw()
		else
			local all, sales, buys, cancels, expires, other = 0, 0, 0, 0, 0, 0
			for _, row in private.inboxQuery:Iterator() do
				local iconType = row:GetField("icon")
				all = all + 1
				if iconType == "SALE" then
					sales = sales + 1
				elseif iconType == "BUY" then
					buys = buys + 1
				elseif iconType == "CANCEL" then
					cancels = cancels + 1
				elseif iconType == "EXPIRE" then
					expires = expires + 1
				elseif iconType == "OTHER" then
					other = other + 1
				end
			end

			context.frame:GetElement("bottom.openAllMail")
				:SetDisabled(all == 0)
				:SetPressed(false)
			context.frame:GetElement("bottom.buttons.openAllSales")
				:SetDisabled(sales == 0)
				:SetPressed(false)
			context.frame:GetElement("bottom.buttons.openAllBuys")
				:SetDisabled(buys == 0)
				:SetPressed(false)
			context.frame:GetElement("bottom.buttons.openAllCancels")
				:SetDisabled(cancels == 0)
				:SetPressed(false)
			context.frame:GetElement("bottom.buttons.openAllExpires")
				:SetDisabled(expires == 0)
				:SetPressed(false)
			context.frame:GetElement("bottom.buttons.openAllOthers")
				:SetDisabled(other == 0)
				:SetPressed(false)

			context.frame:GetElement("bottom")
				:Draw()
		end
	end

	private.fsm = FSM.New("MAILING_INBOX")
		:AddState(FSM.NewState("ST_HIDDEN")
			:SetOnEnter(function(context)
				TSM.Mailing.Open.KillThread()
				Delay.Cancel("mailUpdateCounter")
				context.frame = nil
			end)
			:AddTransition("ST_SHOWN")
			:AddTransition("ST_HIDDEN")
			:AddEventTransition("EV_FRAME_SHOW", "ST_SHOWN")
		)
		:AddState(FSM.NewState("ST_SHOWN")
			:SetOnEnter(function(context, frame, filterText)
				if not context.frame then
					context.frame = frame
					UpdateText(context, filterText)
					UpdateButtons(context)
				end
			end)
			:AddTransition("ST_HIDDEN")
			:AddTransition("ST_OPENING_START")
			:AddEvent("EV_MAIL_DATA_UPDATED", function(context, filterText)
				UpdateText(context, filterText)
				UpdateButtons(context)
			end)
			:AddEventTransition("EV_BUTTON_CLICKED", "ST_OPENING_START")
		)
		:AddState(FSM.NewState("ST_OPENING_START")
			:SetOnEnter(function(context, autoRefresh, keepMoney, filterText, filterType)
				context.opening = true
				UpdateButtons(context)
				TSM.Mailing.Open.StartOpening(private.FSMOpenCallback, autoRefresh, keepMoney, filterText, filterType)
			end)
			:SetOnExit(function(context)
				context.opening = false
				UpdateButtons(context)
			end)
			:AddTransition("ST_SHOWN")
			:AddTransition("ST_HIDDEN")
			:AddEvent("EV_MAIL_DATA_UPDATED", function(context, filterText)
				UpdateText(context, filterText)
			end)
			:AddEventTransition("EV_OPENING_DONE", "ST_SHOWN")
		)
		:AddDefaultEventTransition("EV_FRAME_HIDE", "ST_HIDDEN")
		:Init("ST_HIDDEN", fsmContext)
end

function private.FSMOpenCallback()
	private.fsm:ProcessEvent("EV_OPENING_DONE")

	Sound.PlaySound(private.settings.openMailSound)
end
