local i=script.Parent.Parent.Packages local c=script.Parent.Util local P=require(i.Fusion)local m=require(c.themeProvider)local f=P.Compat local C=P.State local v=P.Computed local b=P.ComputedPairs local z=P.New local E=P.Children local J=P.OnEvent local Q=P.OnChange local n=P.Spring return function(i)local c=C(Vector2.new())local P=C(Vector2.new())local f=i.ItemCount local J=i.ItemHeight local n=v(function()local i,m,C=P:get(),c:get(),J:get()local v=0 local b=-1 if f:get()>0 then v=1+math.floor(i.y/C)b=math.ceil((i.y+m.y)/C)v=math.clamp(v-1,1,f:get())b=math.clamp(b+1,1,f:get())end local z=table.create((b-v)+1)for i=v,b,1 do z[i]=true end return z end)local Z=v(function()return f:get()*J:get()end)local s s=(z("\083\099\114\111\108\108\105\110\103\070\114\097\109\101"))({ClipsDescendants=not i._debug;Visible=i.Visible==nil and true or i.Visible;AnchorPoint=i.AnchorPoint or Vector2.new(0,0);Size=i.Size or UDim2.fromScale(1,1);Position=i.Position or UDim2.new(),BorderSizePixel=i.BorderSizePixel or 0;BorderColor3=Color3.fromRGB(10,10,13);BackgroundColor3=Color3.fromRGB(46,46,46);BackgroundTransparency=1,ScrollBarImageColor3=m:GetColor(Enum.StudioStyleGuideColor.ScrollBar,Enum.StudioStyleGuideModifier.Default);ScrollBarThickness=12;VerticalScrollBarInset=Enum.ScrollBarInset.ScrollBar;BottomImage="\114\098\120\097\115\115\101\116\058\047\047\116\101\120\116\117\114\101\115\047\117\105\047\083\099\114\111\108\108\047\115\099\114\111\108\108-\109\105\100\100\108\101\046\112\110\103",MidImage="\114\098\120\097\115\115\101\116\058\047\047\116\101\120\116\117\114\101\115\047\117\105\047\083\099\114\111\108\108\047\115\099\114\111\108\108-\109\105\100\100\108\101\046\112\110\103";TopImage="\114\098\120\097\115\115\101\116\058\047\047\116\101\120\116\117\114\101\115\047\117\105\047\083\099\114\111\108\108\047\115\099\114\111\108\108-\109\105\100\100\108\101\046\112\110\103",CanvasSize=v(function()return UDim2.fromOffset(0,Z:get())end);[Q("\065\098\115\111\108\117\116\101\087\105\110\100\111\119\083\105\122\101")]=function()c:set(s.AbsoluteWindowSize)end;[Q("\067\097\110\118\097\115\080\111\115\105\116\105\111\110")]=function()local i=(P:get(false)-s.CanvasPosition).Magnitude local c=J:get(false)if i<c then return end P:set(s.CanvasPosition)end,[E]={b(n,function(c)return(z("\070\114\097\109\101"))({Name="\073\110\100\101\120\095"..c;LayoutOrder=c;Size=v(function()return UDim2.new(1,0,0,J:get())end);Position=v(function()return UDim2.new(0,0,0,(c-1)*J:get())end),BackgroundTransparency=i._debug and.5 or 1;BackgroundColor3=Color3.fromRGB(math.random(10,255),math.random(10,255),math.random(10,255));[E]=i.RenderItem(c)})end)}})return s end
local Packages = script.Parent.Parent.Packages
local Util = script.Parent.Util

local Fusion = require(Packages.Fusion)
local themeProvider = require(Util.themeProvider)

local Compat = Fusion.Compat
local State = Fusion.State
local Computed = Fusion.Computed
local ComputedPairs = Fusion.ComputedPairs

local New = Fusion.New
local Children = Fusion.Children
local OnEvent = Fusion.OnEvent
local OnChange = Fusion.OnChange

local Spring = Fusion.Spring

--[[
see also:
- https://blog.logrocket.com/virtual-scrolling-core-principles-and-basic-implementation-in-react/
- https://github.com/WICG/virtual-scroller
--]]

-- Component
--[[
	props = {
		Visible, Size, Position = you know what these do
		ItemCount: state<number> = how many items are in the scroller
		ItemHeight: state<number> = how many pixels tall each item is
		RenderItem(index: number): function = Takes the index of what item it is, returns Instance(s) to mount in the list (aka a component with an index as the prop)
	}
]]

return function(props)
	local WindowSize = State(Vector2.new())
	local CanvasPosition = State(Vector2.new())

	local numItems = props.ItemCount
	local itemHeight = props.ItemHeight

	local Items = Computed(function()
		local canvasPos,windowSize,height = CanvasPosition:get(), WindowSize:get(), itemHeight:get()

		local minIndex = 0
		local maxIndex = -1
		if numItems:get() > 0 then
			minIndex = 1 + math.floor(canvasPos.y / height)
			maxIndex = math.ceil((canvasPos.y + windowSize.y) / height)
			-- Add extra on either side for seamless load
			minIndex = math.clamp(minIndex-1, 1, numItems:get())
			maxIndex = math.clamp(maxIndex+1, 1, numItems:get())
		end

		-- Dict for stable keys
		local items = table.create(maxIndex - minIndex + 1)
		for i = minIndex, maxIndex do
			items[i] = true
		end

		return items
	end)

	local fullCanvasSize = Computed(function()
		return numItems:get() * itemHeight:get()
	end)

	local Frame; Frame = New "ScrollingFrame" {
		ClipsDescendants = not props._debug,
		Visible = props.Visible == nil and true or props.Visible,
		AnchorPoint = props.AnchorPoint or Vector2.new(0, 0),
		Size = props.Size or UDim2.fromScale(1, 1),
		Position = props.Position or UDim2.new(),
		BorderSizePixel = props.BorderSizePixel or 0,
		BorderColor3 = Color3.fromRGB(10,10,13),
		BackgroundColor3 = Color3.fromRGB(46, 46, 46),
		BackgroundTransparency = 1,
		ScrollBarImageColor3 = themeProvider:GetColor(Enum.StudioStyleGuideColor.ScrollBar, Enum.StudioStyleGuideModifier.Default),
		ScrollBarThickness = 12,
		VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar,
		BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
		MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
		TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png",
		CanvasSize = Computed(function()
			return UDim2.fromOffset(0, fullCanvasSize:get())
		end),

		[OnChange "AbsoluteWindowSize"] = function()
			WindowSize:set(Frame.AbsoluteWindowSize)
		end,

		[OnChange "CanvasPosition"] = function()
			-- Exit if the canvas hasn't moved enough to warrant rendering new items
			local distance = (CanvasPosition:get(false) - Frame.CanvasPosition).Magnitude
			local minimum = itemHeight:get(false)

			if distance < minimum then return end

			CanvasPosition:set(Frame.CanvasPosition)
		end,

		[Children] = {
			ComputedPairs(Items, function(i)
				return New "Frame" {
					Name = "Index_"..i,
					LayoutOrder = i,
					Size = Computed(function()
						return UDim2.new(1, 0, 0, itemHeight:get())
					end),
					Position = Computed(function()
						return UDim2.new(0, 0, 0, (i-1)*itemHeight:get())
					end),
					BackgroundTransparency = props._debug and 0.5 or 1,
					BackgroundColor3 = Color3.fromRGB(math.random(10,255), math.random(10,255), math.random(10,255)),

					[Children] = props.RenderItem(i),
				}
			end),
		},
	}

	return Frame
end
