CreateClientConVar("pac_editor_scale","1", true, false)
local L = pace.LanguageString

local PANEL = {}

PANEL.ClassName = "tree"
PANEL.Base = "pac_dtree"

function PANEL:Init()
	pace.pac_dtree.Init(self)

	self:SetLineHeight(18 * GetConVar("pac_editor_scale"):GetFloat())
	self:SetIndentSize(10)

	self.parts = {}

	self:Populate()

	pace.tree = self
end

do

	local function get_added_nodes(self)
		local added_nodes = {}
		for i,v in ipairs(self.added_nodes) do
			if v.part and v:IsVisible() and v:IsExpanded() then
				table.insert(added_nodes, v)
			end
		end
		table.sort(added_nodes, function(a, b) return select(2, a:LocalToScreen()) < select(2, b:LocalToScreen()) end)
		return added_nodes
	end

	local function scroll_to_node(self, node)
		timer.Simple(0.1, function()
			if not self:IsValid() then return end
			if not node:IsValid() then return end

			local _, y = self:LocalToScreen()
			local h = self:GetTall()

			local _, node_y = node:LocalToScreen()

			if node_y > y + h or node_y < y then
				self:ScrollToChild(node)
			end
		end)
	end

	local info_image = {
		pace.MiscIcons.info,
		pace.MiscIcons.warning,
		pace.MiscIcons.error,
	}

	function PANEL:Think(...)
		if not pace.current_part:IsValid() then return end

		if GetConVar("pac_editor_shortcuts_legacy_mode"):GetBool() then
			if
				pace.current_part.pace_tree_node and
				pace.current_part.pace_tree_node:IsValid() and not
				(
					pace.BusyWithProperties:IsValid() or
					pace.ActiveSpecialPanel:IsValid() or
					pace.editing_viewmodel or
					pace.editing_hands or
					pace.properties.search:HasFocus()
				) and
				not gui.IsConsoleVisible()
			then
				if input.IsKeyDown(KEY_LEFT) then
					pace.Call("VariableChanged", pace.current_part, "EditorExpand", false)
				elseif input.IsKeyDown(KEY_RIGHT) then
					pace.Call("VariableChanged", pace.current_part, "EditorExpand", true)
				end

				if input.IsKeyDown(KEY_UP) or input.IsKeyDown(KEY_PAGEUP) then
					local added_nodes = get_added_nodes(self)
					local offset = input.IsKeyDown(KEY_PAGEUP) and 10 or 1
					if not self.scrolled_up or self.scrolled_up < os.clock() then
						for i,v in ipairs(added_nodes) do
							if v == pace.current_part.pace_tree_node then
								local node = added_nodes[i - offset] or added_nodes[1]
								if node then
									node:DoClick()
									scroll_to_node(self, node)
									break
								end
							end
						end

						self.scrolled_up = self.scrolled_up or os.clock() + 0.4
					end
				else
					self.scrolled_up = nil
				end

				if input.IsKeyDown(KEY_DOWN) or input.IsKeyDown(KEY_PAGEDOWN) then
					local added_nodes = get_added_nodes(self)
					local offset = input.IsKeyDown(KEY_PAGEDOWN) and 10 or 1
					if not self.scrolled_down or self.scrolled_down < os.clock() then
						for i,v in ipairs(added_nodes) do
							if v == pace.current_part.pace_tree_node then
								local node = added_nodes[i + offset] or added_nodes[#added_nodes]
								if node then
									node:DoClick()
									--scroll_to_node(self, node)
									break
								end
							end
						end

						self.scrolled_down = self.scrolled_down or os.clock() + 0.4
					end
				else
					self.scrolled_down = nil
				end
			end
		end

		for _, part in pairs(pac.GetLocalParts()) do
			local node = part.pace_tree_node
			if not node or not node:IsValid() then continue end

			if node.add_button then
				node.add_button:SetVisible(false)
			end

			if part.Info then
				local info = part.Info
				node.info:SetTooltip(info.message)
				node.info:SetImage(info_image[info.type])
				node.info:SetVisible(true)
			else
				node.info:SetVisible(false)
			end

			if part.ClassName == "event" then
				if part.is_active then
					node.Icon:SetImage("icon16/clock_red.png")
				else
					node.Icon:SetImage(part.Icon)
				end
			end

			if part.ClassName == "custom_animation" then
				local anim = part:GetLuaAnimation()
				if anim then
					node:SetText(part:GetName() .. " [" .. string.format("%.2f", anim.Frame + anim.FrameDelta) .. "]")
				end
			end

			if (part.ClassName == "proxy" or part.ClassName == "event") and part.Name == "" then
				node:SetText(pace.pac_show_uniqueid:GetBool() and string.format("%s (%s)", part:GetName(), part:GetPrintUniqueID()) or part:GetName())
			end

			if part:IsHiddenCached() then
				if not node.Icon.event_icon then
					local pnl = vgui.Create("DImage", node.Icon)
					pnl:SetImage("icon16/clock_red.png")
					pnl:SetSize(8*(1 + 0.5*(GetConVar("pac_editor_scale"):GetFloat()-1)), 8*(1 + 0.5*(GetConVar("pac_editor_scale"):GetFloat()-1)))
					pnl:SetPos(8*(1 + 0.5*(GetConVar("pac_editor_scale"):GetFloat()-1)), 8*(1 + 0.5*(GetConVar("pac_editor_scale"):GetFloat()-1)))
					pnl:SetVisible(false)
					node.Icon.event_icon = pnl
				end

				node.Icon.event_icon:SetVisible(true)
			else
				if node.Icon.event_icon and not node.Icon.event_icon_alt then
					node.Icon.event_icon:SetVisible(false)
				end
			end
		end

		local pnl = vgui.GetHoveredPanel() or NULL

		if pnl:IsValid() then
			local pnl = pnl:GetParent()

			if IsValid(pnl) and IsValid(pnl.part) then
				pace.Call("HoverPart", pnl.part)
				if pnl.add_button then
					pnl.add_button:SetVisible(true)
				end
			end
		end
	end

	local function DoScrollControl(self, action)
		pace.BulkSelectKey = input.GetKeyCode(GetConVar("pac_bulk_select_key"):GetString())
		if
				pace.current_part.pace_tree_node and
				pace.current_part.pace_tree_node:IsValid() and not
				(
					pace.BusyWithProperties:IsValid() or
					pace.ActiveSpecialPanel:IsValid() or
					pace.editing_viewmodel or
					pace.editing_hands or
					pace.properties.search:HasFocus()
				) and
				not gui.IsConsoleVisible()
			then

				if action == "editor_node_collapse" then
					pace.Call("VariableChanged", pace.current_part, "EditorExpand", false)
				elseif action == "editor_node_expand" then
					pace.Call("VariableChanged", pace.current_part, "EditorExpand", true)
				end

				if action == "editor_up" or action == "editor_pageup" then
					local added_nodes = get_added_nodes(self)
					local offset = action == "editor_pageup" and 10 or 1
					if not self.scrolled_up or self.scrolled_up < os.clock() then
						for i,v in ipairs(added_nodes) do
							if v == pace.current_part.pace_tree_node then
								local node = added_nodes[i - offset] or added_nodes[1]
								if node then
									node:DoClick()
									scroll_to_node(self, node)
									if input.IsKeyDown(pace.BulkSelectKey) then pace.DoBulkSelect(node.part, true) end
									break
								end
							end
						end

						self.scrolled_up = self.scrolled_up or os.clock() + 0.4
					end
				else
					self.scrolled_up = nil
				end

				if action == "editor_down" or action == "editor_pagedown" then
					local added_nodes = get_added_nodes(self)
					local offset = action == "editor_pagedown" and 10 or 1
					if not self.scrolled_down or self.scrolled_down < os.clock() then
						for i,v in ipairs(added_nodes) do
							if v == pace.current_part.pace_tree_node then
								local node = added_nodes[i + offset] or added_nodes[#added_nodes]
								if node then
									node:DoClick()
									if input.IsKeyDown(pace.BulkSelectKey) then pace.DoBulkSelect(node.part, true) end
									--scroll_to_node(self, node)
									break
								end
							end
						end

						self.scrolled_down = self.scrolled_down or os.clock() + 0.4
					end
				else
					self.scrolled_down = nil
				end
			end
	end

	function pace.DoScrollControls(action)
		DoScrollControl(pace.tree, action)
	end

end




function PANEL:OnMouseReleased(mc)
	if mc == MOUSE_RIGHT then
		pace.Call("PartMenu")
	end
end

function PANEL:SetModel(path)
	if not file.Exists(path or "", "GAME") then
		path = player_manager.TranslatePlayerModel(path)
		if not file.Exists(path, "GAME") then
			print(path, "is invalid")
			return
		end
	end

	local pnl = vgui.Create("SpawnIcon", self)
	pnl:SetModel(path or "")
	pnl:SetSize(16, 16)

	self.Icon:Remove()
	self.Icon = pnl

	self.ModelPath = path
end

function PANEL:GetModel()
	return self.ModelPath
end

local function install_drag(node)
	node:SetDraggableName("pac3")

	local old = node.OnDrop
	function node:OnDrop(child, ...)
		-- we're hovering on the label, not the actual node
		-- so get the parent node instead
		if not child.part then
			child = child:GetParent()
		end

		if child.part and child.part:IsValid() then
			if self.part and self.part:IsValid() and self.part:GetParent() ~= child.part then
				pace.RecordUndoHistory()
				self.part:SetParent(child.part)
				pace.RefreshEvents()
				pace.RecordUndoHistory()
			end
		elseif self.part and self.part:IsValid() then
			if self.part.ClassName ~= "group" then
				pace.RecordUndoHistory()
				local group = pac.CreatePart("group", self.part:GetPlayerOwner())
				group:SetEditorExpand(true)
				self.part:SetParent(group)
				pace.RefreshEvents()
				pace.RecordUndoHistory()
				pace.TrySelectPart()

			else
				pace.RecordUndoHistory()
				self.part:SetParent()
				pace.RecordUndoHistory()
				pace.RefreshTree(true)
			end
		end

		return old(self, child, ...)
	end

	function node:DroppedOn(child)

		if not child.part then
			child = child:GetParent()
		end

		self:InsertNode(child)
		self:SetExpanded(true)

		if child.part and child.part:IsValid() then
			if self.part and self.part:IsValid() and child.part:GetParent() ~= self.part then
				pace.RecordUndoHistory()
				child.part:SetParent(self.part)
				pace.RefreshEvents()
				pace.RecordUndoHistory()
			end
		end

	end
end

local function install_expand(node)
	local old = node.SetExpanded
	node.SetExpanded = function(self, b, ...)
		if self.part and self.part:IsValid() then
			self.part:SetEditorExpand(b)
			return old(self, b, ...)
		end
	end

	local old = node.Expander.OnMousePressed
	node.Expander.OnMousePressed = function(pnl, code, ...)
		old(pnl, code, ...)

		if code == MOUSE_RIGHT then
			local menu = DermaMenu()
			menu:SetPos(input.GetCursorPos())
			menu:MakePopup()

			menu:AddOption(L"collapse all", function()
				node.part:CallRecursive('SetEditorExpand', false)
				pace.RefreshTree(true)
			end):SetImage('icon16/arrow_in.png')

			menu:AddOption(L"expand all", function()
				node.part:CallRecursive('SetEditorExpand', true)
				pace.RefreshTree(true)
			end):SetImage('icon16/arrow_down.png')

			menu:AddSpacer()

			local menu1, pnl = menu:AddSubMenu(L"double click actions") pnl:SetIcon("icon16/cursor.png")
				menu1.GetDeleteSelf = function() return false end
				local menu2, pnl = menu1:AddSubMenu(L"generic") pnl:SetIcon("icon16/world.png")
				menu2.GetDeleteSelf = function() return false end
				menu2:AddOption("expand / collapse", function() RunConsoleCommand("pac_doubleclick_action", "expand") end):SetImage('icon16/arrow_down.png')
				menu2:AddOption("rename", function() RunConsoleCommand("pac_doubleclick_action", "rename") end):SetImage('icon16/text_align_center.png')
				menu2:AddOption("write notes", function() RunConsoleCommand("pac_doubleclick_action", "notes") end):SetImage('icon16/page_white_edit.png')
				menu2:AddOption("show / hide", function() RunConsoleCommand("pac_doubleclick_action", "showhide") end):SetImage('icon16/clock_red.png')
				menu2:AddOption("only when specifed actions exist", function() RunConsoleCommand("pac_doubleclick_action", "specific_only") end):SetImage('icon16/application_xp_terminal.png')
				menu2:AddOption("none", function() RunConsoleCommand("pac_doubleclick_action", "none") end):SetImage('icon16/collision_off.png')
				local menu2, pnl = menu1:AddSubMenu(L"specific") pnl:SetIcon("icon16/application_xp_terminal.png")
				menu2.GetDeleteSelf = function() return false end
				menu2:AddOption("use generic actions only", function() RunConsoleCommand("pac_doubleclick_action_specified", "0") end):SetImage('icon16/world.png')
				menu2:AddOption("use specific actions when available", function() RunConsoleCommand("pac_doubleclick_action_specified", "1") end):SetImage('icon16/cog.png')
				menu2:AddOption("use even more specific actions (events)", function() RunConsoleCommand("pac_doubleclick_action_specified", "2") end):SetImage('icon16/clock.png')

		end
	end
end

local fix_folder_funcs = function(tbl)
	tbl.MakeFolder = function() end
	tbl.FilePopulateCallback = function() end
	tbl.FilePopulate = function() end
	tbl.PopulateChildren = function() end
	tbl.ChildExpanded = function() end
	tbl.PopulateChildrenAndSelf = function() end
	return tbl
end

local function node_layout(self, ...)
	pace.pac_dtree_node.PerformLayout(self, ...)
	if self.Label then
		self.Label:SetFont(pace.CurrentFont)
		--self.Label:SetTextColor(derma.Color("text_dark", self, color_black))
	end

	if self.add_button then
		local x = self.Label:GetPos() + self.Label:GetTextInset() + 4
		surface.SetFont(pace.CurrentFont)
		local w = surface.GetTextSize(self.Label:GetText())
		self.add_button:SetPos(x + w, (self.Label:GetTall() - self.add_button:GetTall()) / 2)
	end

	if self.info then
		local is_adding = self.add_button:IsVisible()
		local x = self.Label:GetPos() + self.Label:GetTextInset() + (is_adding and self.add_button:GetWide() + 4 or 4)
		surface.SetFont(pace.CurrentFont)
		local w = surface.GetTextSize(self.Label:GetText())
		self.info:SetPos(x + w, (self.Label:GetTall() - self.info:GetTall()) / 2)
	end
end

local function add_parts_menu(node)
	pace.Call("AddPartMenu", node.part)
end

-- a hack, because creating a new node button will mess up the layout
function PANEL:AddNode(...)

	if self.RootNode then
		install_drag(self.RootNode)
	end

	local node = fix_folder_funcs((self.RootNode and pace.pac_dtree.AddNode or pace.pac_dtree_node.AddNode)(self, ...))
	install_expand(node)
	install_drag(node)

	local add_button = node:Add("DImageButton")
	add_button:SetImage(pace.MiscIcons.new)
	add_button:SetSize(16*GetConVar("pac_editor_scale"):GetFloat(), 16*GetConVar("pac_editor_scale"):GetFloat())
	add_button:SetVisible(false)
	add_button.DoClick = function() add_parts_menu(node) pace.Call("PartSelected", node.part) end
	add_button.DoRightClick = function() node:DoRightClick() end
	node.add_button = add_button
	node.SetModel = self.SetModel
	node.GetModel = self.GetModel
	node.AddNode = PANEL.AddNode
	node.PerformLayout = node_layout

	local info = node:Add("DImageButton")
	info:SetImage(pace.MiscIcons.info)
	info:SetSize(16, 16)
	info:SetVisible(false)
	info.DoClick = function() pace.MessagePrompt(info:GetTooltip()) end
	node.info = info

	return node
end

local enable_model_icons = CreateClientConVar("pac_editor_model_icons", "1")

function PANEL:PopulateParts(node, parts, children)
	fix_folder_funcs(node)

	local temp = {}
	for k,v in pairs(parts) do
		table.insert(temp, v)
	end
	parts = temp

	local tbl = {}

	table.sort(parts, function(a,b)
		return a and b and a:GetName() < b:GetName()
	end)

	for key, val in pairs(parts) do
		if not val:HasChildren() then
			table.insert(tbl, val)
		end
	end

	for key, val in pairs(parts) do
		if val:HasChildren() then
			table.insert(tbl, val)
		end
	end

	for key, part in pairs(tbl) do
		key = part.Id

		if not part:GetShowInEditor() then goto CONTINUE end

		if not part:HasParent() or children then
			local part_node

			if IsValid(part.pace_tree_node) then
				part_node = part.pace_tree_node
			elseif IsValid(self.parts[key]) then
				part_node = self.parts[key]
			else
				part_node = node:AddNode(pace.pac_show_uniqueid:GetBool() and string.format("%s (%s)", part:GetName(), part:GetPrintUniqueID()) or part:GetName())
			end

			fix_folder_funcs(part_node)

			if part.Description then part_node:SetTooltip(L(part.Description)) end --ok but have we ever had any Description other than "right click to add parts"?
			if part.Notes ~= "" then part_node.Label:SetTooltip(part.Notes) end --idk if anyone uses Notes but tooltips are good if they have something on them. It can easily be overridden by other code anyway.

			part.pace_tree_node = part_node
			part_node.part = part

			self.parts[key] = part_node

			part_node.DoClick = function()
				if not part:IsValid() then return end
				pace.Call("PartSelected", part)

				--part_node.add_button:SetVisible(true)

				return true
			end

			part_node.DoRightClick = function()
				if not part:IsValid() then return end

				pace.Call("PartMenu", part)
				pace.Call("PartSelected", part)
				part_node:InternalDoClick()
				return true
			end

			if
				enable_model_icons:GetBool() and
				part.is_model_part and
				part.GetModel and
				part:GetOwner():IsValid()
			then
				part_node:SetModel(part:GetOwner():GetModel(), part.Icon)
			elseif isstring(part.Icon) then
				part_node.Icon:SetImage(part.Icon)
			end
			part_node.Icon:SetSize(16 * GetConVar("pac_editor_scale"):GetFloat(),16 * GetConVar("pac_editor_scale"):GetFloat())

			self:PopulateParts(part_node, part:GetChildren(), true)

			if part.newly_created then
				part_node:SetSelected(true)

				local function expand(part)
					if part:HasParent() and part.Parent.pace_tree_node then
						part.Parent.pace_tree_node:SetExpanded(true)
						expand(part.Parent)
					end
				end

				expand(part)

				part_node:SetSelected(true)
				part.newly_created = nil
			else
				part_node:SetSelected(false)
				part_node:SetExpanded(part:GetEditorExpand())
			end
		end
		::CONTINUE::
	end
end

function PANEL:SelectPart(part)
	for key, node in pairs(self.parts) do
		if not node.part or not node.part:IsValid() then
			node:Remove()
			self.parts[key] = nil
		else
			if node.part == part then
				node:SetSelected(true)
			else
				node:SetSelected(false)
			end
		end
	end

	self:InvalidateLayout()
end

function PANEL:Populate(reset)

	self:SetLineHeight(18 * (1 + (GetConVar("pac_editor_scale"):GetFloat()-1)))
	self:SetIndentSize(10)

	for key, node in pairs(self.parts) do
		if reset or (not node.part or not node.part:IsValid()) then
			node:Remove()
			self.parts[key] = nil
		end
	end

	--[[self.m_pSelectedItem = nil

	for key, node in pairs(self:GetItems()) do
		node:Remove()
	end]]

	self:PopulateParts(self, pac.GetLocalParts())

	self:InvalidateLayout()
end

pace.RegisterPanel(PANEL)

local function remove_node(part)
	if not part:GetShowInEditor() then return end

	if (part.pace_tree_node or NULL):IsValid() then
		part.pace_tree_node:SetForceShowExpander()
		part.pace_tree_node:GetRoot().m_pSelectedItem = nil
		part.pace_tree_node:Remove()
		pace.RefreshTree()

	end
end

pac.AddHook("pac_OnPartRemove", "pace_remove_tree_nodes", remove_node)

local last_refresh = 0
local function refresh(part)
	if last_refresh > SysTime() then return end
	if not part:GetShowInEditor() then return end


	last_refresh = SysTime() + 0.1
	timer.Simple(0, function()
		if not part:IsValid() then return end
		if not part:GetShowInEditor() then return end

		pace.RefreshTree(true)
	end)
end

pac.AddHook("pac_OnWoreOutfit", "pace_refresh_tree_nodes", refresh)
pac.AddHook("pac_OnPartParent", "pace_refresh_tree_nodes", refresh)
pac.AddHook("pac_OnPartCreated", "pace_refresh_tree_nodes", refresh)

pac.AddHook("pace_OnVariableChanged", "pace_create_tree_nodes", function(part, key, val)
	if key == "EditorExpand" then
		local node = part.pace_tree_node
		if IsValid(node) then
			node:SetExpanded(val)
		end
	end
end)

pace.allowed_event_refresh = 0


function pace.RefreshEvents()
	--spam preventer, (load parts' initializes gets called)
	if pace.allowed_event_refresh > CurTime() then return else pace.allowed_event_refresh = CurTime() + 0.1 end

	local events = {}
	for _, part in pairs(pac.GetLocalParts()) do
		if part.ClassName == "event" then
			events[part] = part
		end
	end
	local no_events = table.Count(events) == 0

	for _, child in pairs(pac.GetLocalParts()) do
		child.active_events = {}
		child.active_events_ref_count = 0
		if not no_events then
			for _,event in pairs(events) do
				event:OnThink()
			end
		end
		child:CallRecursive("CalcShowHide", false)
	end

end

function pace.RefreshTree(reset)
	--print("pace.RefreshTree("..tostring(reset)..")")
	if pace.tree:IsValid() then
		timer.Create("pace_refresh_tree",  0.01, 1, function()
			if pace.tree:IsValid() then
				pace.tree:Populate(reset)
				pace.tree.RootNode:SetExpanded(true, true) -- why do I have to do this?

				pace.TrySelectPart()
			end
		end)
	end

end

if Entity(1):IsPlayer() and not PAC_RESTART and not VLL2_FILEDEF then
	pace.OpenEditor()
	pace.RefreshTree(true)
end
