InGameAvatarEditor/src/ServerScriptService/AvatarEditorInGameSetup/AvatarEditorInGame/Modules/Packages/FitFrame/FitFrameOnAxis.lua
local root = script.Parent
local Packages = root.Parent

local Cryo = require(Packages.Cryo)
local Roact = require(Packages.Roact)

local Rect = require(root.Rect)

local FitFrameOnAxis = Roact.PureComponent:extend("FitFrameOnAxis")
FitFrameOnAxis.Axis = {
	Horizontal = {},
	Vertical = {},
	Both = {},
}

FitFrameOnAxis.defaultProps = {
	axis = FitFrameOnAxis.Axis.Vertical,
	minimumSize = UDim2.new(UDim.new(0, 0), UDim.new(0, 0)),
	margin = Rect.square(0),

	FillDirection = Enum.FillDirection.Vertical,
	HorizontalAlignment = Enum.HorizontalAlignment.Left,
	ImageSet = {},
	VerticalAlignment = Enum.VerticalAlignment.Top,
	contentPadding = UDim.new(0, 0),
}

function FitFrameOnAxis:init()
	self.layoutRef = Roact.createRef()
	self.frameRef = self.props[Roact.Ref] or Roact.createRef()

	self.onResize = function()
		local currentLayout = self.layoutRef.current
		local currentFrame = self.frameRef.current
		if not currentFrame or not currentLayout then
			return
		end

		currentFrame.Size = self:__getSize(currentLayout)
	end
end

function FitFrameOnAxis:render()
	assert(self.props.Size == nil, "Size is not a valid property of FitFrameOnAxis. Did you mean `minimumSize`?")
	local children = self.props[Roact.Children] or {}
	local filteredProps = self:__getFilteredProps()

	return Roact.createElement("ImageLabel", filteredProps,
		Cryo.Dictionary.join(children, {
			["$layout"] = Roact.createElement("UIListLayout", {
				FillDirection = self.props.FillDirection,
				HorizontalAlignment = self.props.HorizontalAlignment,
				Padding = self.props.contentPadding,
				SortOrder = Enum.SortOrder.LayoutOrder,
				VerticalAlignment = self.props.VerticalAlignment,

				[Roact.Change.AbsoluteContentSize] = self.onResize,
				[Roact.Ref] = self.layoutRef,
			}),
			["$margin"] = Roact.createElement("UIPadding", {
				PaddingLeft = UDim.new(0, self.props.margin.left),
				PaddingRight = UDim.new(0, self.props.margin.right),
				PaddingTop = UDim.new(0, self.props.margin.top),
				PaddingBottom = UDim.new(0, self.props.margin.bottom),
			}),
		})
	)
end

function FitFrameOnAxis:didMount()
	self.onResize()
end

function FitFrameOnAxis:didUpdate()
	self.onResize()
end

function FitFrameOnAxis:__getFilteredProps()
	-- Will return a new prop map after removing
	-- Roact.Children and any defaultProps in an effort
	-- to only return safe Roblox Instance "ImageLabel"
	-- properties that may be present.
	local filteredProps = Cryo.Dictionary.join(self.props.ImageSet, {
		[Roact.Ref] = self.frameRef,
	})

	for property, _ in pairs(FitFrameOnAxis.defaultProps) do
		filteredProps[property] = Cryo.None
	end

	return Cryo.Dictionary.join(self.props, filteredProps, {
		[Roact.Children] = Cryo.None,
	})
end

function FitFrameOnAxis:__getSize(currentLayout)
	if self.props.axis == FitFrameOnAxis.Axis.Both then
		return self:__getBothAxisSize(currentLayout)
	else
		-- Arrangement of UDims are flip-flopped based
		-- on which axis is our primary axis
		local axisUDim = self:__getAxisUDim(currentLayout.AbsoluteContentSize)
		local otherUDim = self:__getOtherUDim()

		if self.props.axis == FitFrameOnAxis.Axis.Vertical then
			return UDim2.new(otherUDim, axisUDim)
		elseif self.props.axis == FitFrameOnAxis.Axis.Horizontal then
			return UDim2.new(axisUDim, otherUDim)
		end
	end
end

function FitFrameOnAxis:__getBothAxisSize(currentLayout)
	local minimumSize = self.props.minimumSize
	local absoluteContentSize = currentLayout.AbsoluteContentSize

	local xAxis = UDim.new(minimumSize.X.Scale, absoluteContentSize.X + self:__getHorizontalMargin())
	local yAxis = UDim.new(minimumSize.Y.Scale, absoluteContentSize.Y + self:__getVerticalMargin())

	return UDim2.new(xAxis, yAxis)
end

function FitFrameOnAxis:__getAxisUDim(vector2)
	-- Merges minimumSize with given Vector2
	-- to create UDim for primary axis
	local minimumSize = self.props.minimumSize

	local targetUDim
	local lengthOfChildren
	if self.props.axis == FitFrameOnAxis.Axis.Vertical then
		targetUDim = minimumSize.Y
		lengthOfChildren = vector2.Y + self:__getVerticalMargin()
	elseif self.props.axis == FitFrameOnAxis.Axis.Horizontal then
		targetUDim = minimumSize.X
		lengthOfChildren = vector2.X + self:__getHorizontalMargin()
	end

	return UDim.new(targetUDim.Scale, math.max(lengthOfChildren, targetUDim.Offset))
end

function FitFrameOnAxis:__getVerticalMargin()
	return self.props.margin.top + self.props.margin.bottom
end

function FitFrameOnAxis:__getHorizontalMargin()
	return self.props.margin.left + self.props.margin.right
end

function FitFrameOnAxis:__getOtherUDim()
	-- Since there is no primary axis to merge with,
	-- this UDim is entirely represented by minimumSize
	local minimumSize = self.props.minimumSize

	if self.props.axis == FitFrameOnAxis.Axis.Vertical then
		return minimumSize.X
	elseif self.props.axis == FitFrameOnAxis.Axis.Horizontal then
		return minimumSize.Y
	end
end

return FitFrameOnAxis