local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages

local Roact = require(Packages.Roact)

local PatchTree = require(Plugin.PatchTree)
local PatchSet = require(Plugin.PatchSet)

local Theme = require(Plugin.App.Theme)
local VirtualScroller = require(Plugin.App.Components.VirtualScroller)
local BorderedContainer = require(Plugin.App.Components.BorderedContainer)

local e = Roact.createElement

local DomLabel = require(script.DomLabel)

local PatchVisualizer = Roact.Component:extend("PatchVisualizer")

function PatchVisualizer:init()
	self.contentSize, self.setContentSize = Roact.createBinding(Vector2.new(0, 0))

	self.updateEvent = Instance.new("BindableEvent")
end

function PatchVisualizer:willUnmount()
	self.updateEvent:Destroy()
end

function PatchVisualizer:shouldUpdate(nextProps)
	if self.props.patchTree ~= nextProps.patchTree then
		return true
	end

	local currentPatch, nextPatch = self.props.patch, nextProps.patch
	if currentPatch ~= nil or nextPatch ~= nil then
		return not PatchSet.isEqual(currentPatch, nextPatch)
	end

	return false
end

function PatchVisualizer:render()
	local patchTree = self.props.patchTree
	if patchTree == nil and self.props.patch ~= nil then
		patchTree = PatchTree.build(
			self.props.patch,
			self.props.instanceMap,
			self.props.changeListHeaders or { "Property", "Current", "Incoming" }
		)
		if self.props.unappliedPatch then
			patchTree =
				PatchTree.updateMetadata(patchTree, self.props.patch, self.props.instanceMap, self.props.unappliedPatch)
		end
	end

	-- Recusively draw tree
	local scrollElements, elementHeights, elementIndex = {}, {}, 0

	if patchTree then
		local elementTotal = patchTree:getCount()
		local depthsComplete = {}
		local function drawNode(node, depth)
			elementIndex += 1

			local parentNode = patchTree:getNode(node.parentId)
			local isFinalChild = true
			if parentNode then
				for _id, sibling in parentNode.children do
					if type(sibling) == "table" and sibling.name and sibling.name > node.name then
						isFinalChild = false
						break
					end
				end
			end

			local elementHeight, setElementHeight = Roact.createBinding(24)
			elementHeights[elementIndex] = elementHeight
			scrollElements[elementIndex] = e(DomLabel, {
				transparency = self.props.transparency,
				showStringDiff = self.props.showStringDiff,
				showTableDiff = self.props.showTableDiff,
				updateEvent = self.updateEvent,
				elementHeight = elementHeight,
				setElementHeight = setElementHeight,
				elementIndex = elementIndex,
				isFinalElement = elementIndex == elementTotal,
				depth = depth,
				depthsComplete = table.clone(depthsComplete),
				hasChildren = (node.children ~= nil and next(node.children) ~= nil),
				isFinalChild = isFinalChild,
				patchType = node.patchType,
				className = node.className,
				isWarning = node.isWarning,
				instance = node.instance,
				name = node.name,
				changeInfo = node.changeInfo,
				changeList = node.changeList,
			})

			if isFinalChild then
				depthsComplete[depth] = true
			end
		end

		patchTree:forEach(function(node, depth)
			depthsComplete[depth] = false
			for i = depth + 1, #depthsComplete do
				depthsComplete[i] = nil
			end

			drawNode(node, depth)
		end)
	end

	return Theme.with(function(theme)
		return e(BorderedContainer, {
			transparency = self.props.transparency,
			size = self.props.size,
			position = self.props.position,
			anchorPoint = self.props.anchorPoint,
			layoutOrder = self.props.layoutOrder,
		}, {
			CleanMerge = e("TextLabel", {
				Visible = #scrollElements == 0,
				Text = "No changes to sync, project is up to date.",
				FontFace = theme.Font.Main,
				TextSize = theme.TextSize.Medium,
				TextColor3 = theme.TextColor,
				TextWrapped = true,
				Size = UDim2.new(1, 0, 1, 0),
				BackgroundTransparency = 1,
			}),

			VirtualScroller = e(VirtualScroller, {
				size = UDim2.new(1, 0, 1, -2),
				position = UDim2.new(0, 0, 0, 2),
				transparency = self.props.transparency,
				count = #scrollElements,
				updateEvent = self.updateEvent.Event,
				render = function(i)
					return scrollElements[i]
				end,
				getHeightBinding = function(i)
					return elementHeights[i]
				end,
			}),
		})
	end)
end

return PatchVisualizer
