local s=Roact.Component:extend("\073\116\101\109\076\105\115\116")s.defaultProps={MaxHeight=-1}function s.init(I,s)I:setState({Min=0,Max=s.MaxHeight,CanvasPosition=Vector2.new()})I.UpdateBoundaries=function(s)if I.Mounted then I:setState({CanvasPosition=s.CanvasPosition;Min=s.CanvasPosition.Y-s.AbsoluteSize.Y;Max=s.CanvasPosition.Y+s.AbsoluteSize.Y})end end end function s.didMount(s)s.Mounted=true end function s.willUnmount(s)s.Mounted=false end function s.didUpdate(c,s,I)local M=c.props.ScrollTo and s.ScrollTo~=c.props.ScrollTo if s.Scope~=c.props.Scope and not M then c:setState({CanvasPosition=Vector2.new(0,0);Min=0,Max=c.props.MaxHeight})end end function s.render(s)local I=s.props local c=s.state local M=0 local e=0 local p local U={}local v=0 local B=I.RowHeight local P=Support.Values(I.Items)table.sort(P,function(s,I)return s.Order<I.Order end)for B=1,#P,1 do local i=P[B]local b=i.Parent and I.IdMap[i.Parent]local N=b and I.Items[b]local J=true local A=0 if b then local s=N while s do if not s.Expanded then J=nil break else A=A+1 end local c=I.IdMap[s.Parent]s=I.Items[c]end end if i.Id==I.ScrollTo and s.ScrolledTo~=I.ScrollTo then local M=v*I.RowHeight if M<c.CanvasPosition.Y or M>c.CanvasPosition.Y+I.MaxHeight then p=Vector2.new(0,M)s.ScrolledTo=i.Id end end if J then v=v+1 local s=(v-1)*I.RowHeight if s<c.Min then M=M+1 J=nil elseif s>c.Max then e=e+1 J=nil end end U[i.Id]=J and new(ItemRow,Support.Merge({},i,{Depth=A;Core=I.Core,ToggleExpand=I.ToggleExpand;Height=I.RowHeight}))end return new(ScrollingFrame,{Layout="\076\105\115\116";LayoutDirection="\086\101\114\116\105\099\097\108",CanvasSize=UDim2.new(1,-2,0,v*I.RowHeight),Size=UDim2.new(1,0,0,v*I.RowHeight);CanvasPosition=p or c.CanvasPosition,ScrollBarThickness=4,ScrollBarImageTransparency=.6;VerticalScrollBarInset="\083\099\114\111\108\108\066\097\114",[Roact.Change.CanvasPosition]=s.UpdateBoundaries;[Roact.Children]=Support.Merge(U,{TopSpacer=new(Frame,{Size=UDim2.new(0,0,0,M*I.RowHeight),LayoutOrder=0}),BottomSpacer=new(Frame,{Size=UDim2.new(0,0,0,e*I.RowHeight),LayoutOrder=#P+1});SizeConstraint=new("\085\073\083\105\122\101\067\111\110\115\116\114\097\105\110\116",{MinSize=Vector2.new(0,20);MaxSize=Vector2.new(math.huge,I.MaxHeight)})})})end return s

local ItemList = Roact.Component:extend 'ItemList'
ItemList.defaultProps = {
    MaxHeight = -1
}

function ItemList:init(props)
    self:setState {
        Min = 0,
        Max = props.MaxHeight,
        CanvasPosition = Vector2.new()
    }

    -- Create callback for updating canvas boundaries
    self.UpdateBoundaries = function (rbx)
        if self.Mounted then
            self:setState {
                CanvasPosition = rbx.CanvasPosition,
                Min = rbx.CanvasPosition.Y - rbx.AbsoluteSize.Y,
                Max = rbx.CanvasPosition.Y + rbx.AbsoluteSize.Y
            }
        end
    end
end

function ItemList:didMount()
    self.Mounted = true
end

function ItemList:willUnmount()
    self.Mounted = false
end

function ItemList:didUpdate(previousProps, previousState)
    local IsScrollTargetSet = self.props.ScrollTo and
        (previousProps.ScrollTo ~= self.props.ScrollTo)

    -- Reset canvas position whenever scope updates (unless a scrolling target is set)
    if (previousProps.Scope ~= self.props.Scope) and (not IsScrollTargetSet) then
        self:setState({
            CanvasPosition = Vector2.new(0, 0);
            Min = 0;
            Max = self.props.MaxHeight;
        })
    end
end

function ItemList:render()
    local props = self.props
    local state = self.state

    -- Keep track of how many items are out of view
    local SkippedAbove = 0
    local SkippedBelow = 0
    local TargetCanvasPosition

    -- Declare a button for each item
    local ItemList = {}
    local VisibleItemCount = 0
    local ItemHeight = props.RowHeight

    -- Go through each item in order
    local OrderedItems = Support.Values(props.Items)
    table.sort(OrderedItems, function (A, B)
        return A.Order < B.Order
    end)
    for i = 1, #OrderedItems do
        local Item = OrderedItems[i]

        -- Get item parent state
        local ParentId = Item.Parent and props.IdMap[Item.Parent]
        local ParentState = ParentId and props.Items[ParentId]

        -- Determine visibility and depth from ancestors
        local Visible = true
        local Depth = 0
        if ParentId then
            local ParentState = ParentState
            while ParentState do

                -- Stop if ancestor not visible
                if not ParentState.Expanded then
                    Visible = nil
                    break

                -- Count visible ancestors
                else
                    Depth = Depth + 1
                end

                -- Check next ancestor
                local ParentId = props.IdMap[ParentState.Parent]
                ParentState = props.Items[ParentId]

            end
        end

        -- Set canvas position to begin at item if requested and out-of-view
        if (Item.Id == props.ScrollTo) and (self.ScrolledTo ~= props.ScrollTo) then
            local ItemPosition = VisibleItemCount * props.RowHeight
            if ItemPosition < state.CanvasPosition.Y or
               ItemPosition > (state.CanvasPosition.Y + props.MaxHeight) then
                TargetCanvasPosition = Vector2.new(0, ItemPosition)
                self.ScrolledTo = Item.Id
            end
        end

        -- Calculate whether item is in view
        if Visible then
            VisibleItemCount = VisibleItemCount + 1
            local ItemTop = (VisibleItemCount - 1) * props.RowHeight
            if ItemTop < state.Min then
                SkippedAbove = SkippedAbove + 1
                Visible = nil
            elseif ItemTop > state.Max then
                SkippedBelow = SkippedBelow + 1
                Visible = nil
            end
        end

        -- Declare component for item
        ItemList[Item.Id] = Visible and new(ItemRow, Support.Merge({}, Item, {
            Depth = Depth,
            Core = props.Core,
            ToggleExpand = props.ToggleExpand,
            Height = props.RowHeight
        }))
    end

    return new(ScrollingFrame, {
        Layout = 'List',
        LayoutDirection = 'Vertical',
        CanvasSize = UDim2.new(1, -2, 0, VisibleItemCount * props.RowHeight),
        Size = UDim2.new(1, 0, 0, VisibleItemCount * props.RowHeight),
        CanvasPosition = TargetCanvasPosition or state.CanvasPosition,
        ScrollBarThickness = 4,
        ScrollBarImageTransparency = 0.6,
        VerticalScrollBarInset = 'ScrollBar',
        [Roact.Change.CanvasPosition] = self.UpdateBoundaries,
        [Roact.Children] = Support.Merge(ItemList, {
            TopSpacer = new(Frame, {
                Size = UDim2.new(0, 0, 0, SkippedAbove * props.RowHeight),
                LayoutOrder = 0
            }),
            BottomSpacer = new(Frame, {
                Size = UDim2.new(0, 0, 0, SkippedBelow * props.RowHeight),
                LayoutOrder = #OrderedItems + 1
            }),
            SizeConstraint = new('UISizeConstraint', {
                MinSize = Vector2.new(0, 20),
                MaxSize = Vector2.new(math.huge, props.MaxHeight)
            })
        })
    })
end

return ItemList
