local MAJOR_VERSION = "LibDogTag-3.0"
local MINOR_VERSION = tonumber(("20221026032717"):match("%d+")) or 33333333333333

if MINOR_VERSION > _G.DogTag_MINOR_VERSION then
	_G.DogTag_MINOR_VERSION = MINOR_VERSION
end

local _G, pairs, table, ipairs, type, tostring, math, GetTime = _G, pairs, table, ipairs, type, tostring, math, GetTime

-- #AUTODOC_NAMESPACE DogTag

DogTag_funcs[#DogTag_funcs+1] = function(DogTag)

local L = DogTag.L

local newList, newDict, newSet, del = DogTag.newList, DogTag.newDict, DogTag.newSet, DogTag.del

local helpFrame

local wow_ver = select(4, GetBuildInfo())
local wow_1000 = wow_ver >= 100000
--[[
Notes:
	This opens the in-game documentation, which provides information to users on syntax as well as the available tags and modifiers.
Example:
	LibStub("LibDogTag-3.0"):OpenHelp()
]]
function DogTag:OpenHelp()
	helpFrame = CreateFrame("Frame", MAJOR_VERSION .. "_HelpFrame", UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil)
	helpFrame:SetWidth(600)
	helpFrame:SetHeight(300)
	helpFrame:SetPoint("CENTER", UIParent, "CENTER")
	helpFrame:EnableMouse(true)
	helpFrame:SetMovable(true)
	helpFrame:SetResizable(true)
	if helpFrame.SetMinResize then
		helpFrame:SetMinResize(600, 300)
	elseif helpFrame.SetResizeBounds then
		helpFrame:SetResizeBounds(600, 300)
	end
	helpFrame:SetFrameLevel(50)
	helpFrame:SetFrameStrata("FULLSCREEN_DIALOG")

	local bg = newDict(
		'bgFile', [[Interface\DialogFrame\UI-DialogBox-Background]],
		'edgeFile', [[Interface\DialogFrame\UI-DialogBox-Border]],
		'tile', true,
		'tileSize', 32,
		'edgeSize', 32,
		'insets', newDict(
			'left', 5,
			'right', 6,
			'top', 5,
			'bottom', 6
		)
	)
	helpFrame:SetBackdrop(bg)
	bg.insets = del(bg.insets)
	bg = del(bg)
	helpFrame:SetBackdropColor(0, 0, 0)
	helpFrame:SetClampedToScreen(true)
	
	local header = CreateFrame("Frame", helpFrame:GetName() .. "_Header", helpFrame)
	helpFrame.header = header
	header:SetHeight(34.56)
	header:SetClampedToScreen(true)
	local left = header:CreateTexture(header:GetName() .. "_TextureLeft", "ARTWORK")
	header.left = left
	left:SetPoint("TOPLEFT")
	left:SetPoint("BOTTOMLEFT")
	left:SetWidth(11.54)
	left:SetTexture([[Interface\DialogFrame\UI-DialogBox-Header]])
	left:SetTexCoord(0.235, 0.28, 0.04, 0.58)
	local right = header:CreateTexture(header:GetName() .. "_TextureRight", "ARTWORK")
	header.right = right
	right:SetPoint("TOPRIGHT")
	right:SetPoint("BOTTOMRIGHT")
	right:SetWidth(11.54)
	right:SetTexture([[Interface\DialogFrame\UI-DialogBox-Header]])
	right:SetTexCoord(0.715, 0.76, 0.04, 0.58)
	local center = header:CreateTexture(header:GetName() .. "_TextureCenter", "ARTWORK")
	header.center = center
	center:SetPoint("TOPLEFT", left, "TOPRIGHT")
	center:SetPoint("BOTTOMRIGHT", right, "BOTTOMLEFT")
	center:SetTexture([[Interface\DialogFrame\UI-DialogBox-Header]])
	center:SetTexCoord(0.28, 0.715, 0.04, 0.58)
	
	local closeButton = CreateFrame("Button", helpFrame:GetName() .. "_CloseButton", helpFrame, "UIPanelCloseButton")
	helpFrame.closeButton = closeButton
	closeButton:SetFrameLevel(helpFrame:GetFrameLevel()+5)
	closeButton:SetScript("OnClick", function(this)
		this:GetParent():Hide()
	end)
	closeButton:SetPoint("TOPRIGHT", helpFrame, "TOPRIGHT", -5, -5)
	
	local isDragging
	header:EnableMouse(true)
	header:RegisterForDrag("LeftButton")
	header:SetScript("OnDragStart", function(this)
		isDragging = true
		this:GetParent():StartMoving()
	end)
	header:SetScript("OnDragStop", function(this)
		isDragging = false
		this:GetParent():StopMovingOrSizing()
	end)

	local titleText = header:CreateFontString(header:GetName() .. "_FontString", "OVERLAY", "GameFontNormal")
	helpFrame.titleText = titleText
	titleText:SetText(L["DogTag Help"])
	titleText:SetPoint("CENTER", helpFrame, "TOP", 0, -8)
	titleText:SetHeight(26)
	titleText:SetShadowColor(0, 0, 0)
	titleText:SetShadowOffset(1, -1)

	header:SetPoint("LEFT", titleText, "LEFT", -32, 0)
	header:SetPoint("RIGHT", titleText, "RIGHT", 32, 0)
	
	local sizer_se = CreateFrame("Frame", helpFrame:GetName() .. "_SizerSoutheast", helpFrame)
	helpFrame.sizer_se = sizer_se
	sizer_se:SetPoint("BOTTOMRIGHT", helpFrame, "BOTTOMRIGHT", 0, 0)
	sizer_se:SetWidth(25)
	sizer_se:SetHeight(25)
	sizer_se:EnableMouse(true)
	sizer_se:RegisterForDrag("LeftButton")
	sizer_se:SetScript("OnDragStart", function(this)
		isDragging = true
		this:GetParent():StartSizing("BOTTOMRIGHT")
	end)
	sizer_se:SetScript("OnDragStop", function(this)
		isDragging = false
		this:GetParent():StopMovingOrSizing()
	end)
	local line1 = sizer_se:CreateTexture(sizer_se:GetName() .. "_Line1", "BACKGROUND")
	line1:SetWidth(14)
	line1:SetHeight(14)
	line1:SetPoint("BOTTOMRIGHT", -10, 10)
	line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
	local x = 0.1 * 14/17
	line1:SetTexCoord(1/32 - x, 0.5, 1/32, 0.5 + x, 1/32, 0.5 - x, 1/32 + x, 0.5)

	local line2 = sizer_se:CreateTexture(sizer_se:GetName() .. "_Line2", "BACKGROUND")
	line2:SetWidth(11)
	line2:SetHeight(11)
	line2:SetPoint("BOTTOMRIGHT", -10, 10)
	line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
	local x = 0.1 * 11/17
	line2:SetTexCoord(1/32 - x, 0.5, 1/32, 0.5 + x, 1/32, 0.5 - x, 1/32 + x, 0.5)

	local line3 = sizer_se:CreateTexture(sizer_se:GetName() .. "_Line3", "BACKGROUND")
	line3:SetWidth(8)
	line3:SetHeight(8)
	line3:SetPoint("BOTTOMRIGHT", -10, 10)
	line3:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
	local x = 0.1 * 8/17
	line3:SetTexCoord(1/32 - x, 0.5, 1/32, 0.5 + x, 1/32, 0.5 - x, 1/32 + x, 0.5)
	
	local treeView = CreateFrame("Frame", helpFrame:GetName() .. "_TreeView", helpFrame, BackdropTemplateMixin and "BackdropTemplate" or nil)
	helpFrame.treeView = treeView
	local bg = newDict(
		'bgFile', [[Interface\Buttons\WHITE8X8]],
		'edgeFile', [[Interface\Tooltips\UI-Tooltip-Border]],
		'tile', true,
		'tileSize', 16,
		'edgeSize', 16,
		'insets', newDict(
			'left', 3,
			'right', 3,
			'top', 3,
			'bottom', 3
		)
	)
	treeView:SetBackdrop(bg)
	bg.insets = del(bg.insets)
	bg = del(bg)
	treeView:SetBackdropBorderColor(0.6, 0.6, 0.6)
	treeView:SetBackdropColor(0, 0, 0)
	local scrollFrame = CreateFrame("ScrollFrame", treeView:GetName() .. "_ScrollFrame", treeView)
	local scrollChild = CreateFrame("Frame", scrollFrame:GetName() .. "_Frame", scrollFrame)
	local scrollBar = CreateFrame("Slider", scrollFrame:GetName() .. "_ScrollBar", scrollFrame, "UIPanelScrollBarTemplate")
	treeView.scrollFrame = scrollFrame
	treeView.scrollChild = scrollChild
	treeView.scrollBar = scrollBar
	
	local html

	scrollFrame:SetScrollChild(scrollChild)
	scrollFrame:SetPoint("TOPLEFT", treeView, "TOPLEFT", 8, -12)
	scrollFrame:SetPoint("BOTTOMRIGHT", treeView, "BOTTOMRIGHT", -28, 12)
	scrollFrame:EnableMouseWheel(true)
	scrollFrame:SetScript("OnMouseWheel", function(this, change)
		local childHeight = scrollChild:CalculateHeight()
		local frameHeight = scrollFrame:GetHeight()
		if childHeight <= frameHeight then
			return
		end

		local diff = frameHeight - childHeight

		local delta = 1
		if change < 0 then
			delta = -1
		end

		local value = scrollBar:GetValue() + delta*(24/diff)
		if value < 0 then
			value = 0
		elseif value > 1 then
			value = 1
		end
		scrollBar:SetValue(value) -- will trigger OnValueChanged
	end)

	scrollChild:SetHeight(1)
	scrollChild:SetWidth(1)

	function scrollChild:CalculateHeight()
		local top = self:GetTop()
		if not top then
			return 0
		end

		local current = treeView

		local bottom = top

		local children = current.children
		while children do
			local child
			for i,v in ipairs(children) do
				if v:IsShown() then
					local b = v:GetBottom()
					if b and b < bottom then
						bottom = b
						child = v
					end
				end
			end
			if not child then
				break
			end
			current = child
			children = current.children
		end

		return top - bottom
	end

	scrollBar:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", 0, -16)
	scrollBar:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", 0, 16)
	scrollBar:SetMinMaxValues(0, 1)
	scrollBar:SetValueStep(1e-5)
	scrollBar:SetValue(0)
	scrollBar:SetWidth(16)
	scrollBar:SetScript("OnValueChanged", function(this)
		local max = scrollChild:CalculateHeight() - scrollFrame:GetHeight()

		local val = scrollBar:GetValue() * max

		if math.abs(scrollFrame:GetVerticalScroll() - val) < 1 then
			return
		end

		scrollFrame:SetVerticalScroll(val)
	end)
	scrollBar:EnableMouseWheel(true)
	scrollBar:SetScript("OnMouseWheel", function(this, ...)
		scrollFrame:GetScript("OnMouseWheel")(scrollFrame, ...)
	end)
	
	local function reposition(children, last, depth)
		if not children then
			return last
		end
		for i, v in ipairs(children) do
			v:Show()
			v:ClearAllPoints()
			v:SetPoint("LEFT", scrollFrame, "LEFT", depth*16, 0)
			v:SetPoint("RIGHT", scrollFrame, "RIGHT")
			if not last then
				v:SetPoint("TOP", scrollChild, "TOP")
			else
				v:SetPoint("TOP", last, "BOTTOM")
			end
			last = v
			if v.expand and v.expand:GetNormalTexture():GetTexture() ~= [[Interface\Buttons\UI-PlusButton-Up]] then
				last = reposition(v.children, last, depth+1)
			elseif v.children then
				for i, child in ipairs(v.children) do
					child:Hide()
				end
			end
		end
		return last
	end
	
	treeView.children = {}
	function treeView:Reposition()
		reposition(self.children, nil, 0)
		
		local height = scrollChild:CalculateHeight()
		
		if height < scrollFrame:GetHeight() then
			scrollBar:Hide()
			scrollFrame:SetPoint("BOTTOMRIGHT", treeView, "BOTTOMRIGHT", -8, 12)
			scrollBar:SetValue(0)
			scrollFrame:SetVerticalScroll(0)
		else
			scrollBar:Show()
			scrollFrame:SetPoint("BOTTOMRIGHT", treeView, "BOTTOMRIGHT", -28, 12)
		end
	end
	
	local selectedTreeLine
	local function TreeLine_OnEnter(this)
		if this:IsEnabled() then
			this.highlight:SetAlpha(1)
			this.highlight:Show()
		end
	end
	local function TreeLine_OnLeave(this)
		if this ~= selectedTreeLine then
			this.highlight:Hide()
		else
			this.highlight:SetAlpha(0.7)
		end
		GameTooltip:Hide()
	end
	
	local function TreeLine_Unselect(this)
		this.highlight:Hide()
		selectedTreeLine = nil
	end
	
	local function TreeLine_OnExpandClick(this, openOnly)
		local treeLine = this:GetParent()
		local tex = this:GetNormalTexture():GetTexture()
		local expand = (tex == [[Interface\Buttons\UI-PlusButton-Up]])
		if openOnly == true or openOnly == false then
			if openOnly then
				if not expand then
					return
				end
			else
				if expand then
					return
				end
			end
		end
		if expand then
			this:SetNormalTexture([[Interface\Buttons\UI-MinusButton-UP]])
			this:SetPushedTexture([[Interface\Buttons\UI-MinusButton-Down]])
			this:SetDisabledTexture([[Interface\Buttons\UI-MinusButton-Disabled]])
			this:SetHighlightTexture([[Interface\Buttons\UI-MinusButton-Hilight]])
		else
			this:SetNormalTexture([[Interface\Buttons\UI-PlusButton-Up]])
			this:SetPushedTexture([[Interface\Buttons\UI-PlusButton-Down]])
			this:SetDisabledTexture([[Interface\Buttons\UI-PlusButton-Disabled]])
			this:SetHighlightTexture([[Interface\Buttons\UI-PlusButton-Hilight]])
		end
		treeView:Reposition()

		local scrollValue = treeView.scrollFrame:GetVerticalScroll()

		local max = treeView.scrollChild:CalculateHeight() - treeView.scrollFrame:GetHeight()

		treeView.scrollBar:SetValue(scrollValue/max)
	end
	
	local function TreeLine_OnClick(this)
		if not this:IsEnabled() then
			return
		end
		
		local last_selectedTreeLine = selectedTreeLine
		if selectedTreeLine then
			TreeLine_Unselect(selectedTreeLine)
		end
		
		selectedTreeLine = this
		if GetMouseFocus() == this then
			this.highlight:SetAlpha(1)
		else
			this.highlight:SetAlpha(0.7)
		end
		this.highlight:Show()
		
		if this.expand then
			if this == last_selectedTreeLine then
				this.expand:Click()
			else
				TreeLine_OnExpandClick(this.expand, true)
			end
		end
		
		local data = this.htmlFunc()
		html.text = data
		html:SetText(data)
		
		helpFrame.mainPane.scrollBar:SetValue(0)
	end
	
	local treeLineNum = 0
	local function getTreeLine(hasExpand, parent, name, htmlFunc)
		treeLineNum = treeLineNum + 1
		local frame = CreateFrame("Button", treeView:GetName() .. "_TreeLine" .. treeLineNum, parent == treeView and treeView.scrollChild or parent)
		table.insert(parent.children, frame)
		local text = frame:CreateFontString(frame:GetName() .. "_Text", "OVERLAY", "GameFontHighlight")
		frame.text = text
		text:SetJustifyH("LEFT")
		frame:SetFontString(text)
		text:SetText(name)
		frame:SetNormalFontObject(GameFontHighlight)
		frame:SetHeight(16)
		frame:EnableMouse(true)
		frame:SetScript("OnEnter", TreeLine_OnEnter)
		frame:SetScript("OnLeave", TreeLine_OnLeave)
		frame:SetScript("OnClick", TreeLine_OnClick)
		
		local highlight = frame:CreateTexture(frame:GetName() .. "_Highlight", "BACKGROUND")
		frame.highlight = highlight
		highlight:SetTexture([[Interface\QuestFrame\UI-QuestTitleHighlight]])
		highlight:SetBlendMode("ADD")
		highlight:SetAllPoints(frame)
		highlight:Hide()
		
		local expand
		if hasExpand then
			expand = CreateFrame("Button", frame:GetName() .. "_Expand", frame)
			frame.expand = expand
			expand:SetNormalTexture([[Interface\Buttons\UI-PlusButton-Up]])
			expand:SetPushedTexture([[Interface\Buttons\UI-PlusButton-Down]])
			expand:SetDisabledTexture([[Interface\Buttons\UI-PlusButton-Disabled]])
			expand:SetHighlightTexture([[Interface\Buttons\UI-PlusButton-Hilight]])
			expand:SetScript("OnClick", TreeLine_OnExpandClick)
			expand:SetWidth(16)
			expand:SetHeight(16)
		
			expand:SetPoint("LEFT", frame, "LEFT", 0, 0)
			text:SetPoint("LEFT", expand, "RIGHT", 1, 0)
			frame.children = {}
		else
			text:SetPoint("LEFT", frame, "LEFT", 17, 0)
		end
		text:SetPoint("RIGHT", frame, "RIGHT", 0, 0)
		
		frame.htmlFunc = htmlFunc
		
		return frame
	end
	
	local mainPane = CreateFrame("Frame", helpFrame:GetName() .. "_MainPane", helpFrame, BackdropTemplateMixin and "BackdropTemplate" or nil)
	helpFrame.mainPane = mainPane
	local bg = newDict(
		'bgFile', [[Interface\Buttons\WHITE8X8]],
		'edgeFile', [[Interface\Tooltips\UI-Tooltip-Border]],
		'tile', true,
		'tileSize', 16,
		'edgeSize', 16,
		'insets', newDict(
			'left', 3,
			'right', 3,
			'top', 3,
			'bottom', 3
		)
	)
	mainPane:SetBackdrop(bg)
	bg.insets = del(bg.insets)
	bg = del(bg)
	mainPane:SetBackdropBorderColor(0.6, 0.6, 0.6)
	mainPane:SetBackdropColor(0, 0, 0)
	mainPane:SetPoint("TOPLEFT", treeView, "TOPRIGHT", -3, 0)
	mainPane:SetPoint("RIGHT", helpFrame, "RIGHT", -12, 0)
	mainPane:SetPoint("BOTTOM", treeView, "BOTTOM")
	
	local scrollFrame = CreateFrame("ScrollFrame", mainPane:GetName() .. "_ScrollFrame", mainPane)
	local scrollChild = CreateFrame("Frame", mainPane:GetName() .. "_ScrollChild", scrollFrame)
	local scrollBar = CreateFrame("Slider", mainPane:GetName() .. "_ScrollBar", scrollFrame, "UIPanelScrollBarTemplate")
	mainPane.scrollFrame = scrollFrame
	mainPane.scrollChild = scrollChild
	mainPane.scrollBar = scrollBar

	scrollFrame:SetScrollChild(scrollChild)
	scrollFrame:SetPoint("TOPLEFT", mainPane, "TOPLEFT", 9, -9)
	scrollFrame:SetPoint("BOTTOMRIGHT", mainPane, "BOTTOMRIGHT", -28, 12)
	scrollFrame:EnableMouseWheel(true)
	scrollFrame:SetScript("OnMouseWheel", function(this, change)
		local childHeight = scrollChild:CalculateHeight()
		local frameHeight = scrollFrame:GetHeight()
		if childHeight <= frameHeight then
			return
		end

		-- nextFreeScroll = GetTime() + 1 -- unused

		local diff = childHeight - frameHeight

		local delta = 1
		if change > 0 then
			delta = -1
		end

		local value = scrollBar:GetValue() + delta*24/diff
		if value < 0 then
			value = 0
		elseif value > 1 then
			value = 1
		end
		scrollBar:SetValue(value) -- will trigger OnValueChanged
	end)

	scrollChild:SetHeight(10)
	scrollChild:SetWidth(10)

	local first = true
	function scrollChild:CalculateHeight()
		local html = self.html
		local t = newList(html:GetRegions())
		local top, bottom
		for i,v in ipairs(t) do
			if v:GetTop() and (not top or top < v:GetTop()) then
				top = v:GetTop()
			end
			if v:GetBottom() and (not bottom or bottom > v:GetBottom()) then
				bottom = v:GetBottom()
			end
		end
		t = del(t)
		return top and (top - bottom) or 10
	end
	_G.scrollChild = scrollChild

	scrollBar:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", 0, -16)
	scrollBar:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", 0, 16)
	scrollBar:SetMinMaxValues(0, 1)
	scrollBar:SetValueStep(1e-5)
	scrollBar:SetValue(0)
	scrollBar:SetWidth(16)
	scrollBar:SetScript("OnValueChanged", function(this)
		local max = scrollChild:CalculateHeight() - scrollFrame:GetHeight()

		local val = scrollBar:GetValue() * max
		
		if math.abs(scrollFrame:GetVerticalScroll() - val) < 1 then
			return
		end

		scrollFrame:SetVerticalScroll(val)

		scrollFrame:UpdateScrollChildRect()
	end)
	scrollBar:EnableMouseWheel(true)
	scrollBar:SetScript("OnMouseWheel", function(this, ...)
		scrollFrame:GetScript("OnMouseWheel")(scrollFrame, ...)
	end)
	
	html = CreateFrame("SimpleHTML", scrollChild:GetName() .. "_HTML", scrollChild)
	scrollChild.html = html
	html:SetFontObject('p', GameFontNormal)
	html:SetSpacing('p', 3)
	
	html:SetFontObject('h1', GameFontHighlightLarge)
	local font, height, flags = GameFontHighlightLarge:GetFont()
	height = height * 1.25
	html:SetFont('h1', font, height, flags)
	html:SetSpacing('h1', height/2)
	
	html:SetFontObject('h2', GameFontHighlightLarge)
	local _, height = GameFontHighlightLarge:GetFont()
	html:SetSpacing('h2', height/2)
	
	html:SetFontObject('h3', GameFontHighlightLarge)
	local _, height = GameFontHighlightLarge:GetFont()
	height = height * 14/16
	html:SetFont('h3', font, height, flags)
	html:SetSpacing('h3', 0)
	
	html:SetHeight(1)
	html:SetWidth(400)
	html:SetPoint("TOPLEFT", 0, 0)
	-- Parnic: left/top seems to be the default, but these functions want a text type in 10.0
	-- so SetJustifyH("P", "LEFT"), for example. instead of trying to set all font types to
	-- what appears to be the default, just skip it entirely
	if not wow_1000 then
		html:SetJustifyH("LEFT")
		html:SetJustifyV("TOP")
	end
	
	local searchBox = CreateFrame("EditBox", helpFrame:GetName() .. "_SearchBox", helpFrame)
	searchBox:SetFontObject(ChatFontNormal)
	searchBox:SetHeight(17)
	searchBox:SetAutoFocus(false)
	
	local searchBox_bg = searchBox:CreateTexture(searchBox:GetName() .. "_Background", "BACKGROUND")
	searchBox_bg:SetTexture(0, 0, 0)
	searchBox_bg:SetPoint("BOTTOMLEFT", 1, 1)
	searchBox_bg:SetPoint("TOPRIGHT", -1, -1)
	
	local searchBox_line1 = searchBox:CreateTexture(searchBox:GetName() .. "_Line1", "BACKGROUND")
	searchBox_line1:SetTexture([[Interface\Buttons\WHITE8X8]])
	searchBox_line1:SetHeight(1)
	searchBox_line1:SetPoint("TOPLEFT", searchBox, "BOTTOMLEFT", 0, 1)
	searchBox_line1:SetPoint("TOPRIGHT", searchBox, "BOTTOMRIGHT", 0, 1)
	searchBox_line1:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	local searchBox_line2 = searchBox:CreateTexture(searchBox:GetName() .. "_Line2", "BACKGROUND")
	searchBox_line2:SetTexture([[Interface\Buttons\WHITE8X8]])
	searchBox_line2:SetWidth(1)
	searchBox_line2:SetPoint("TOPLEFT", searchBox, "TOPRIGHT", -1, 0)
	searchBox_line2:SetPoint("BOTTOMLEFT", searchBox, "BOTTOMRIGHT", -1, 0)
	searchBox_line2:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	local searchBox_line3 = searchBox:CreateTexture(searchBox:GetName() .. "_Line3", "BACKGROUND")
	searchBox_line3:SetTexture([[Interface\Buttons\WHITE8X8]])
	searchBox_line3:SetHeight(1)
	searchBox_line3:SetPoint("BOTTOMLEFT", searchBox, "TOPLEFT", 0, -1)
	searchBox_line3:SetPoint("BOTTOMRIGHT", searchBox, "TOPRIGHT", 0, -1)
	searchBox_line3:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local searchBox_line4 = searchBox:CreateTexture(searchBox:GetName() .. "_Line4", "BACKGROUND")
	searchBox_line4:SetTexture([[Interface\Buttons\WHITE8X8]])
	searchBox_line4:SetWidth(1)
	searchBox_line4:SetPoint("TOPRIGHT", searchBox, "TOPLEFT", 1, 0)
	searchBox_line4:SetPoint("BOTTOMRIGHT", searchBox, "BOTTOMLEFT", 1, 0)
	searchBox_line4:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local editBox = CreateFrame("EditBox", helpFrame:GetName() .. "_EditBox", helpFrame)
	editBox:SetFontObject(ChatFontNormal)
	editBox:SetMultiLine(true)
	editBox:SetAutoFocus(false)
	
	local editBox_bg = editBox:CreateTexture(editBox:GetName() .. "_Background", "BACKGROUND")
	editBox_bg:SetTexture(0, 0, 0)
	editBox_bg:SetPoint("BOTTOMLEFT", 1, 1)
	editBox_bg:SetPoint("TOPRIGHT", -1, -1)
	
	local editBox_label = editBox:CreateFontString(editBox:GetName() .. "_Label", "ARTWORK", "GameFontHighlightSmall")
	editBox_label:SetText(L["Test area:"])
	editBox_label:SetPoint("BOTTOMLEFT", editBox, "TOPLEFT", 0, 3)
	
	local editBox_line1 = editBox:CreateTexture(editBox:GetName() .. "_Line1", "BACKGROUND")
	editBox_line1:SetTexture([[Interface\Buttons\WHITE8X8]])
	editBox_line1:SetHeight(1)
	editBox_line1:SetPoint("TOPLEFT", editBox, "BOTTOMLEFT", 0, 1)
	editBox_line1:SetPoint("TOPRIGHT", editBox, "BOTTOMRIGHT", 0, 1)
	editBox_line1:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	local editBox_line2 = editBox:CreateTexture(editBox:GetName() .. "_Line2", "BACKGROUND")
	editBox_line2:SetTexture([[Interface\Buttons\WHITE8X8]])
	editBox_line2:SetWidth(1)
	editBox_line2:SetPoint("TOPLEFT", editBox, "TOPRIGHT", -1, 0)
	editBox_line2:SetPoint("BOTTOMLEFT", editBox, "BOTTOMRIGHT", -1, 0)
	editBox_line2:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	local editBox_line3 = editBox:CreateTexture(editBox:GetName() .. "_Line3", "BACKGROUND")
	editBox_line3:SetTexture([[Interface\Buttons\WHITE8X8]])
	editBox_line3:SetHeight(1)
	editBox_line3:SetPoint("BOTTOMLEFT", editBox, "TOPLEFT", 0, -1)
	editBox_line3:SetPoint("BOTTOMRIGHT", editBox, "TOPRIGHT", 0, -1)
	editBox_line3:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local editBox_line4 = editBox:CreateTexture(editBox:GetName() .. "_Line4", "BACKGROUND")
	editBox_line4:SetTexture([[Interface\Buttons\WHITE8X8]])
	editBox_line4:SetWidth(1)
	editBox_line4:SetPoint("TOPRIGHT", editBox, "TOPLEFT", 1, 0)
	editBox_line4:SetPoint("BOTTOMRIGHT", editBox, "BOTTOMLEFT", 1, 0)
	editBox_line4:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local currentUnit = "player"
	
	local fontString_holder = CreateFrame("Frame", helpFrame:GetName() .. "_FontString_Holder", helpFrame)
	fontString_holder:SetPoint("LEFT", editBox, "RIGHT", 20, 0)
	fontString_holder:SetPoint("RIGHT", helpFrame, "RIGHT", -20, 0)
	fontString_holder:SetPoint("BOTTOM", editBox, "BOTTOM", 0, 0)
	fontString_holder:SetPoint("TOP", editBox, "TOP", 0, -20)
	local bg = fontString_holder:CreateTexture(fontString_holder:GetName() .. "_Background", "BACKGROUND")
	bg:SetPoint("BOTTOMLEFT", 1, 1)
	bg:SetPoint("TOPRIGHT", -1, -1)
	bg:SetTexture(0.1, 0.1, 0.1, 0.5)
	
	local fontString = fontString_holder:CreateFontString(helpFrame:GetName() .. "_FontString", "ARTWORK")
	fontString:SetAllPoints()
	fontString:SetFontObject(ChatFontNormal)
	
	local fontString_label = helpFrame:CreateFontString(fontString:GetName() .. "_Label", "ARTWORK", "GameFontHighlightSmall")
	fontString_label:SetText(L["Output:"])
	fontString_label:SetPoint("BOTTOMLEFT", fontString, "TOPLEFT", 0, 3)
	
	local fontString_line1 = fontString_holder:CreateTexture(fontString:GetName() .. "_Line1", "BACKGROUND")
	fontString_line1:SetTexture([[Interface\Buttons\WHITE8X8]])
	fontString_line1:SetHeight(1)
	fontString_line1:SetPoint("TOPLEFT", fontString_holder, "BOTTOMLEFT", 0, 1)
	fontString_line1:SetPoint("TOPRIGHT", fontString_holder, "BOTTOMRIGHT", 0, 1)
	fontString_line1:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local fontString_line2 = fontString_holder:CreateTexture(fontString:GetName() .. "_Line2", "BACKGROUND")
	fontString_line2:SetTexture([[Interface\Buttons\WHITE8X8]])
	fontString_line2:SetWidth(1)
	fontString_line2:SetPoint("TOPLEFT", fontString_holder, "TOPRIGHT", -1, 0)
	fontString_line2:SetPoint("BOTTOMLEFT", fontString_holder, "BOTTOMRIGHT", -1, 0)
	fontString_line2:SetVertexColor(3/8, 3/8, 3/8, 1)
	
	local fontString_line3 = fontString_holder:CreateTexture(fontString:GetName() .. "_Line3", "BACKGROUND")
	fontString_line3:SetTexture([[Interface\Buttons\WHITE8X8]])
	fontString_line3:SetHeight(1)
	fontString_line3:SetPoint("BOTTOMLEFT", fontString_holder, "TOPLEFT", 0, -1)
	fontString_line3:SetPoint("BOTTOMRIGHT", fontString_holder, "TOPRIGHT", 0, -1)
	fontString_line3:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	local fontString_line4 = fontString_holder:CreateTexture(fontString:GetName() .. "_Line4", "BACKGROUND")
	fontString_line4:SetTexture([[Interface\Buttons\WHITE8X8]])
	fontString_line4:SetWidth(1)
	fontString_line4:SetPoint("TOPRIGHT", fontString_holder, "TOPLEFT", 1, 0)
	fontString_line4:SetPoint("BOTTOMRIGHT", fontString_holder, "BOTTOMLEFT", 1, 0)
	fontString_line4:SetVertexColor(3/4, 3/4, 3/4, 1)
	
	editBox:SetScript("OnEscapePressed", function(this)
		this:ClearFocus()
		this:SetText(DogTag:CleanCode(this:GetText()))
	end)
	
	editBox:SetScript("OnEnterPressed", editBox:GetScript("OnEscapePressed"))
	
	local function updateFontString(text)
		text = text or editBox:GetText():gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
		local kwargs = newList()
		kwargs.unit = currentUnit
		DogTag:AddFontString(fontString, helpFrame, text, "Unit", kwargs)
		kwargs = del(kwargs)
	end
	
	local fs = helpFrame:CreateFontString(nil, "ARTWORK")
	local lastText
	editBox:SetScript("OnTextChanged", function(this)
		local colorText = this:GetText()
		if lastText == colorText then
			return
		end
		local text = colorText:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "")
		updateFontString(text)
		
		lastText = DogTag:ColorizeCode(text):gsub("|r", "")
		local position = this:GetCursorPosition()
		local skip = 0
		for i = 1, position do
			if colorText:byte(i) == ("|"):byte() then
				if colorText:byte(i+1) == ("c"):byte() then
					skip = skip + 10
				elseif colorText:byte(i+1) == ("r"):byte() then
					skip = skip + 2
				end
			end
		end
		position = position - skip
		this:SetText(lastText)
		
		local betterPosition = 0
		for i = 1, position do
			betterPosition = betterPosition + 1
			while lastText:byte(betterPosition) == ("|"):byte() do
				if lastText:byte(betterPosition+1) == ("c"):byte() then
					betterPosition = betterPosition + 10
				elseif lastText:byte(betterPosition+1) == ("r"):byte() then
					betterPosition = betterPosition + 2
				else
					break
				end
			end
		end
		
		this:SetCursorPosition(betterPosition)
		
		fs:SetFontObject(this:GetFontObject())
		fs:SetWidth(this:GetWidth())
		fs:SetText(text)
		local height = fs:GetHeight() + 6
		if height < 42 then
			height = 42
		end
		editBox:SetPoint("TOPRIGHT", helpFrame, "BOTTOM", -10, 16 + height)
	end)
	
	editBox:SetText("[Name]")
	
	searchBox:SetScript("OnEscapePressed", function(this)
		this:ClearFocus()
	end)
	searchBox:SetScript("OnEnterPressed", searchBox:GetScript("OnEscapePressed"))
	
	local dropdown = CreateFrame("Frame", helpFrame:GetName() .. "_DropDown", helpFrame, "UIDropDownMenuTemplate")
	local dropdown_label = dropdown:CreateFontString(dropdown:GetName() .. "_Label", "ARTWORK", "GameFontHighlightSmall")
	dropdown_label:SetPoint("RIGHT", dropdown, "LEFT", 20, 0)
	dropdown_label:SetText(L["Test on: "])
	
	local function dropdown_OnClick(this)
		UIDropDownMenu_SetSelectedValue(dropdown, this.value)
		currentUnit = this.value
		updateFontString()
	end
	UIDropDownMenu_Initialize(dropdown, function()
		local info = newList()
		info.text = L["Player"]
		info.value = "player"
		info.func = dropdown_OnClick
		UIDropDownMenu_AddButton(info)
		info = del(info)
		
		local info = newList()
		info.text = L["Target"]
		info.value = "target"
		info.func = dropdown_OnClick
		UIDropDownMenu_AddButton(info)
		info = del(info)
		
		local info = newList()
		info.text = L["Pet"]
		info.value = "pet"
		info.func = dropdown_OnClick
		UIDropDownMenu_AddButton(info)
		info = del(info)
	end)
	UIDropDownMenu_SetSelectedValue(dropdown, currentUnit)
	
	scrollFrame:SetScript("OnSizeChanged", function(this)
		html:SetWidth(this:GetWidth())
		html:SetText(html.text)
		editBox:SetWidth(this:GetWidth()*1/2 - 20)
	end)
	
	editBox:SetPoint("BOTTOMLEFT", helpFrame, "BOTTOMLEFT", 16, 16)
	editBox:SetPoint("TOPRIGHT", helpFrame, "BOTTOM", -10, 55)
	treeView:SetPoint("TOPLEFT", helpFrame, "TOPLEFT", 12, -35)
	treeView:SetPoint("RIGHT", helpFrame, "LEFT", 162, 0)
	treeView:SetPoint("BOTTOM", editBox, "TOP", 0, 15)
	dropdown:SetPoint("BOTTOMRIGHT", fontString, "TOPRIGHT", -110, 0)
	
	local searchBox_text = searchBox:CreateFontString(searchBox:GetName() .. "_Text", "ARTWORK", "GameFontHighlightSmall")
	searchBox_text:SetPoint("TOPLEFT", helpFrame, "TOPLEFT", 14, -16)
	searchBox_text:SetText(L["Search:"])
	searchBox:SetPoint("LEFT", searchBox_text, "RIGHT", 3, 0)
	searchBox:SetWidth(120)
	
	local function escapeHTML__handler(c)
		if c == "&" then
			return "&amp;"
		elseif c == "<" then
			return "&lt;"
		elseif c == ">" then
			return "&gt;"
		end
	end
	local function escapeHTML(text)
		return (text:gsub("([&<>])", escapeHTML__handler))
	end
	
	local function _fix__handler(text)
		if text:sub(1, 2) == "{{" and text:sub(-2) == "}}" then
			local x = text:sub(3, -3)
			x = "[" .. x .. "]"
			x = DogTag:ColorizeCode(x)
			-- Find the first opening bracket in the colored string,
			-- which is the one we just prepended to `x`.
			local first = string.find(x, "|cff%x%x%x%x%x%x%[") + 11
			-- Find the last closing bracket in the colored string,
			-- which is the one we just appended to `x`.
			local last = string.find(x, "|cff%x%x%x%x%x%x%]|r[^%]]*$") - 1
			local y = x:sub(first, last)
			return y
		end
		return DogTag:ColorizeCode(text:sub(2, -2))
	end
	local function fix__handler(text)
		return escapeHTML(_fix__handler(text))
	end
	local function fix(text)
		return text:gsub("(%b{})", fix__handler)
	end
	
	local syntaxHTML = L["SyntaxHTML"]
	if syntaxHTML == "SyntaxHTML" then
		syntaxHTML = fix([=[<html><body>
		<h1>Syntax</h1>
		<p>
			LibDogTag-3.0 works by allowing normal text with interspersed tags wrapped in brackets, e.g. {Hello [Tag] There}. Syntax is in the standard form alpha {[Tag]} bravo where alpha is a literal word, bravo is a literal word and {[Tag]} will be replaced by the associated dynamic text. All tags and modifiers are case-insensitive, but will be corrected to proper casing if the tags are legal.
			<br/><br/>
		</p>
		<h2>Modifiers</h2>
		<p>
			Modifiers can change how a tag's output looks. For example, the {{:Hide(0)}} modifier will hide the result if it is equal to the number {{0}}, so {[HP:Hide(0)]} will show the current health except when it's equal to {{0}}, at which point it will be blank. You can chain together multiple modifiers as well, e.g. {[MissingHP:Hide(0):Red]} will show the missing health as red and not show it if it's equal to {{0}}. Modifiers are actually syntactic sugar for tags. {[HP:Hide(0)]} is exactly the same as {[Hide(HP, 0)]}. All modifiers work this way and all tags can be used as modifiers if they accept an argument.
			<br/><br/>
		</p>
		<h2>Arguments</h2>
		<p>
			Tags and modifiers can also take an argument, and can be fed in in a syntax similar to {[Tag(argument)]} or {[Tag:Modifier(argument)]}. You can specify arguments out of order by name Using the syntax {[HP(unit='player')]}. This is exactly equal to {[HP('player')]} and {['player':HP]}.
			<br/><br/>
		</p>
		<h2>Literals</h2>
		<p>
			Strings require either double or single quotes and are used like {["Hello" ' There']}. Numbers can be typed just as normal numbers, e.g. {[1234 56.78 1e6]}. There are also the literals {{nil}}, {{true}}, and {{false}}, which act just like tags.
			<br/><br/>
		</p>
		<h2>Logic Branching (if statements)</h2>
		<p>
			* The {{&}} and {{and}} operators function as boolean AND. e.g. {[Alpha and Bravo]} will check if Alpha is non-false, if so, run Bravo.<br />
			* The {{||}} and {{or}} operators function as boolean OR. e.g. {[Alpha or Bravo]} will check if Alpha is false, if so, run Bravo, otherwise just show Alpha.<br />
			* The {{?}} operator functions as an if statement. It can be used in conjunction with {{!}} to create an if-else statement. e.g. {[IsPlayer ? "Player"]} or {[IsPlayer ? "Player" ! "NPC"]}.<br />
			* The {{if}} operator functions as an if statement. It can be used in conjunction with {{else}} to create an if-else statement. e.g. {[if IsPlayer then "Player" end]} or {[if IsPlayer then "Player" else "NPC" end]} or {[if IsPlayer then "Player" elseif IsPet then "Pet" else "NPC"]}.<br />
			* The {{not}} and {{~}} operators turn a false value into true and true value into false. e.g. {[not IsPlayer]} or {[~IsPlayer]}
			<br/><br/>
		</p>
		<h3>Examples</h3>
		<p>
			{[Status || FractionalHP(known=true) || PercentHP]}<br />
			Will return one of the following (but only one): <br /><br />
			* "|cffffffffDead|r", "|cffffffffOffline|r", "|cffffffffGhost|r", etc -- no further information since the OR indicates that there is already a legitimate return<br />
			* "|cffffffff3560/8490|r" or "|cffffffff130/6575|r" (but not "|cffffffff62/100|r" unless the target in fact has {{100}} hit points) -- and not "|cffffffff0/2340|r" or "|cffffffff0/3592|r" because that would mean it is dead and that would have already been taken care of by the first tag in the sequence<br />
			* "|cffffffff25|r" or "|cffffffff35|r" or "|cffffffff72|r" (percent health) -- if the unit is not dead, offline, etc, and your addon is uncertain of your target's maximum and current health, it will display percent health.<br /><br />
			{[Status || (IsPlayer ? HP(known=true)) || PercentHP:Percent]} will deliver similar returns as to that above, but in a slightly different format which should be fairly apparent already.<br /><br />
			But to clarify, the nested {{(IsPlayer ? HP(known=true))}} creates an if statement which means that if {{IsPlayer}} is false, the whole value is taken to be false, and if you've read this far you deserve a cookie. If {{IsPlayer}} is true, the actual returned value of this nested expression is actually the term following the AND -- in this case, {{HP(known=true)}}. So this will show {{HP(known=true)}} if {{IsPlayer}} is found true (that is, if the unit is actually a player).<br/>
			<br/>
			{[if IsFriend then -MissingHP:Green else HP:Red end]}<br /> 
			Will return one of the following (but only one):<br /><br />
			* If the unit is friendly, it will display the amount of health they must be healed to meet their maximum. It will be displayed in green, and with a negative sign in front of it.<br /> 
			* If the unit is an enemy, it will display their current health. As this sequence is written, it will not consider whether it is a valid health value or not. On enemies where the health value is uncertain, it will show a percentage (but without a percent sign), until a more reliable value can be determined. This value will be displayed in red.
			<br/><br/>
		</p>
		<h2>Unit specification</h2>
		<p>
			Units are typically pre-specified by the addon which uses DogTag, whether {{"player"}}, {{"mouseover"}}, or otherwise. You can override the unit a specific tag or modifier operates on by using the form {[Tag(unit="myunit")]} or {[Tag:Modifier(unit="myunit")]}. e.g. {[HP(unit='player')]} gets the player's health.
			<br/><br/>
		</p>
		<h3>List of example units</h3>
		<p>
			* player - your character<br />
			* target - your target<br />
			* targettarget - your target's target<br />
			* pet - your pet<br />
			* mouseover - the unit you are currently hovering over<br />
			* focus - your focus unit<br />
			* party1 - the first member of your party<br />
			* partypet2 - the pet of the second member of your party<br />
			* raid3 - the third member of your raid<br />
			* raidpet4 - the pet of the fourth member of your raid
			<br/><br/>
		</p>
		<h2>Arithmetic operators</h2>
		<p>
			You can use arithmetic operators in your DogTag sequences without issue, they function as expected with proper order-of-operations.<br/><br/>
			* {{+}} - Addition<br />
			* {{-}} - Subtraction<br />
			* {{*}} - Multiplication<br />
			* {{/}} - Division<br />
			* {{%}} - Modulus<br />
			* {{^}} - Exponentiation
			<br/><br/>
		</p>
		<h2>Comparison operators</h2>
		<p>
			You can use comparison operators in your DogTag very similarly to arithmetic operators.<br /><br />
			* {{=}} - Equality<br />
			* {{~=}} - Inequality<br />
			* {{<}} - Less than<br />
			* {{>}} - Greater than<br />
			* {{<=}} - Less than or equal<br />
			* {{>=}} - Greater than or equal
			<br/><br/>
		</p>
		<h2>Concatenation</h2>
		<p>
			Concatenation (joining two pieces of text together) is very easy, all you have to do is place them next to each other separated by a space, e.g. {['Hello' " There"]} =&gt; "|cffffffffHello There|r". For a more true-to-life example, {[HP '/' MaxHP]} => "|cffffffff50/100|r".
		</p>
	</body></html>]=])
	else
		syntaxHTML = fix(syntaxHTML)
	end
	
	local treeLine = getTreeLine(false, treeView, L["Syntax"], function()
		return syntaxHTML
	end)
	treeLine:Click()
	
	local function caseDesensitize__handler(c)
		return ("[%s%s]"):format(c:lower(), c:upper())
	end
	local function caseDesensitize(searchText)
		return searchText:gsub("%a", caseDesensitize__handler)
	end
	
	local function escapeSearch(searchText)
		return searchText:gsub("([%%%[%]%^%$%.%+%*%?%(%)])", "%%%1")
	end
	
	local tagCache = {}
	local categories = {}
	
	local searchLine = getTreeLine(false, treeView, L["Search results"], function()
		local searchText = searchBox:GetText()
		searchText = (searchText or ''):trim():gsub("%s%s+", " ")
		if searchText == '' then
			return "<html><body><h3>" .. L["Type your search into the search box in the upper-left corner of DogTag Help"] .. "</h3></body></html>"
		end
		local searches = newList((" "):split(searchText))
		local t = newList()
		t[#t+1] = "<html>"
		t[#t+1] = "<body>"
		t[#t+1] = "<h1>"
		t[#t+1] = L["Tags matching %q"]:format(searchText)
		t[#t+1] = "</h1>"
		for i, v in ipairs(searches) do
			searches[i] = caseDesensitize(escapeSearch(v))
		end
		for ns, tagCache_ns in pairs(tagCache) do
			local firstNamespace = true
			if categories[ns] then
				for _, category in ipairs(categories[ns]) do
					local firstCategory = true
					for tag, data in pairs(tagCache_ns) do
						if data.category == category then
							local good = false
							for i, v in ipairs(searches) do
								if data.html:match(v) then
									good = true
									break
								end
							end
							if good then
								if firstNamespace then
									firstNamespace = false
									t[#t+1] = "<h1>"
									t[#t+1] = ns
									t[#t+1] = "</h1>"
								end
								if firstCategory then
									firstCategory = false
									t[#t+1] = "<h2>"
									t[#t+1] = category
									t[#t+1] = "</h2>"
								end
								t[#t+1] = data.html
							end
						end
					end
				end
			end
		end
		searches = del(searches)
		t[#t+1] = "</body>"
		t[#t+1] = "</html>"
		local s = table.concat(t)
		t = del(t)
		return s
	end)
	
	local Tags = DogTag.Tags
	
	local namespaces = newList()
	for namespace in pairs(Tags) do
		namespaces[#namespaces+1] = namespace
	end
	table.sort(namespaces)
	
	for i,ns in ipairs(namespaces) do
		local nsLine = getTreeLine(true, treeView, L[ns], function()
			return ''
		end)
		local tags = {}
		local categories_ns = {}
		categories[ns] = categories_ns
		for tag, tagData in pairs(Tags[ns]) do
			tags[#tags+1] = tag
			if tagData.category then
				categories_ns[tagData.category] = true
			end
		end
		table.sort(tags)
		
		local tagCache_ns = {}
		tagCache[ns] = tagCache_ns
		for i, tag in ipairs(tags) do
			local tagData = Tags[ns][tag]
			if not tagData.noDoc then
				local tagCache_ns_tag = {}
				tagCache_ns[tag] = tagCache_ns_tag
				tagCache_ns_tag.category = tagData.category
				local u = newList()
				u[#u+1] = "<h3>"
				local t = newList()
				t[#t+1] = "["
				if tagData.category ~= L["Operators"] then
					t[#t+1] = tag
					local arg = tagData.arg
					if arg then
						t[#t+1] = "("
						for i = 1, #arg, 3 do
							if i > 1 then
								t[#t+1] = ", "
							end
							local argName, argTypes, argDefault = arg[i], arg[i+1], arg[i+2]
							t[#t+1] = argName
							if argName ~= "..." and argDefault ~= "@req" then
								t[#t+1] = "="
								if argDefault == "@undef" then
									t[#t+1] = "undef"
								elseif argDefault == false then
									if argTypes:match("boolean") then
										t[#t+1] = "false"
									else
										t[#t+1] = "nil"
									end
								elseif type(argDefault) == "string" then
									t[#t+1] = ("%q"):format(argDefault)
								else
									t[#t+1] = tostring(argDefault)
								end
							end
						end
						t[#t+1] = ")"
					end
				else
					local arg = tagData.arg
					local operator = tag
					if operator == "unm" then
						operator = "-"
					end
					if #arg == 3 then
						t[#t+1] = operator
					end
					for i = 1, #arg, 3 do
						local argName, argTypes, argDefault = arg[i], arg[i+1], arg[i+2]
						if i > 1 then
							t[#t+1] = " "
							t[#t+1] = operator
							t[#t+1] = " "
						end
						t[#t+1] = argName
					end
				end
				t[#t+1] = "]"
				u[#u+1] = escapeHTML(DogTag:ColorizeCode(table.concat(t)))
				u[#u+1] = "</h3>"
				t = del(t)
				u[#u+1] = "<p>"
				u[#u+1] = "|r |r |r |r "
				u[#u+1] = escapeHTML(tagData.doc)
				if tagData.alias then
					u[#u+1] = "<br/>"
					u[#u+1] = "|r |r |r |r "
					u[#u+1] = L["alias for "]
					u[#u+1] = escapeHTML(DogTag:ColorizeCode("[" .. tagData.alias .. "]"))
				end	
				u[#u+1] = "<br/>"
				local examples = newList((";"):split(tagData.example))
				for i, v in ipairs(examples) do
					local tag, result = v:trim():match("^(.*) => \"(.*)\"$")
					u[#u+1] = "|r |r |r |r • "
					u[#u+1] = escapeHTML(DogTag:ColorizeCode(tag))
					u[#u+1] = " => \"|cffffffff"
					u[#u+1] = escapeHTML(result)
					u[#u+1] = "|r\""
					u[#u+1] = "<br/>"
				end
				examples = del(examples)
				u[#u+1] = "<br/>"
				u[#u+1] = "</p>"
				tagCache_ns_tag.html = table.concat(u)
				u = del(u)
			end
		end
		
		local tmp = {}
		for cat in pairs(categories_ns) do
			tmp[#tmp+1] = cat
		end
		categories_ns = tmp
		categories[ns] = categories_ns
		tmp = nil
		table.sort(categories_ns)
		for i, category in ipairs(categories_ns) do
			local cachedHTML
			local catLine = getTreeLine(false, nsLine, category, function()
				if cachedHTML then
					return cachedHTML
				end
				local t = newList()
				
				t[#t+1] = "<html>"
				t[#t+1] = "<body>"
				t[#t+1] = "<h1>"
				t[#t+1] = category
				t[#t+1] = "</h1>"
				for tag, data in pairs(tagCache_ns) do
					if data.category == category then
						t[#t+1] = data.html
					end
				end
				
				t[#t+1] = "</body>"
				t[#t+1] = "</html>"
				local s = table.concat(t)
				t = del(t)
				cachedHTML = s
				return s
			end)
		end
	end
	namespaces = del(namespaces)
	
	local nextUpdateTime = 0
	local function OnUpdate(this)
		if GetTime() < nextUpdateTime then
			return
		end
		this:SetScript("OnUpdate", nil)
		
		searchLine:Click()
	end
	
	local lastText = ''
	searchBox:SetScript("OnTextChanged", function(this)
		local text = this:GetText()
		if text == lastText then
			return
		end
		lastText = text
		nextUpdateTime = GetTime() + 0.5

		searchBox:SetScript("OnUpdate", OnUpdate)
	end)
	
	treeView:Reposition()
	
	collectgarbage('collect')
	function DogTag:OpenHelp()
		helpFrame:Show()
	end
end

_G.SlashCmdList.DOGTAG = function()
	DogTag:OpenHelp()
end

_G.SLASH_DOGTAG1 = "/dogtag"
_G.SLASH_DOGTAG2 = "/dog"
_G.SLASH_DOGTAG3 = "/dt"

end
