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

local _, TSM = ...
local DBViewer = TSM.UI:NewPackage("DBViewer")
local Database = TSM.Include("Util.Database")
local Log = TSM.Include("Util.Log")
local UIElements = TSM.Include("UI.UIElements")
local private = {
	frame = nil,
	frameContext = {},
	dividedContainerContext = {},
	selectedDBName = nil,
	structureScrollingTableContext = {},
	browseScrollingTableContext = {},
	defaultBrowseScrollingTableContext = { colWidth = {}, colHidden = {} },
}
local DEFAULT_FRAME_CONTEXT = {
	width = 900,
	height = 600,
	centerX = 100,
	centerY = 0,
	scale = 1,
}
local MIN_FRAME_SIZE = {
	width = 900,
	height = 600
}
local DEFAULT_DIVIDED_CONTAINER_CONTEXT = {
	leftWidth = 200,
}
local DEFAULT_STRUCTURE_SCROLLING_TABLE_CONTEXT = {
	colWidth = {
		order = 24,
		field = 450,
		type = 60,
		attributes = 96,
	},
	colHidden = {},
}



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

function DBViewer.OnDisable()
	-- hide the frame
	if private.frame then
		DBViewer.Toggle()
	end
end

function DBViewer.Toggle()
	if not private.frame then
		private.frame = private.CreateMainFrame()
		private.frame:Draw()
		private.frame:Show()
	else
		private.frame:Hide()
		assert(not private.frame)
	end
end



-- ============================================================================
-- UI Functions
-- ============================================================================

function private.CreateMainFrame()
	private.selectedDBName = nil
	return UIElements.New("ApplicationFrame", "base")
		:SetParent(UIParent)
		:SetMinResize(MIN_FRAME_SIZE.width, MIN_FRAME_SIZE.height)
		:SetContextTable(private.frameContext, DEFAULT_FRAME_CONTEXT)
		:SetStrata("HIGH")
		:SetTitle("TSM DB Viewer")
		:SetScript("OnHide", private.FrameOnHide)
		:SetContentFrame(UIElements.New("DividedContainer", "container")
			:SetContextTable(private.dividedContainerContext, DEFAULT_DIVIDED_CONTAINER_CONTEXT)
			:SetBackgroundColor("PRIMARY_BG")
			:SetMinWidth(100, 100)
			:SetLeftChild(UIElements.New("ScrollFrame", "left")
				:AddChildrenWithFunction(private.AddTableRows)
			)
			:SetRightChild(UIElements.New("Frame", "right")
				:SetLayout("VERTICAL")
			)
		)
end

function private.AddTableRows(frame)
	for _, name in Database.InfoNameIterator() do
		frame:AddChild(UIElements.New("Button", "nav_"..name)
			:SetHeight(20)
			:SetPadding(8, 0, 0, 0)
			:SetFont("BODY_BODY3")
			:SetJustifyH("LEFT")
			:SetHighlightEnabled(true)
			:SetText(name)
			:SetBackground("PRIMARY_BG")
			:SetScript("OnClick", private.NavButtonOnClick)
		)
	end
end

function private.CreateTableContent()
	local contentFrame = private.frame:GetElement("container.right")
	contentFrame:ReleaseAllChildren()
	contentFrame:AddChild(UIElements.New("TabGroup", "tabs")
		:SetMargin(0, 0, 4, 0)
		:SetNavCallback(private.ContentNavCallback)
		:AddPath("Structure", true)
		:AddPath("Browse")
	)
	contentFrame:Draw()
end

function private.ContentNavCallback(_, path)
	if path == "Structure" then
		return private.CreateStructureFrame()
	elseif path == "Browse" then
		return private.CreateBrowseFrame()
	else
		error("Invalid path: "..tostring(path))
	end
end

function private.CreateStructureFrame()
	local query = Database.CreateInfoFieldQuery(private.selectedDBName)
		:OrderBy("order", true)
	return UIElements.New("Frame", "structure")
		:SetLayout("VERTICAL")
		:AddChild(UIElements.New("Frame", "info")
			:SetLayout("HORIZONTAL")
			:SetHeight(20)
			:SetMargin(4)
			:AddChild(UIElements.New("Text", "numRows")
				:SetFont("BODY_BODY2")
				:SetText("Total Rows: "..Database.GetNumRows(private.selectedDBName))
			)
			:AddChild(UIElements.New("Text", "numRows")
				:SetFont("BODY_BODY2")
				:SetText("Active Queries: "..Database.GetNumActiveQueries(private.selectedDBName))
			)
		)
		:AddChild(UIElements.New("QueryScrollingTable", "table")
			:SetContextTable(private.structureScrollingTableContext, DEFAULT_STRUCTURE_SCROLLING_TABLE_CONTEXT)
			:GetScrollingTableInfo()
				:NewColumn("order")
					:SetTitle("#")
					:SetFont("ITEM_BODY3")
					:SetJustifyH("LEFT")
					:SetTextInfo("order")
					:SetSortInfo("order")
					:Commit()
				:NewColumn("field")
					:SetTitle("Field")
					:SetFont("ITEM_BODY3")
					:SetJustifyH("LEFT")
					:SetTextInfo("field")
					:SetSortInfo("field")
					:Commit()
				:NewColumn("type")
					:SetTitle("Type")
					:SetFont("ITEM_BODY3")
					:SetJustifyH("LEFT")
					:SetTextInfo("type")
					:SetSortInfo("type")
					:Commit()
				:NewColumn("attributes")
					:SetTitle("Attributes")
					:SetFont("ITEM_BODY3")
					:SetJustifyH("LEFT")
					:SetTextInfo("attributes")
					:SetSortInfo("attributes")
					:Commit()
				:Commit()
			:SetQuery(query)
			:SetAutoReleaseQuery(true)
			:SetSelectionDisabled(true)
		)
end

function private.CreateBrowseFrame()
	local query = Database.CreateDBQuery(private.selectedDBName)
	local fieldQuery = Database.CreateInfoFieldQuery(private.selectedDBName)
		:Select("field")
		:OrderBy("order", true)
	wipe(private.defaultBrowseScrollingTableContext.colWidth)
	for _, field in fieldQuery:Iterator() do
		private.defaultBrowseScrollingTableContext.colWidth[field] = 100
	end
	wipe(private.browseScrollingTableContext)

	local frame = UIElements.New("Frame", "browse")
		:SetLayout("VERTICAL")
		:AddChild(UIElements.New("Input", "queryInput")
			:SetHeight(26)
			:SetMargin(8)
			:SetBackgroundColor("PRIMARY_BG_ALT")
			:SetValue("query")
			:SetScript("OnEnterPressed", private.QueryInputOnEnterPressed)
		)
		:AddChild(UIElements.New("QueryScrollingTable", "table")
			:SetContextTable(private.browseScrollingTableContext, private.defaultBrowseScrollingTableContext)
			:SetContext(query)
			:SetQuery(query)
			:SetAutoReleaseQuery(true)
			:SetSelectionDisabled(true)
		)

	local stInfo = frame:GetElement("table"):GetScrollingTableInfo()
	for _, field in fieldQuery:Iterator() do
		stInfo:NewColumn(field)
			:SetTitle(field)
			:SetFont("ITEM_BODY3")
			:SetJustifyH("LEFT")
			:SetTextInfo(field, tostring)
			:SetTooltipInfo(field, private.TooltipFunc)
			:Commit()
	end
	fieldQuery:Release()
	stInfo:Commit()

	return frame
end



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

function private.FrameOnHide(frame)
	assert(frame == private.frame)
	private.frame:Release()
	private.frame = nil
end

function private.NavButtonOnClick(button)
	local navFrame = button:GetParentElement()
	private.selectedDBName = button:GetText()
	for _, name in Database.InfoNameIterator() do
		navFrame:GetElement("nav_"..name)
			:SetTextColor(name == private.selectedDBName and "INDICATOR" or "TEXT")
			:Draw()
	end
	private.CreateTableContent()
end

function private.QueryInputOnEnterPressed(input)
	local func, errStr = loadstring(input:GetValue())
	if not func then
		Log.PrintfUser("Failed to compile code: "..errStr)
		return
	end
	local tableElement = input:GetElement("__parent.table")
	local query = tableElement:GetContext()
	query:Reset()
	setfenv(func, { query = query })
	local ok, funcErrStr = pcall(func)
	if not ok then
		Log.PrintfUser("Failed to execute code: "..funcErrStr)
		return
	end
	tableElement:UpdateData(true)
end



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

function private.TooltipFunc(value)
	value = tostring(value)
	if strmatch(value, "item:") or strmatch(value, "battlepet:") or strmatch(value, "[ip]:") then
		-- this is an item string or item link
		return value
	else
		return "Value: "..value
	end
end
