--!strict
--!nolint LocalShadow

local FigmaSquircle = require("./figma_squircle_luau")
local Flatten = require("./flatten")
local Rasterize = require("./rasterize").rasterize

export type RedrawParams = {
	CornerRadius: number?,
	CornerSmoothing: number?,
	TopLeftRadius: number?,
	TopRightRadius: number?,
	BottomRightRadius: number?,
	BottomLeftRadius: number?,
	PreserveSmoothing: boolean?,
	AntiAlias: boolean?,
	Stroke: number?,
}

local VERTEX_STRIDE = 8
local SingleSubPath = buffer.create(4)
buffer.writeu32(SingleSubPath, 0, 0)

-- All geometry is rendered at 2x scale for HiDPI. Params use logical pixels;
-- the render function doubles radii to match the internal coordinate space.
local function RenderToBuffer(pixelBuf: buffer, width: number, height: number, params: RedrawParams, dstXOffset: number?, dstYOffset: number?, dstRowStride: number?)
	-- Smoothing of exactly 1 produces degenerate corners; clamp just below.
	local clampedSmoothing = math.min(params.CornerSmoothing or 1, 1 - 1e-6)

	local topLeftRadius, topRightRadius =
		params.TopLeftRadius, params.TopRightRadius
	local bottomRightRadius, bottomLeftRadius =
		params.BottomRightRadius, params.BottomLeftRadius

	local defaultRadius2x = (params.CornerRadius or 0) * 2
	local tl2x = if topLeftRadius then topLeftRadius * 2 else nil
	local tr2x = if topRightRadius then topRightRadius * 2 else nil
	local br2x = if bottomRightRadius then bottomRightRadius * 2 else nil
	local bl2x = if bottomLeftRadius then bottomLeftRadius * 2 else nil

	local outerOps = FigmaSquircle.get_svg_path({
		width = width,
		height = height,
		cornerRadius = defaultRadius2x,
		cornerSmoothing = clampedSmoothing,
		topLeftCornerRadius = tl2x,
		topRightCornerRadius = tr2x,
		bottomRightCornerRadius = br2x,
		bottomLeftCornerRadius = bl2x,
		preserveSmoothing = params.PreserveSmoothing,
	})

	local antiAlias = if params.AntiAlias ~= nil then params.AntiAlias else true
	local tolerance = if antiAlias then 1/8 else 1/4
	local outerVertices, outerVertexCount = Flatten(outerOps, tolerance)

	local finalVertices = outerVertices
	local finalVertexCount = outerVertexCount
	local subPathStarts = SingleSubPath
	local subPathCount = 1

	local stroke = params.Stroke
	local strokePx = if stroke and stroke > 0 then stroke * 2 else 0
	local innerW = width - 2 * strokePx
	local innerH = height - 2 * strokePx

	if strokePx > 0 and innerW > 0 and innerH > 0 then
		-- Cap inner smoothing slightly below 1 so the arc section has real size,
		-- preventing a degenerate vertex where two cubics meet at a sharp point.
		local innerSmoothing = math.min(clampedSmoothing, 0.99)
		local innerDefaultRadius = math.max(0, defaultRadius2x - strokePx)
		local innerOps = FigmaSquircle.get_svg_path({
			width = innerW,
			height = innerH,
			cornerRadius = innerDefaultRadius,
			cornerSmoothing = innerSmoothing,
			topLeftCornerRadius =
				if tl2x then math.max(0, tl2x - strokePx) else nil,
			topRightCornerRadius =
				if tr2x then math.max(0, tr2x - strokePx) else nil,
			bottomRightCornerRadius =
				if br2x then math.max(0, br2x - strokePx) else nil,
			bottomLeftCornerRadius =
				if bl2x then math.max(0, bl2x - strokePx) else nil,
			preserveSmoothing = params.PreserveSmoothing,
		})

		local innerVertices, innerVertexCount = Flatten(innerOps, tolerance)

		-- Build combined vertex buffer: outer + reversed/offset inner
		local totalCount = outerVertexCount + innerVertexCount
		local combined = buffer.create(totalCount * VERTEX_STRIDE)
		buffer.copy(combined, 0, outerVertices, 0, outerVertexCount * VERTEX_STRIDE)

		for idx = 0, innerVertexCount - 1 do
			local srcIdx = (innerVertexCount - 1 - idx) * VERTEX_STRIDE
			local dstIdx = (outerVertexCount + idx) * VERTEX_STRIDE
			buffer.writef32(combined, dstIdx, buffer.readf32(innerVertices, srcIdx) + strokePx)
			buffer.writef32(combined, dstIdx + 4, buffer.readf32(innerVertices, srcIdx + 4) + strokePx)
		end

		finalVertices = combined
		finalVertexCount = totalCount
		subPathStarts = buffer.create(8)
		buffer.writeu32(subPathStarts, 0, 0)
		buffer.writeu32(subPathStarts, 4, outerVertexCount)
		subPathCount = 2
	end

	Rasterize(
		pixelBuf,
		finalVertices,
		finalVertexCount,
		subPathStarts,
		subPathCount,
		width,
		height,
		1, 1, 1, 1,
		antiAlias,
		dstXOffset,
		dstYOffset,
		dstRowStride
	)
end

-- Each corner extends (1 + smoothing) * radius along both adjacent edges.
-- The factor is doubled here because the image is in 2x internal pixel space.
-- Slice boundaries sit at the max extension of corners sharing an edge,
-- so that no corner is cut by the 9-slice.
-- For strokes, the slice must also contain the stroke's inner edge so that
-- the stretchable edge/center slices only contain uniform geometry.
local function CalcSliceCenter(width: number, height: number, params: RedrawParams): Rect
	local smoothing = math.min(params.CornerSmoothing or 1, 1 - 1e-6)
	local defaultRadius = params.CornerRadius or 0
	local factor = (1 + smoothing) * 2
	local strokeExtension = if params.Stroke then params.Stroke * 2 + 2 else 0

	local topLeftExtension = math.max((params.TopLeftRadius or defaultRadius) * factor, strokeExtension)
	local topRightExtension = math.max((params.TopRightRadius or defaultRadius) * factor, strokeExtension)
	local bottomRightExtension = math.max((params.BottomRightRadius or defaultRadius) * factor, strokeExtension)
	local bottomLeftExtension = math.max((params.BottomLeftRadius or defaultRadius) * factor, strokeExtension)

	local left = math.max(topLeftExtension, bottomLeftExtension)
	local top = math.max(topLeftExtension, topRightExtension)
	local right = math.max(topRightExtension, bottomRightExtension)
	local bottom = math.max(bottomRightExtension, bottomLeftExtension)

	return Rect.new(left, top, width - right, height - bottom)
end

-- The max extension factor is 2 (smoothing at its peak), so each corner
-- occupies up to radius * 2 logical pixels along each edge.
-- The result is then doubled for 2x internal scale.
local function CalcSize(topLeft: number, topRight: number, bottomRight: number, bottomLeft: number, stroke: number?): (number, number)
	local strokeExt = if stroke and stroke > 0 then stroke + 1 else 0
	local topLeftExtension = math.max(topLeft * 2, strokeExt)
	local topRightExtension = math.max(topRight * 2, strokeExt)
	local bottomRightExtension = math.max(bottomRight * 2, strokeExt)
	local bottomLeftExtension = math.max(bottomLeft * 2, strokeExt)

	local width = (math.max(1, math.ceil(math.max(topLeftExtension + topRightExtension, bottomLeftExtension + bottomRightExtension))) + 1) * 2
	local height = (math.max(1, math.ceil(math.max(topLeftExtension + bottomLeftExtension, topRightExtension + bottomRightExtension))) + 1) * 2

	return width, height
end

return {
	RenderToBuffer = RenderToBuffer,
	CalcSliceCenter = CalcSliceCenter,
	CalcSize = CalcSize,
}


--!strict
--!nolint LocalShadow

-- Edge buffer layout (f32 fields at byte offsets)
-- [0] yMin  [4] yMax  [8] xAtYmin  [12] dxPerDy  [16] dir (+1/-1)
const EDGE_STRIDE = 20

-- Vertex buffer layout (f32 fields at byte offsets)
-- [0] x  [4] y
const VERTEX_STRIDE = 8

-- Module-level scratch buffers, reused across renders to avoid per-call allocation.
-- Each buffer grows as needed but is never shrunk.
local scratchEdgeBuf = buffer.create(0)
local scratchEdgeCap = 0
local scratchXBuf = buffer.create(4)
local scratchXCap = 1
local scratchAreaBuf = buffer.create(0)
local scratchFillBuf = buffer.create(0)
local scratchScanlineCap = 0

local function ensureEdgeBuf(needed: number): buffer
    if needed > scratchEdgeCap then
        scratchEdgeCap = needed
        scratchEdgeBuf = buffer.create(needed * EDGE_STRIDE)
    end

    return scratchEdgeBuf
end

local function ensureXBuf(needed: number): buffer
    if needed > scratchXCap then
        scratchXCap = needed
        scratchXBuf = buffer.create(needed * 4)
    end

    return scratchXBuf
end

local function ensureScanlineBufs(width: number): (buffer, buffer)
    if width > scratchScanlineCap then
        scratchScanlineCap = width
        scratchAreaBuf = buffer.create(width * 4)
        scratchFillBuf = buffer.create(width * 4)
    end

    return scratchAreaBuf, scratchFillBuf
end

-- Fills a span of pixels with a u32 color by writing one pixel then exponentially
-- doubling via buffer.copy. This is needed as buffer.fill sets every byte to the same
-- 8-bit value, so it can't write a repeating 4-byte RGBA pattern.
const function fillInterior(
    pixelBuf: buffer,
    spanStart: number,
    span: number,
    colorU32: number
)
    buffer.writeu32(pixelBuf, spanStart, colorU32)

    local filled = 1
    while filled < span do
        const chunk = math.min(filled, span - filled)
        buffer.copy(pixelBuf, spanStart + filled * 4, pixelBuf, spanStart, chunk * 4)
        filled += chunk
    end
end

-- Clip an edge intersection pair to pixel bounds and fill the span
const function fillSpanSolid(
    pixelBuf: buffer,
    rowOffset: number,
    xLeft: number,
    xRight: number,
    width: number,
    colorU32: number
)
    const pxLeft = math.max(0, math.ceil(xLeft))
    const pxRight = math.min(width - 1, math.floor(xRight))
    const span = pxRight - pxLeft + 1

    if span <= 0 then
        return
    end

    fillInterior(pixelBuf, rowOffset + pxLeft * 4, span, colorU32)
end

-- Signed area coverage for one edge within a single scanline band [yTop, yTop+1].
-- areaBuf accumulates the pixel-local signed area (used for edge pixels).
-- fillBuf accumulates the winding delta (propagated across the scanline via prefix sum).
const function processEdgeAA(
    areaBuf: buffer,
    fillBuf: buffer,
    yMin: number,
    yMax: number,
    xAtYmin: number,
    dxPerDy: number,
    dir: number,
    yTop: number,
    width: number
)
    local sy0 = math.max(yTop, yMin)
    local sy1 = math.min(yTop + 1, yMax)
    if sy1 <= sy0 then
        return
    end

    const x0 = xAtYmin + (sy0 - yMin) * dxPerDy
    const x1 = xAtYmin + (sy1 - yMin) * dxPerDy
    const h = sy1 - sy0

    const xLo = math.min(x0, x1)
    const xHi = math.max(x0, x1)
    const ix0 = math.clamp(math.floor(xLo), 0, width - 1)
    const ix1 = math.clamp(math.floor(xHi), 0, width - 1)

    -- Near-vertical edges: single pixel column
    if ix0 == ix1 then
        const avgX = math.clamp(((x0 - ix0) + (x1 - ix0)) / 2, 0, 1)
        const covered = h * (1 - avgX)

        const off = ix0 * 4
        buffer.writef32(areaBuf, off, buffer.readf32(areaBuf, off) + dir * covered)
        buffer.writef32(fillBuf, off, buffer.readf32(fillBuf, off) + dir * h)
        return
    end

    -- Near-horizontal edges: split at integer x boundaries
    const xToY = h / (x1 - x0)

    for ix = ix0, ix1 do
        const cx0 = math.max(ix, xLo)
        const cx1 = math.min(ix + 1, xHi)

        if cx1 <= cx0 then
            continue
        end

        const cy0 = sy0 + (cx0 - x0) * xToY
        const cy1 = sy0 + (cx1 - x0) * xToY
        const subH = math.abs(cy1 - cy0)
        const avgX = math.clamp(((cx0 - ix) + (cx1 - ix)) / 2, 0, 1)
        const covered = subH * (1 - avgX)

        const off = ix * 4
        buffer.writef32(areaBuf, off, buffer.readf32(areaBuf, off) + dir * covered)
        buffer.writef32(fillBuf, off, buffer.readf32(fillBuf, off) + dir * subH)
    end
end

-- Scan forward from `start` to find the next pixel with edge data.
-- Returns the index of the first pixel with non-zero areaBuf or fillBuf,
-- or scanXMax + 1 if no such pixel exists.
const function findNextEdge(areaBuf: buffer, fillBuf: buffer, start: number, scanXMax: number): number
    local idx = start

    while idx <= scanXMax do
        const off = idx * 4

        if buffer.readf32(areaBuf, off) ~= 0 or buffer.readf32(fillBuf, off) ~= 0 then
            break
        end

        idx += 1
    end

    return idx
end

-- Find sorted edge-scanline intersections for the non-AA path.
-- Returns updated firstEdgeOffset (for active edge tracking) and intersection count.
const function findIntersections(
    edgeBuf: buffer,
    edgeEnd: number,
    firstEdgeOffset: number,
    xBuf: buffer,
    scanY: number
): (number, number)
    -- Skip edges that end before this scanline
    while firstEdgeOffset < edgeEnd and buffer.readf32(edgeBuf, firstEdgeOffset + 4) <= scanY do
        firstEdgeOffset += EDGE_STRIDE
    end

    local xCount = 0

    for eOffset = firstEdgeOffset, edgeEnd - EDGE_STRIDE, EDGE_STRIDE do
        const yMin = buffer.readf32(edgeBuf, eOffset)

        if yMin > scanY then
            break
        end

        const yMax = buffer.readf32(edgeBuf, eOffset + 4)

        if yMax <= scanY then
            continue
        end

        const xAtYmin = buffer.readf32(edgeBuf, eOffset + 8)
        const dxPerDy = buffer.readf32(edgeBuf, eOffset + 12)
        const xIntersect = xAtYmin + (scanY - yMin) * dxPerDy

        -- Insertion sort into xBuf
        local pos = xCount

        while pos > 0 and buffer.readf32(xBuf, (pos - 1) * 4) > xIntersect do
            buffer.writef32(xBuf, pos * 4, buffer.readf32(xBuf, (pos - 1) * 4))
            pos -= 1
        end

        buffer.writef32(xBuf, pos * 4, xIntersect)
        xCount += 1
    end

    return firstEdgeOffset, xCount
end

const function rasterize(
    pixelBuf: buffer,
    vertices: buffer,
    vertexCount: number,
    subPathStarts: buffer,
    subPathCount: number,
    width: number,
    height: number,
    r: number,
    g: number,
    b: number,
    a: number,
    antiAlias: boolean,
    dstXOffset: number?,
    dstYOffset: number?,
    dstRowStride: number?
)
    -- Build edge table from polygon vertices, closing each sub-path independently
    local edgeBuf = ensureEdgeBuf(vertexCount)
    local edgeCount = 0
    local shapeYMin = math.huge
    local shapeYMax = -math.huge

    for s = 0, subPathCount - 1 do
        const startIdx = buffer.readu32(subPathStarts, s * 4)
        const endIdx = if s < subPathCount - 1
            then buffer.readu32(subPathStarts, (s + 1) * 4)
            else vertexCount

        for i = startIdx, endIdx - 1 do
            const j = if i < endIdx - 1 then i + 1 else startIdx

            const iOff = i * VERTEX_STRIDE
            const x0 = buffer.readf32(vertices, iOff)
            const y0 = buffer.readf32(vertices, iOff + 4)
            const jOff = j * VERTEX_STRIDE
            const x1 = buffer.readf32(vertices, jOff)
            const y1 = buffer.readf32(vertices, jOff + 4)

            if y0 == y1 then
                continue
            end

            const offset = edgeCount * EDGE_STRIDE

            -- Orient edges so yMin < yMax; dir tracks original winding direction
            if y0 < y1 then
                buffer.writef32(edgeBuf, offset, y0)
                buffer.writef32(edgeBuf, offset + 4, y1)
                buffer.writef32(edgeBuf, offset + 8, x0)
                buffer.writef32(edgeBuf, offset + 12, (x1 - x0) / (y1 - y0))
                buffer.writef32(edgeBuf, offset + 16, 1)
                if y0 < shapeYMin then shapeYMin = y0 end
                if y1 > shapeYMax then shapeYMax = y1 end

            else
                buffer.writef32(edgeBuf, offset, y1)
                buffer.writef32(edgeBuf, offset + 4, y0)
                buffer.writef32(edgeBuf, offset + 8, x1)
                buffer.writef32(edgeBuf, offset + 12, (x0 - x1) / (y0 - y1))
                buffer.writef32(edgeBuf, offset + 16, -1)
                if y1 < shapeYMin then shapeYMin = y1 end
                if y0 > shapeYMax then shapeYMax = y0 end
            end

            edgeCount += 1
        end
    end

    -- Sort edges by yMin for scanline traversal
    local edgeIndices = table.create(edgeCount) :: { number }

    for idx = 0, edgeCount - 1 do
        edgeIndices[idx + 1] = idx
    end

    table.sort(edgeIndices, function(a: number, b: number)
        return buffer.readf32(edgeBuf, a * EDGE_STRIDE) < buffer.readf32(edgeBuf, b * EDGE_STRIDE)
    end)

    local sortedBuf = buffer.create(edgeCount * EDGE_STRIDE)

    for idx = 0, edgeCount - 1 do
        buffer.copy(sortedBuf, idx * EDGE_STRIDE, edgeBuf, edgeIndices[idx + 1] * EDGE_STRIDE, EDGE_STRIDE)
    end

    buffer.copy(edgeBuf, 0, sortedBuf, 0, edgeCount * EDGE_STRIDE)

    const rb = math.floor(r * 255 + 0.5)
    const gb = math.floor(g * 255 + 0.5)
    const bb = math.floor(b * 255 + 0.5)
    const ab = math.floor(a * 255 + 0.5)

    const colorU32 = rb + gb * 0x100 + bb * 0x10000 + ab * 0x1000000
    const colorRgb = rb + gb * 0x100 + bb * 0x10000

    const xBuf = ensureXBuf(math.max(1, edgeCount))
    const rowStride = dstRowStride or (width * 4)
    const xByteOff = (dstXOffset or 0) * 4
    const yOff = dstYOffset or 0
    const edgeEnd = edgeCount * EDGE_STRIDE

    const yStart = math.max(0, math.floor(shapeYMin))
    const yEnd = math.min(height - 1, math.floor(shapeYMax))

    -- Clear only the bounding-box rows instead of the full buffer.
    -- buffer.create zero-initializes, so first render is already clean;
    -- on redraws the caller clears the previous region before calling us.
    -- When rendering into an atlas (dstRowStride set), skip the clear since
    -- the atlas buffer is freshly allocated and zero-initialized.
    if not dstRowStride then
        buffer.fill(pixelBuf, yStart * rowStride, 0, (yEnd - yStart + 1) * rowStride)
    end

    local rowOffset = (yOff + yStart) * rowStride + xByteOff
    local firstEdgeOffset = 0

    if antiAlias then
        const areaBuf, fillBuf = ensureScanlineBufs(width)
        -- Initialize to full width so the first scanline clears any stale data
        -- left over from a previous render in the reused scratch buffers.
        local prevXMin = 0
        local prevXMax = width - 1

        for y = yStart, yEnd do
            -- Only clear the pixel range dirtied by the previous scanline
            if prevXMin <= prevXMax then
                const clearOff = prevXMin * 4
                const clearLen = (prevXMax - prevXMin + 1) * 4
                buffer.fill(areaBuf, clearOff, 0, clearLen)
                buffer.fill(fillBuf, clearOff, 0, clearLen)
            end

            local scanXMin = width
            local scanXMax = -1

            -- Advance past edges whose yMax is behind this scanline
            while firstEdgeOffset < edgeEnd and buffer.readf32(edgeBuf, firstEdgeOffset + 4) <= y do
                firstEdgeOffset += EDGE_STRIDE
            end

            for eOffset = firstEdgeOffset, edgeEnd - EDGE_STRIDE, EDGE_STRIDE do
                const eYMin = buffer.readf32(edgeBuf, eOffset)

                if eYMin >= y + 1 then
                    break
                end

                const eYMax = buffer.readf32(edgeBuf, eOffset + 4)
                const eX = buffer.readf32(edgeBuf, eOffset + 8)
                const eDx = buffer.readf32(edgeBuf, eOffset + 12)
                const eDir = buffer.readf32(edgeBuf, eOffset + 16)

                const ex0 = eX + (math.max(y, eYMin) - eYMin) * eDx
                const ex1 = eX + (math.min(y + 1, eYMax) - eYMin) * eDx
                const ixl = math.clamp(math.floor(math.min(ex0, ex1)), 0, width - 1)
                const ixr = math.clamp(math.floor(math.max(ex0, ex1)), 0, width - 1)

                if ixl < scanXMin then scanXMin = ixl end
                if ixr > scanXMax then scanXMax = ixr end

                processEdgeAA(areaBuf, fillBuf, eYMin, eYMax, eX, eDx, eDir, y, width)
            end

            -- Prefix-sum accumulation: coverage = |area + running_sum|
            -- Consecutive solid pixels are batched into a single fillInterior call.
            -- Interior skip: when a solid pixel has zero edge contribution, scan
            -- forward to find the next edge boundary and fill the gap in one call.
            if scanXMin > scanXMax then
                prevXMin = scanXMin
                prevXMax = scanXMax
                rowOffset += rowStride
                continue
            end

            local sum = 0.0
            local solidRunStart = -1
            local px = scanXMin

            while px <= scanXMax do
                const off = px * 4
                const area = buffer.readf32(areaBuf, off)
                const fill = buffer.readf32(fillBuf, off)
                const coverage = math.min(math.abs(area + sum), 1)
                sum += fill

                -- Interior skip: solid pixel with no edge contribution means we're
                -- between edges. Jump to the next edge boundary in one fill call.
                if coverage >= 1 and area == 0 and fill == 0 then
                    if solidRunStart < 0 then
                        solidRunStart = px
                    end

                    const skipTo = findNextEdge(areaBuf, fillBuf, px + 1, scanXMax)
                    fillInterior(pixelBuf, rowOffset + solidRunStart * 4, skipTo - solidRunStart, colorU32)
                    solidRunStart = -1
                    px = skipTo
                    continue
                end

                if coverage >= 1 then
                    if solidRunStart < 0 then
                        solidRunStart = px
                    end

                    px += 1
                    continue
                end

                if solidRunStart >= 0 then
                    fillInterior(pixelBuf, rowOffset + solidRunStart * 4, px - solidRunStart, colorU32)
                    solidRunStart = -1
                end

                if coverage > 0 then
                    const aaAlpha = math.floor(ab * coverage + 0.5)
                    buffer.writeu32(pixelBuf, rowOffset + off, colorRgb + aaAlpha * 0x1000000)
                end

                px += 1
            end

            if solidRunStart >= 0 then
                fillInterior(pixelBuf, rowOffset + solidRunStart * 4, scanXMax - solidRunStart + 1, colorU32)
            end

            prevXMin = scanXMin
            prevXMax = scanXMax
            rowOffset += rowStride
        end

    else
        -- Non-AA: even-odd scanline fill at pixel centers
        for y = yStart, yEnd do
            const scanY = y + 0.5
            local xCount: number
            firstEdgeOffset, xCount = findIntersections(edgeBuf, edgeEnd, firstEdgeOffset, xBuf, scanY)

            local pair = 0
            while pair < xCount - 1 do
                const xLeft = buffer.readf32(xBuf, pair * 4)
                const xRight = buffer.readf32(xBuf, (pair + 1) * 4)
                fillSpanSolid(pixelBuf, rowOffset, xLeft, xRight, width, colorU32)
                pair += 2
            end

            rowOffset += rowStride
        end
    end
end

return {
	rasterize = rasterize,
	fillInterior = fillInterior,
}


--!strict

const RepStorage = game:GetService("ReplicatedStorage")
const UserInputService = game:GetService("UserInputService")
const Players = game:GetService("Players")

const Supercorner = require(RepStorage.supercorner)

const STROKE_RADIUS = 0
const SIZE = 200
const SLIDER_HEIGHT = 40
const GAP = 80

const ScreenGui = Instance.new("ScreenGui")
ScreenGui.IgnoreGuiInset = true
ScreenGui.Parent = Players.LocalPlayer.PlayerGui

const Bg = Instance.new("Frame")
Bg.Size = UDim2.fromScale(1, 1)
Bg.BackgroundColor3 = Color3.fromRGB(255, 253, 240)
Bg.BorderSizePixel = 0
Bg.Parent = ScreenGui

const BgPadding = Instance.new("UIPadding")
BgPadding.PaddingTop = UDim.new(0, 100)
BgPadding.PaddingBottom = UDim.new(0, 100)
BgPadding.PaddingLeft = UDim.new(0, 100)
BgPadding.PaddingRight = UDim.new(0, 100)
BgPadding.Parent = Bg

const function CreateColumn(
    anchorX: number,
    positionX: UDim
): Frame
	const column = Instance.new("Frame")
	column.Size = UDim2.new(0, SIZE, 1, 0)
	column.AnchorPoint = Vector2.new(anchorX, 0)
	column.Position = UDim2.new(positionX, UDim.new())
	column.BackgroundTransparency = 1
	column.AutomaticSize = Enum.AutomaticSize.Y
	column.Parent = Bg

	const layout = Instance.new("UIListLayout")
	layout.FillDirection = Enum.FillDirection.Vertical
	layout.HorizontalAlignment = Enum.HorizontalAlignment.Center
	layout.SortOrder = Enum.SortOrder.LayoutOrder
	layout.Padding = UDim.new(0, 25)
	layout.Parent = column

	return column
end

-- Supercorner column (left)
const SupercornerColumn = CreateColumn(1, UDim.new(0.5, -GAP / 2))

const SupercornerTitle = Instance.new("TextLabel")
SupercornerTitle.Size = UDim2.new(1, 0, 0, 28)
SupercornerTitle.BackgroundTransparency = 1
SupercornerTitle.TextColor3 = Color3.fromRGB(66, 61, 38)
SupercornerTitle.TextSize = 20
SupercornerTitle.Font = Enum.Font.GothamBlack
SupercornerTitle.Text = "SuperCorner"
SupercornerTitle.LayoutOrder = 0
SupercornerTitle.Parent = SupercornerColumn

local ImageLabel = Instance.new("ImageLabel")
ImageLabel.Size = UDim2.fromOffset(SIZE, SIZE)
ImageLabel.BackgroundTransparency = 1
ImageLabel.LayoutOrder = 1
ImageLabel.Parent = SupercornerColumn

-- Native column (right)
const NativeColumn = CreateColumn(0, UDim.new(0.5, GAP / 2))

const NativeTitle = Instance.new("TextLabel")
NativeTitle.Size = UDim2.new(1, 0, 0, 28)
NativeTitle.BackgroundTransparency = 1
NativeTitle.TextColor3 = Color3.fromRGB(66, 61, 38)
NativeTitle.TextSize = 20
NativeTitle.Font = Enum.Font.GothamBlack
NativeTitle.Text = "UICorner"
NativeTitle.LayoutOrder = 0
NativeTitle.Parent = NativeColumn

const NativeFrame = Instance.new("Frame")
NativeFrame.Size = UDim2.fromOffset(SIZE, SIZE)
NativeFrame.BackgroundColor3 = Color3.fromRGB(120, 108, 70)
NativeFrame.BorderSizePixel = 0
NativeFrame.LayoutOrder = 1
NativeFrame.Parent = NativeColumn

const NativeCorner = Instance.new("UICorner")
NativeCorner.CornerRadius = UDim.new(0, 45)
NativeCorner.Parent = NativeFrame

-- State
local CornerRadius = 45
local CornerSmoothing = 1
local TopLeftRadius: number? = nil
local TopRightRadius: number? = nil
local BottomRightRadius: number? = nil
local BottomLeftRadius: number? = nil

const squircle = Supercorner.new({
	CornerRadius = CornerRadius,
	CornerSmoothing = CornerSmoothing,
	MaxTopLeftRadius = SIZE / 2,
	MaxTopRightRadius = SIZE / 2,
	MaxBottomRightRadius = SIZE / 2,
	MaxBottomLeftRadius = SIZE / 2,
	Redrawable = true,
	Stroke = if STROKE_RADIUS > 0 then STROKE_RADIUS else nil,
})

ImageLabel.ImageColor3 = Color3.fromRGB(120, 108, 70)
ImageLabel.ImageContent = squircle:GetContent()
ImageLabel.SliceCenter = squircle:GetSliceCenter()
ImageLabel.SliceScale = squircle:GetSliceScale()
ImageLabel.ScaleType = Enum.ScaleType.Slice

local function Redraw()
	squircle:Redraw({
		CornerRadius = CornerRadius,
		CornerSmoothing = CornerSmoothing,
		TopLeftRadius = TopLeftRadius,
		TopRightRadius = TopRightRadius,
		BottomRightRadius = BottomRightRadius,
		BottomLeftRadius = BottomLeftRadius,
		Stroke = if STROKE_RADIUS > 0 then STROKE_RADIUS else nil,
	})

	ImageLabel.ImageContent = squircle:GetContent()
	ImageLabel.SliceCenter = squircle:GetSliceCenter()
end

-- Slider system
type SliderSetValue = (value: number) -> ()

const function CreateSlider(
	name: string,
	min_val: number,
	max_val: number,
	initial: number,
	step: number,
	layout_order: number,
	parent: Frame,
	on_changed: (number) -> ()
): SliderSetValue
	const function round(v: number): number
		return math.round(v / step) * step
	end

	const container = Instance.new("Frame")
	container.Size = UDim2.new(1, 0, 0, SLIDER_HEIGHT)
	container.BackgroundTransparency = 1
	container.LayoutOrder = layout_order
	container.Parent = parent

	const label = Instance.new("TextLabel")
	label.Size = UDim2.new(1, 0, 0, 16)
	label.BackgroundTransparency = 1
	label.TextColor3 = Color3.fromRGB(66, 61, 38)
	label.TextSize = 16
	label.Font = Enum.Font.GothamBold
	label.Text = string.format("%s: %g", name, initial)
	label.Parent = container

	const track = Instance.new("Frame")
	track.Size = UDim2.new(1, 0, 0, 6)
	track.Position = UDim2.new(0, 0, 0, 24)
	track.BackgroundColor3 = Color3.fromRGB(235, 230, 215)
	track.BorderSizePixel = 0
	track.Parent = container

	const track_corner = Instance.new("UICorner")
	track_corner.CornerRadius = UDim.new(0, 3)
	track_corner.Parent = track

	const fill = Instance.new("Frame")
	fill.Size = UDim2.fromScale((initial - min_val) / (max_val - min_val), 1)
	fill.BackgroundColor3 = Color3.fromRGB(120, 108, 70)
	fill.BorderSizePixel = 0
	fill.Parent = track

	const fill_corner = Instance.new("UICorner")
	fill_corner.CornerRadius = UDim.new(0, 3)
	fill_corner.Parent = fill

	const knob = Instance.new("Frame")
	knob.Size = UDim2.fromOffset(14, 14)
	knob.AnchorPoint = Vector2.new(0.5, 0.5)
	knob.Position = UDim2.new((initial - min_val) / (max_val - min_val), 0, 0.5, 0)
	knob.BackgroundColor3 = Color3.fromRGB(66, 61, 38)
	knob.BorderSizePixel = 0
	knob.Parent = track

	const knob_corner = Instance.new("UICorner")
	knob_corner.CornerRadius = UDim.new(1, 0)
	knob_corner.Parent = knob

	local dragging = false
	local last_value = round(initial)

	local function set_value(value: number)
		const rounded = round(value)
		const t = math.clamp((rounded - min_val) / (max_val - min_val), 0, 1)
		knob.Position = UDim2.new(t, 0, 0.5, 0)
		fill.Size = UDim2.fromScale(t, 1)
		label.Text = string.format("%s: %g", name, rounded)

		if rounded == last_value then
			return
		end

		last_value = rounded
		on_changed(rounded)
	end

	local function update_from_x(abs_x: number)
		const track_abs_pos = track.AbsolutePosition.X
		const track_abs_size = track.AbsoluteSize.X
		const t = math.clamp((abs_x - track_abs_pos) / track_abs_size, 0, 1)
		set_value(min_val + t * (max_val - min_val))
	end

	knob.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = true
		end
	end)

	track.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = true
			update_from_x(input.Position.X)
		end
	end)

	UserInputService.InputChanged:Connect(function(input)
		if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
			update_from_x(input.Position.X)
		end
	end)

	UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = false
		end
	end)

	return set_value
end

-- Corner radius sync (shift-drag links all corner sliders)
const CornerSetters: { SliderSetValue } = {}
local Syncing = false

local function corner_changed(value: number, index: number)
	if index == 1 then TopLeftRadius = value

	elseif index == 2 then TopRightRadius = value

	elseif index == 3 then BottomRightRadius = value

	elseif index == 4 then BottomLeftRadius = value

	elseif index == 5 then NativeCorner.CornerRadius = UDim.new(0, value)
	end

	if not Syncing and (UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) or UserInputService:IsKeyDown(Enum.KeyCode.RightShift)) then
		Syncing = true

		for i, setter in CornerSetters do
			if i ~= index then
				setter(value)
			end
		end

		Syncing = false
	end

	if index ~= 5 then
		Redraw()
	end
end

-- Supercorner sliders
local SupercornerSliderOrder = 2

table.insert(CornerSetters, CreateSlider("Top Left Radius", 0, SIZE, CornerRadius, 1, SupercornerSliderOrder, SupercornerColumn, function(value)
	corner_changed(value, 1)
end))
SupercornerSliderOrder += 1

table.insert(CornerSetters, CreateSlider("Top Right Radius", 0, SIZE, CornerRadius, 1, SupercornerSliderOrder, SupercornerColumn, function(value)
	corner_changed(value, 2)
end))
SupercornerSliderOrder += 1

table.insert(CornerSetters, CreateSlider("Bottom Right Radius", 0, SIZE, CornerRadius, 1, SupercornerSliderOrder, SupercornerColumn, function(value)
	corner_changed(value, 3)
end))
SupercornerSliderOrder += 1

table.insert(CornerSetters, CreateSlider("Bottom Left Radius", 0, SIZE, CornerRadius, 1, SupercornerSliderOrder, SupercornerColumn, function(value)
	corner_changed(value, 4)
end))
SupercornerSliderOrder += 1

CreateSlider("Corner Smoothing", 0, 1, CornerSmoothing, 0.01, SupercornerSliderOrder, SupercornerColumn, function(value)
	CornerSmoothing = value
	Redraw()
end)

-- Native slider
table.insert(CornerSetters, CreateSlider("Corner Radius", 0, SIZE, CornerRadius, 1, 2, NativeColumn, function(value)
	corner_changed(value, 5)
end))


--!strict

local RepStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local Supercorner = require(RepStorage.supercorner)
local Atlas = Supercorner.atlas

local SIZE = 200
local SLIDER_HEIGHT = 40
local KEYPOINTS = 25

local ScreenGui = Instance.new("ScreenGui")
ScreenGui.IgnoreGuiInset = true
ScreenGui.Parent = Players.LocalPlayer.PlayerGui

local Bg = Instance.new("Frame")
Bg.Size = UDim2.fromScale(1, 1)
Bg.BackgroundColor3 = Color3.fromRGB(255, 253, 240)
Bg.BorderSizePixel = 0
Bg.Parent = ScreenGui

local BgPadding = Instance.new("UIPadding")
BgPadding.PaddingTop = UDim.new(0, 80)
BgPadding.PaddingBottom = UDim.new(0, 80)
BgPadding.PaddingLeft = UDim.new(0, 80)
BgPadding.PaddingRight = UDim.new(0, 80)
BgPadding.Parent = Bg

-- Main column
local Column = Instance.new("Frame")
Column.Size = UDim2.new(0, 300, 1, 0)
Column.AnchorPoint = Vector2.new(0.5, 0)
Column.Position = UDim2.fromScale(0.5, 0)
Column.BackgroundTransparency = 1
Column.Parent = Bg

local Layout = Instance.new("UIListLayout")
Layout.FillDirection = Enum.FillDirection.Vertical
Layout.HorizontalAlignment = Enum.HorizontalAlignment.Center
Layout.SortOrder = Enum.SortOrder.LayoutOrder
Layout.Padding = UDim.new(0, 20)
Layout.Parent = Column

-- Title
local Title = Instance.new("TextLabel")
Title.Size = UDim2.new(1, 0, 0, 28)
Title.BackgroundTransparency = 1
Title.TextColor3 = Color3.fromRGB(66, 61, 38)
Title.TextSize = 20
Title.Font = Enum.Font.GothamBlack
Title.Text = "Atlas Test"
Title.LayoutOrder = 0
Title.Parent = Column

-- Preview image
local ImageLabel = Instance.new("ImageLabel")
ImageLabel.Size = UDim2.fromOffset(SIZE, SIZE)
ImageLabel.BackgroundTransparency = 1
ImageLabel.ImageColor3 = Color3.fromRGB(120, 108, 70)
ImageLabel.ScaleType = Enum.ScaleType.Slice
ImageLabel.LayoutOrder = 1
ImageLabel.Parent = Column

-- Info label
local InfoLabel = Instance.new("TextLabel")
InfoLabel.Size = UDim2.new(1, 0, 0, 48)
InfoLabel.BackgroundTransparency = 1
InfoLabel.TextColor3 = Color3.fromRGB(100, 92, 60)
InfoLabel.TextSize = 14
InfoLabel.Font = Enum.Font.GothamMedium
InfoLabel.TextWrapped = true
InfoLabel.LayoutOrder = 2
InfoLabel.Parent = Column

-- Keypoint strip (shows all keypoints side by side)
local StripLabel = Instance.new("TextLabel")
StripLabel.Size = UDim2.new(1, 0, 0, 18)
StripLabel.BackgroundTransparency = 1
StripLabel.TextColor3 = Color3.fromRGB(66, 61, 38)
StripLabel.TextSize = 14
StripLabel.Font = Enum.Font.GothamBold
StripLabel.Text = "All Keypoints"
StripLabel.LayoutOrder = 3
StripLabel.Parent = Column

local StripContainer = Instance.new("Frame")
StripContainer.Size = UDim2.new(1, 0, 0, 60)
StripContainer.BackgroundTransparency = 1
StripContainer.LayoutOrder = 4
StripContainer.Parent = Column

local StripLayout = Instance.new("UIListLayout")
StripLayout.FillDirection = Enum.FillDirection.Horizontal
StripLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
StripLayout.VerticalAlignment = Enum.VerticalAlignment.Center
StripLayout.SortOrder = Enum.SortOrder.LayoutOrder
StripLayout.Padding = UDim.new(0, 4)
StripLayout.Parent = StripContainer

-- State
local StartRadius = 0
local EndRadius = 40
local StartSmoothing = 1.0
local EndSmoothing = 1.0
local currentAlpha = 0

local atlas: Supercorner.SupercornerAtlas? = nil
local stripImages: { ImageLabel } = {}

local function RebuildAtlas()
	if atlas then
		atlas:Destroy()
	end

	atlas = Atlas({
		Start = { CornerRadius = StartRadius, CornerSmoothing = StartSmoothing },
		End = { CornerRadius = EndRadius, BottomLeftRadius = 65, CornerSmoothing = EndSmoothing },
		Keypoints = KEYPOINTS,
		Stroke = 5
	})

	local a = atlas :: Supercorner.SupercornerAtlas
	local content = a:GetContent()

	-- Update main preview
	local kp = a:GetKeypointFromAlpha(currentAlpha)
	ImageLabel.ImageContent = content
	ImageLabel.ImageRectOffset = kp.ImageRectOffset
	ImageLabel.ImageRectSize = kp.ImageRectSize
	ImageLabel.SliceCenter = kp.SliceCenter
	ImageLabel.SliceScale = kp.SliceScale

	InfoLabel.Text = `Keypoints: {a:GetKeypointCount()} | Offset: {kp.ImageRectOffset} | Size: {kp.ImageRectSize}`

	-- Rebuild strip
	for _, img in stripImages do
		img:Destroy()
	end

	table.clear(stripImages)

	local thumbSize = math.floor(math.min(60, 280 / a:GetKeypointCount()))

	for idx = 1, a:GetKeypointCount() do
		local kpData = a:GetKeypoint(idx)

		local thumb = Instance.new("ImageLabel")
		thumb.Size = UDim2.fromOffset(thumbSize, thumbSize)
		thumb.BackgroundTransparency = 1
		thumb.ImageColor3 = Color3.fromRGB(120, 108, 70)
		thumb.ScaleType = Enum.ScaleType.Slice
		thumb.ImageContent = content
		thumb.ImageRectOffset = kpData.ImageRectOffset
		thumb.ImageRectSize = kpData.ImageRectSize
		thumb.SliceCenter = kpData.SliceCenter
		thumb.SliceScale = kpData.SliceScale
		thumb.LayoutOrder = idx
		thumb.Parent = StripContainer

		table.insert(stripImages, thumb)
	end
end

local function UpdatePreview()
	if not atlas then return end

	local a = atlas :: Supercorner.SupercornerAtlas
	local kp = a:GetKeypointFromAlpha(currentAlpha)

	ImageLabel.ImageContent = a:GetContent()
	ImageLabel.ImageRectOffset = kp.ImageRectOffset
	ImageLabel.ImageRectSize = kp.ImageRectSize
	ImageLabel.SliceCenter = kp.SliceCenter
	ImageLabel.SliceScale = kp.SliceScale

	local index = math.clamp(math.round(currentAlpha * (a:GetKeypointCount() - 1)) + 1, 1, a:GetKeypointCount())
	InfoLabel.Text = `Keypoint: {index}/{a:GetKeypointCount()} | Alpha: {string.format("%.2f", currentAlpha)} | Offset: {kp.ImageRectOffset} | Size: {kp.ImageRectSize}`
end

-- Slider helper
local function CreateSlider(
	name: string,
	min_val: number,
	max_val: number,
	initial: number,
	step: number,
	layout_order: number,
	on_changed: (number) -> ()
)
	local function round(v: number): number
		return math.round(v / step) * step
	end

	local container = Instance.new("Frame")
	container.Size = UDim2.new(1, 0, 0, SLIDER_HEIGHT)
	container.BackgroundTransparency = 1
	container.LayoutOrder = layout_order
	container.Parent = Column

	local label = Instance.new("TextLabel")
	label.Size = UDim2.new(1, 0, 0, 16)
	label.BackgroundTransparency = 1
	label.TextColor3 = Color3.fromRGB(66, 61, 38)
	label.TextSize = 16
	label.Font = Enum.Font.GothamBold
	label.Text = string.format("%s: %g", name, initial)
	label.Parent = container

	local track = Instance.new("Frame")
	track.Size = UDim2.new(1, 0, 0, 6)
	track.Position = UDim2.new(0, 0, 0, 24)
	track.BackgroundColor3 = Color3.fromRGB(235, 230, 215)
	track.BorderSizePixel = 0
	track.Parent = container

	local trackCorner = Instance.new("UICorner")
	trackCorner.CornerRadius = UDim.new(0, 3)
	trackCorner.Parent = track

	local fill = Instance.new("Frame")
	fill.Size = UDim2.fromScale((initial - min_val) / (max_val - min_val), 1)
	fill.BackgroundColor3 = Color3.fromRGB(120, 108, 70)
	fill.BorderSizePixel = 0
	fill.Parent = track

	local fillCorner = Instance.new("UICorner")
	fillCorner.CornerRadius = UDim.new(0, 3)
	fillCorner.Parent = fill

	local knob = Instance.new("Frame")
	knob.Size = UDim2.fromOffset(14, 14)
	knob.AnchorPoint = Vector2.new(0.5, 0.5)
	knob.Position = UDim2.new((initial - min_val) / (max_val - min_val), 0, 0.5, 0)
	knob.BackgroundColor3 = Color3.fromRGB(66, 61, 38)
	knob.BorderSizePixel = 0
	knob.Parent = track

	local knobCorner = Instance.new("UICorner")
	knobCorner.CornerRadius = UDim.new(1, 0)
	knobCorner.Parent = knob

	local dragging = false
	local lastValue = round(initial)

	local function updateFromX(absX: number)
		local trackPos = track.AbsolutePosition.X
		local trackSize = track.AbsoluteSize.X
		local t = math.clamp((absX - trackPos) / trackSize, 0, 1)
		local value = round(min_val + t * (max_val - min_val))
		t = math.clamp((value - min_val) / (max_val - min_val), 0, 1)
		knob.Position = UDim2.new(t, 0, 0.5, 0)
		fill.Size = UDim2.fromScale(t, 1)
		label.Text = string.format("%s: %g", name, value)

		if value == lastValue then return end

		lastValue = value
		on_changed(value)
	end

	knob.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = true
		end
	end)

	track.InputBegan:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = true
			updateFromX(input.Position.X)
		end
	end)

	UserInputService.InputChanged:Connect(function(input)
		if dragging and input.UserInputType == Enum.UserInputType.MouseMovement then
			updateFromX(input.Position.X)
		end
	end)

	UserInputService.InputEnded:Connect(function(input)
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			dragging = false
		end
	end)
end

-- Sliders
CreateSlider("Alpha", 0, 1, 0, 0.01, 10, function(value)
	currentAlpha = value
	UpdatePreview()
end)

CreateSlider("Start Radius", 0, 100, StartRadius, 1, 11, function(value)
	StartRadius = value
	RebuildAtlas()
end)

CreateSlider("End Radius", 0, 100, EndRadius, 1, 12, function(value)
	EndRadius = value
	RebuildAtlas()
end)

CreateSlider("Start Smoothing", 0, 1, StartSmoothing, 0.01, 13, function(value)
	StartSmoothing = value
	RebuildAtlas()
end)

CreateSlider("End Smoothing", 0, 1, EndSmoothing, 0.01, 14, function(value)
	EndSmoothing = value
	RebuildAtlas()
end)

-- Initial render
RebuildAtlas()

print(`[atlas test] Atlas created with {KEYPOINTS} keypoints, cache size: {Supercorner.GetAtlasCacheSize()}`)


--!strict

const RepStorage = game:GetService("ReplicatedStorage")
const Players = game:GetService("Players")

const Supercorner = require(RepStorage.supercorner)

const GRID_COLS = 4
const GRID_ROWS = 4
const CELL_SIZE = 100
const GAP = 10
const CORNER_RADIUS = 25
const CORNER_SMOOTHING = 1.

const ScreenGui = Instance.new("ScreenGui")
ScreenGui.IgnoreGuiInset = true
ScreenGui.Parent = Players.LocalPlayer.PlayerGui

const Bg = Instance.new("Frame")
Bg.Size = UDim2.fromScale(1, 1)
Bg.BackgroundColor3 = Color3.fromRGB(255, 253, 240)
Bg.BorderSizePixel = 0
Bg.Parent = ScreenGui

const StatusLabel = Instance.new("TextLabel")
StatusLabel.Size = UDim2.new(1, 0, 0, 40)
StatusLabel.Position = UDim2.new(0, 0, 0, 20)
StatusLabel.BackgroundTransparency = 1
StatusLabel.TextColor3 = Color3.fromRGB(66, 61, 38)
StatusLabel.TextSize = 18
StatusLabel.Font = Enum.Font.GothamBold
StatusLabel.Parent = ScreenGui

const GridContainer = Instance.new("Frame")
GridContainer.Size = UDim2.fromOffset(
	GRID_COLS * CELL_SIZE + (GRID_COLS - 1) * GAP,
	GRID_ROWS * CELL_SIZE + (GRID_ROWS - 1) * GAP
)
GridContainer.AnchorPoint = Vector2.new(0.5, 0.5)
GridContainer.Position = UDim2.fromScale(0.5, 0.5)
GridContainer.BackgroundTransparency = 1
GridContainer.Parent = Bg

const GridLayout = Instance.new("UIGridLayout")
GridLayout.CellSize = UDim2.fromOffset(CELL_SIZE, CELL_SIZE)
GridLayout.CellPadding = UDim2.fromOffset(GAP, GAP)
GridLayout.SortOrder = Enum.SortOrder.LayoutOrder
GridLayout.Parent = GridContainer

local squircles: { Supercorner.Supercorner } = {}
local imageLabels: { ImageLabel } = {}
local contents: { Content } = {}

for i = 1, GRID_COLS * GRID_ROWS do
	local squircle = Supercorner.new({
		CornerRadius = CORNER_RADIUS,
		CornerSmoothing = CORNER_SMOOTHING,
		Redrawable = i == 1,
	})

	local imageLabel = Instance.new("ImageLabel")
	imageLabel.BackgroundTransparency = 1
	imageLabel.ImageColor3 = Color3.fromRGB(120, 108, 70)
	imageLabel.ImageContent = squircle:GetContent()
	imageLabel.ScaleType = Enum.ScaleType.Slice
	imageLabel.SliceCenter = squircle:GetSliceCenter()
	imageLabel.SliceScale = squircle:GetSliceScale()
	imageLabel.LayoutOrder = i
	imageLabel.Parent = GridContainer

	table.insert(squircles, squircle)
	table.insert(imageLabels, imageLabel)
	table.insert(contents, squircle:GetContent())
end

-- Verify static instances share the same Content (indices 2+)
local allStaticShared = true
local firstStaticContent = contents[2]

for i = 3, #contents do
	if contents[i] ~= firstStaticContent then
		allStaticShared = false
		break
	end
end

local count = GRID_COLS * GRID_ROWS
local allShared = allStaticShared

local cacheSize = Supercorner.GetCacheSize()
local staticCacheSize = Supercorner.GetStaticCacheSize()

if allShared then
	StatusLabel.Text = `PASS: {count} squircles sharing 1 entry (editable: {cacheSize}, static: {staticCacheSize})`
	StatusLabel.TextColor3 = Color3.fromRGB(46, 139, 87)

else
	StatusLabel.Text = `FAIL: squircles are NOT sharing the same entry (editable: {cacheSize}, static: {staticCacheSize})`
	StatusLabel.TextColor3 = Color3.fromRGB(200, 50, 50)
end

print(`[cache test] {count} squircles created, editable cache: {cacheSize}, static cache: {staticCacheSize}`)

-- Animate the first squircle's corner radius down to 0
local radius = CORNER_RADIUS

while radius > 0 do
	radius = math.max(0, radius - 1)

	squircles[1]:Redraw({
		CornerRadius = radius,
		CornerSmoothing = CORNER_SMOOTHING,
	})

	imageLabels[1].ImageContent = squircles[1]:GetContent()
	imageLabels[1].SliceCenter = squircles[1]:GetSliceCenter()
	task.wait(0.05)
end

print(`[cache test] first squircle redraw complete, editable cache: {Supercorner.GetCacheSize()}, static cache: {Supercorner.GetStaticCacheSize()}`)


--!strict

local Fusion =
	require(script.Parent.Parent.Parent.Parent._Index["elttob_fusion@0.3.0"].fusion)
local Supercorner =
	require(script.Parent.Parent.Parent.Parent._Index["cameronpcampbell_supercorner@0.1.0"].supercorner)

local Shared = require(script.Parent.shared)

local Peek, Children = Fusion.peek, Fusion.Children

type Scope = Fusion.Scope<typeof(Fusion)>
type UsedAs<T> = Fusion.UsedAs<T>
type Child = Fusion.Child

const ATLAS_KEYS = {
	Start = true, End = true, Keypoints = true, Alpha = true,
	PreserveSmoothing = true, AntiAlias = true,
	StrokeThickness = true, StrokeColor = true,
	StrokeTransparency = true, StrokeGradient = true,
}

local function Lerp(a: number, b: number, t: number): number
	return a + (b - a) * t
end

local function TryCreateAtlas(params: Supercorner.AtlasParams): Supercorner.SupercornerAtlas?
	local _ok, atlas = pcall(Supercorner.atlas, params)

	if not atlas then
		warn("[Supercorner Fusion] Failed to create atlas, possibly due to the editable memory budget being reached. Falling back to UICorner.")
	end

	return atlas
end

local function AtlasFusion(
	scope: Scope,
	props: {
		Start: Supercorner.CreateAtlasParams,
		End: Supercorner.CreateAtlasParams,
		Keypoints: number,
		Alpha: UsedAs<number>,
		PreserveSmoothing: boolean?,
		AntiAlias: boolean?,
		BackgroundColor3: UsedAs<Color3>?,
		BackgroundTransparency: UsedAs<number>?,
		StrokeThickness: number?,
		StrokeColor: UsedAs<Color3>?,
		StrokeTransparency: UsedAs<number>?,
		StrokeGradient: UsedAs<Instance>?,
		[any]: any,
	}
): Instance
	local passthroughProps = Shared.GetPassthroughProps(props, ATLAS_KEYS)
	local userChildren: Child? = props[Children]
	local peekedStrokeThickness = props.StrokeThickness or 0

	-- Create atlases
	local fillAtlas = TryCreateAtlas({
		Start = props.Start,
		End = props.End,
		Keypoints = props.Keypoints,
		PreserveSmoothing = props.PreserveSmoothing,
		AntiAlias = props.AntiAlias,
	})

	local strokeAtlas: Supercorner.SupercornerAtlas? = nil

	if peekedStrokeThickness > 0 then
		strokeAtlas = TryCreateAtlas({
			Start = props.Start,
			End = props.End,
			Keypoints = props.Keypoints,
			PreserveSmoothing = props.PreserveSmoothing,
			AntiAlias = props.AntiAlias,
			Stroke = peekedStrokeThickness,
		})

		if not strokeAtlas and fillAtlas then
			fillAtlas:Destroy()
			fillAtlas = nil
		end
	end

	-- Fallback: interpolate radii from Start/End based on alpha
	if not fillAtlas then
		local startDefault = props.Start.CornerRadius or 0
		local endDefault = props.End.CornerRadius or 0
		local startTL = props.Start.TopLeftRadius or startDefault
		local startTR = props.Start.TopRightRadius or startDefault
		local startBR = props.Start.BottomRightRadius or startDefault
		local startBL = props.Start.BottomLeftRadius or startDefault
		local endTL = props.End.TopLeftRadius or endDefault
		local endTR = props.End.TopRightRadius or endDefault
		local endBR = props.End.BottomRightRadius or endDefault
		local endBL = props.End.BottomLeftRadius or endDefault

		return Shared.RenderFallback(scope, {
			topLeft = scope:Computed(function(use)
				return Lerp(startTL, endTL, use(props.Alpha) or 0)
			end),

			topRight = scope:Computed(function(use)
				return Lerp(startTR, endTR, use(props.Alpha) or 0)
			end),

			bottomRight = scope:Computed(function(use)
				return Lerp(startBR, endBR, use(props.Alpha) or 0)
			end),

			bottomLeft = scope:Computed(function(use)
				return Lerp(startBL, endBL, use(props.Alpha) or 0)
			end),
		}, passthroughProps, userChildren, {
			bgColor = props.BackgroundColor3,
			bgTransparency = props.BackgroundTransparency,
			strokeThickness = peekedStrokeThickness,
			strokeColor = props.StrokeColor,
			strokeTransparency = props.StrokeTransparency,
			strokeGradient = Peek(props.StrokeGradient),
		})
	end

	-- State values for fill keypoint
	local fillImageRectOffsetState = scope:Value(Vector2.zero)
	local fillImageRectSizeState = scope:Value(Vector2.zero)
	local fillSliceCenterState = scope:Value(Rect.new(0, 0, 0, 0))
	local fillSliceScaleState = scope:Value(0.5 :: number)

	-- State values for stroke keypoint
	local strokeImageRectOffsetState = scope:Value(Vector2.zero)
	local strokeImageRectSizeState = scope:Value(Vector2.zero)
	local strokeSliceCenterState = scope:Value(Rect.new(0, 0, 0, 0))
	local strokeSliceScaleState = scope:Value(0.5 :: number)

	-- Content is constant per atlas
	local fillContent = fillAtlas:GetContent()
	local strokeContent = if strokeAtlas then strokeAtlas:GetContent() else nil

	-- Update keypoint state from alpha
	local function UpdateFromAlpha(alpha: number)
		if fillAtlas then
			local kp = fillAtlas:GetKeypointFromAlpha(alpha)
			fillImageRectOffsetState:set(kp.ImageRectOffset)
			fillImageRectSizeState:set(kp.ImageRectSize)
			fillSliceCenterState:set(kp.SliceCenter)
			fillSliceScaleState:set(kp.SliceScale)
		end

		if strokeAtlas then
			local kp = strokeAtlas:GetKeypointFromAlpha(alpha)
			strokeImageRectOffsetState:set(kp.ImageRectOffset)
			strokeImageRectSizeState:set(kp.ImageRectSize)
			strokeSliceCenterState:set(kp.SliceCenter)
			strokeSliceScaleState:set(kp.SliceScale)
		end
	end

	-- Initial update
	UpdateFromAlpha(Peek(props.Alpha) or 0)

	-- Alpha observer (only if Alpha is a state object)
	if typeof(props.Alpha) == "table" then
		scope:Observer(props.Alpha):onChange(function()
			UpdateFromAlpha(Peek(props.Alpha) or 0)
		end)
	end

	-- Cleanup
	table.insert(scope, function()
		if fillAtlas then fillAtlas:Destroy(); fillAtlas = nil end
		if strokeAtlas then strokeAtlas:Destroy(); strokeAtlas = nil end
	end)

	-- Build instance tree
	local fillChild = scope:New("ImageLabel") {
		ImageContent = fillContent,
		ImageRectOffset = fillImageRectOffsetState,
		ImageRectSize = fillImageRectSizeState,
		ScaleType = Enum.ScaleType.Slice,
		SliceCenter = fillSliceCenterState,
		SliceScale = fillSliceScaleState,
		BackgroundTransparency = 1,
		ImageColor3 = props.BackgroundColor3,
		ImageTransparency = props.BackgroundTransparency,
		Size = UDim2.new(1, 0, 1, 0),
	}

	local strokeChild: Instance? = nil

	if strokeAtlas then
		local strokeChildren: { [any]: any }? = nil

		if props.StrokeGradient ~= nil then
			strokeChildren = { StrokeGradient = props.StrokeGradient }
		end

		strokeChild = scope:New("ImageLabel") {
			ImageContent = strokeContent,
			ImageRectOffset = strokeImageRectOffsetState,
			ImageRectSize = strokeImageRectSizeState,
			ScaleType = Enum.ScaleType.Slice,
			SliceCenter = strokeSliceCenterState,
			SliceScale = strokeSliceScaleState,
			BackgroundTransparency = 1,
			ImageColor3 = props.StrokeColor,
			ImageTransparency = props.StrokeTransparency,
			Size = UDim2.new(1, 0, 1, 0),
			[Children] = strokeChildren,
		}
	end

	local rootChildren: { any } = {
		fillChild,
		strokeChild,
	}

	if userChildren then
		table.insert(rootChildren, scope:New("Frame") {
			Size = UDim2.new(1, 0, 1, 0),
			BackgroundTransparency = 1,
			ZIndex = 2,
			[Children] = userChildren,
		})
	end

	local rootProps: { [any]: any } = {
		BackgroundTransparency = 1,
		[Children] = rootChildren,
	}

	for key, value in passthroughProps do
		rootProps[key] = value
	end

	return scope:New("Frame")(rootProps)
end

return AtlasFusion


--!strict

local Fusion =
	require(script.Parent.Parent.Parent._Index["elttob_fusion@0.3.0"].fusion)
local Supercorner =
	require(script.Parent.Parent.Parent._Index["cameronpcampbell_supercorner@0.1.0"].supercorner)

local Shared = require(script.shared)
local AtlasFusion = require(script.atlas)

local Peek, Children = Fusion.peek, Fusion.Children

type Scope = Fusion.Scope<typeof(Fusion)>
type UsedAs<T> = Fusion.UsedAs<T>
type Child = Fusion.Child

const CREATE_ONLY_PROPS = { "MaxCornerRadius", "MaxTopLeftRadius", "MaxTopRightRadius", "MaxBottomRightRadius", "MaxBottomLeftRadius", "Redrawable" }

const SUPERCORNER_KEYS = {
	CornerRadius = true, CornerSmoothing = true,
	TopLeftRadius = true, TopRightRadius = true,
	BottomRightRadius = true, BottomLeftRadius = true,
	MaxCornerRadius = true, MaxTopLeftRadius = true,
	MaxTopRightRadius = true, MaxBottomRightRadius = true,
	MaxBottomLeftRadius = true, PreserveSmoothing = true,
	AntiAlias = true, Redrawable = true,
	StrokeThickness = true, StrokeColor = true,
	StrokeTransparency = true, StrokeGradient = true,
}

local function TryCreateSupercorner(props: { [any]: any }, stroke: number): Supercorner.Supercorner?
	local _ok, supercorner = pcall(Supercorner.new, {
		CornerRadius = Peek(props.CornerRadius),
		CornerSmoothing = Peek(props.CornerSmoothing),
		TopLeftRadius = Peek(props.TopLeftRadius),
		TopRightRadius = Peek(props.TopRightRadius),
		BottomRightRadius = Peek(props.BottomRightRadius),
		BottomLeftRadius = Peek(props.BottomLeftRadius),
		MaxCornerRadius = Peek(props.MaxCornerRadius),
		MaxTopLeftRadius = Peek(props.MaxTopLeftRadius),
		MaxTopRightRadius = Peek(props.MaxTopRightRadius),
		MaxBottomRightRadius = Peek(props.MaxBottomRightRadius),
		MaxBottomLeftRadius = Peek(props.MaxBottomLeftRadius),
		PreserveSmoothing = Peek(props.PreserveSmoothing),
		AntiAlias = Peek(props.AntiAlias),
		Redrawable = Peek(props.Redrawable),
		Stroke = stroke,
	})

	if not supercorner then
		warn("[Supercorner Fusion] Failed to create Supercorner, possibly due to the editable memory budget being reached. Falling back to UICorner.")
	end

	return supercorner
end

local function UpdateSupercornerValues(
	sc: Supercorner.Supercorner,
	contentState: Fusion.Value<Content?>,
	sliceCenterState: Fusion.Value<Rect?>,
	sliceScaleState: Fusion.Value<number?>
)
	contentState:set(sc:GetContent())
	sliceCenterState:set(sc:GetSliceCenter())
	sliceScaleState:set(sc:GetSliceScale())
end

local function SupercornerFusion(
	scope: Scope,
	props: {
		CornerRadius: UsedAs<number>?,
		CornerSmoothing: UsedAs<number>?,
		TopLeftRadius: UsedAs<number>?,
		TopRightRadius: UsedAs<number>?,
		BottomRightRadius: UsedAs<number>?,
		BottomLeftRadius: UsedAs<number>?,
		MaxCornerRadius: UsedAs<number>?,
		MaxTopLeftRadius: UsedAs<number>?,
		MaxTopRightRadius: UsedAs<number>?,
		MaxBottomRightRadius: UsedAs<number>?,
		MaxBottomLeftRadius: UsedAs<number>?,
		PreserveSmoothing: UsedAs<boolean>?,
		AntiAlias: UsedAs<boolean>?,
		Redrawable: UsedAs<boolean>?,
		BackgroundColor3: UsedAs<Color3>?,
		BackgroundTransparency: UsedAs<number>?,
		StrokeThickness: UsedAs<number>?,
		StrokeColor: UsedAs<Color3>?,
		StrokeTransparency: UsedAs<number>?,
		StrokeGradient: UsedAs<Instance>?,
		[any]: any,
	}
): Instance
	local passthroughProps = Shared.GetPassthroughProps(props, SUPERCORNER_KEYS)
	local userChildren: Child? = props[Children]

	-- Warn if create-only props change
	for _, propName in CREATE_ONLY_PROPS do
		local propValue = props[propName]

		if propValue == nil then continue end
		if typeof(propValue) ~= "table" then continue end

		local initial = Peek(propValue)

		scope:Observer(propValue):onChange(function()
			if Peek(propValue) ~= initial then
				warn(`[Supercorner Fusion] '{propName}' cannot be updated after creation.`)
			end
		end)
	end

	-- Derived state
	local computeNeedsFill = scope:Computed(function(use)
		return (use(props.BackgroundTransparency) or 0) ~= 1
	end)

	local computeNeedsStroke = scope:Computed(function(use)
		return (use(props.StrokeThickness) or 0) > 0
	end)

	local computeGeometry = scope:Computed(function(use)
		return {
			CornerRadius = use(props.CornerRadius),
			CornerSmoothing = use(props.CornerSmoothing),
			TopLeftRadius = use(props.TopLeftRadius),
			TopRightRadius = use(props.TopRightRadius),
			BottomRightRadius = use(props.BottomRightRadius),
			BottomLeftRadius = use(props.BottomLeftRadius),
			PreserveSmoothing = use(props.PreserveSmoothing),
			AntiAlias = use(props.AntiAlias),
			StrokeThickness = use(props.StrokeThickness),
		}
	end)

	-- Supercorner instance state
	local fillContentState = scope:Value(nil :: Content?)
	local fillSliceCenterState = scope:Value(nil :: Rect?)
	local fillSliceScaleState = scope:Value(nil :: number?)

	local strokeContentState = scope:Value(nil :: Content?)
	local strokeSliceCenterState = scope:Value(nil :: Rect?)
	local strokeSliceScaleState = scope:Value(nil :: number?)

	local fillSC: Supercorner.Supercorner? = nil
	local strokeSC: Supercorner.Supercorner? = nil

	-- Initial creation
	local useFallback = false

	if Peek(computeNeedsFill) then
		fillSC = TryCreateSupercorner(props, 0)

		if fillSC then
			UpdateSupercornerValues(fillSC, fillContentState, fillSliceCenterState, fillSliceScaleState)
		else
			useFallback = true
		end
	end

	if Peek(computeNeedsStroke) and not useFallback then
		local peekedStrokeThickness = Peek(props.StrokeThickness) or 0
		strokeSC = TryCreateSupercorner(props, peekedStrokeThickness)

		if strokeSC then
			UpdateSupercornerValues(strokeSC, strokeContentState, strokeSliceCenterState, strokeSliceScaleState)
		else
			useFallback = true

			if fillSC then
				fillSC:Destroy()
				fillSC = nil
			end
		end
	end

	if useFallback then
		local defaultRadius = scope:Computed(function(use)
			return use(props.CornerRadius) or 0
		end)

		return Shared.RenderFallback(scope, {
			topLeft = scope:Computed(function(use)
				return use(props.TopLeftRadius) or use(defaultRadius)
			end),

			topRight = scope:Computed(function(use)
				return use(props.TopRightRadius) or use(defaultRadius)
			end),

			bottomRight = scope:Computed(function(use)
				return use(props.BottomRightRadius) or use(defaultRadius)
			end),

			bottomLeft = scope:Computed(function(use)
				return use(props.BottomLeftRadius) or use(defaultRadius)
			end),
		}, passthroughProps, userChildren, {
			bgColor = props.BackgroundColor3,
			bgTransparency = props.BackgroundTransparency,
			strokeThickness = props.StrokeThickness,
			strokeColor = props.StrokeColor,
			strokeTransparency = props.StrokeTransparency,
			strokeGradient = Peek(props.StrokeGradient),
		})
	end

	-- Geometry change observer — redraw on prop changes
	scope:Observer(computeGeometry):onChange(function()
		local geo = Peek(computeGeometry)

		if fillSC then
			local ok, err = (pcall :: any)(fillSC.Redraw, fillSC, {
				CornerRadius = geo.CornerRadius,
				CornerSmoothing = geo.CornerSmoothing,
				TopLeftRadius = geo.TopLeftRadius,
				TopRightRadius = geo.TopRightRadius,
				BottomRightRadius = geo.BottomRightRadius,
				BottomLeftRadius = geo.BottomLeftRadius,
				PreserveSmoothing = geo.PreserveSmoothing,
				AntiAlias = geo.AntiAlias,
			})

			if ok then
				UpdateSupercornerValues(fillSC, fillContentState, fillSliceCenterState, fillSliceScaleState)
			else
				warn(`[Supercorner Fusion] Failed to redraw Supercorner. Make sure the component was created with \`Redrawable = true\`. Error: {err}`)
			end
		end

		if strokeSC then
			local ok, err = (pcall :: any)(strokeSC.Redraw, strokeSC, {
				CornerRadius = geo.CornerRadius,
				CornerSmoothing = geo.CornerSmoothing,
				TopLeftRadius = geo.TopLeftRadius,
				TopRightRadius = geo.TopRightRadius,
				BottomRightRadius = geo.BottomRightRadius,
				BottomLeftRadius = geo.BottomLeftRadius,
				PreserveSmoothing = geo.PreserveSmoothing,
				AntiAlias = geo.AntiAlias,
				Stroke = geo.StrokeThickness,
			})

			if ok then
				UpdateSupercornerValues(strokeSC, strokeContentState, strokeSliceCenterState, strokeSliceScaleState)
			else
				warn(`[Supercorner Fusion] Failed to redraw stroke Supercorner. Make sure the component was created with \`Redrawable = true\`. Error: {err}`)
			end
		end
	end)

	-- Fill toggle observer
	scope:Observer(computeNeedsFill):onChange(function()
		local peekedNeedsFill = Peek(computeNeedsFill)

		if peekedNeedsFill and not fillSC then
			fillSC = TryCreateSupercorner(props, 0)

			if fillSC then
				UpdateSupercornerValues(fillSC, fillContentState, fillSliceCenterState, fillSliceScaleState)
			end

		elseif not peekedNeedsFill and fillSC then
			fillSC:Destroy()
			fillSC = nil
			fillContentState:set(nil)
			fillSliceCenterState:set(nil)
			fillSliceScaleState:set(nil)
		end
	end)

	-- Stroke toggle observer
	scope:Observer(computeNeedsStroke):onChange(function()
		local peekedNeedsStroke = Peek(computeNeedsStroke)

		if peekedNeedsStroke and not strokeSC then
			local peekedStrokeThickness = Peek(props.StrokeThickness) or 0
			strokeSC = TryCreateSupercorner(props, peekedStrokeThickness)

			if strokeSC then
				UpdateSupercornerValues(strokeSC, strokeContentState, strokeSliceCenterState, strokeSliceScaleState)
			end

		elseif not peekedNeedsStroke and strokeSC then
			strokeSC:Destroy()
			strokeSC = nil
			strokeContentState:set(nil)
			strokeSliceCenterState:set(nil)
			strokeSliceScaleState:set(nil)
		end
	end)

	-- Cleanup
	table.insert(scope, function()
		if fillSC then fillSC:Destroy(); fillSC = nil end
		if strokeSC then strokeSC:Destroy(); strokeSC = nil end
	end)

	-- Build instance tree
	local computeFillChild = scope:Computed(function(use, innerScope: Fusion.Scope): Instance?
		local usedContent = use(fillContentState)
		if usedContent == nil then return nil end

		return innerScope:New("ImageLabel") {
			ImageContent = fillContentState,
			ScaleType = Enum.ScaleType.Slice,
			SliceCenter = fillSliceCenterState,
			SliceScale = fillSliceScaleState,
			BackgroundTransparency = 1,
			ImageColor3 = props.BackgroundColor3,
			ImageTransparency = props.BackgroundTransparency,
			Size = UDim2.new(1, 0, 1, 0),
		}
	end)

	local computeStrokeChild = scope:Computed(function(use, innerScope: Fusion.Scope): Instance?
		local usedContent = use(strokeContentState)
		if usedContent == nil then return nil end

		local strokeChildren: { [any]: any }? = nil

		if props.StrokeGradient ~= nil then
			strokeChildren = { StrokeGradient = props.StrokeGradient }
		end

		return innerScope:New("ImageLabel") {
			ImageContent = strokeContentState,
			ScaleType = Enum.ScaleType.Slice,
			SliceCenter = strokeSliceCenterState,
			SliceScale = strokeSliceScaleState,
			BackgroundTransparency = 1,
			ImageColor3 = props.StrokeColor,
			ImageTransparency = props.StrokeTransparency,
			Size = UDim2.new(1, 0, 1, 0),
			[Children] = strokeChildren,
		}
	end)

	local rootChildren: { any } = {
		computeFillChild,
		computeStrokeChild,
	}

	if userChildren then
		table.insert(rootChildren, scope:New("Frame") {
			Size = UDim2.new(1, 0, 1, 0),
			BackgroundTransparency = 1,
			ZIndex = 2,
			[Children] = userChildren,
		})
	end

	local rootProps: { [any]: any } = {
		BackgroundTransparency = 1,
		[Children] = rootChildren,
	}

	for key, value in passthroughProps do
		rootProps[key] = value
	end

	return scope:New("Frame")(rootProps)
end

return {
	new = SupercornerFusion,
	atlas = AtlasFusion,
}


--!strict

local Fusion =
	require(script.Parent.Parent.Parent.Parent._Index["elttob_fusion@0.3.0"].fusion)

local Peek, Children = Fusion.peek, Fusion.Children

type Scope = Fusion.Scope<typeof(Fusion)>
type UsedAs<T> = Fusion.UsedAs<T>
type Child = Fusion.Child

export type ResolvedRadii = {
	topLeft: UsedAs<number>,
	topRight: UsedAs<number>,
	bottomRight: UsedAs<number>,
	bottomLeft: UsedAs<number>,
}

export type VisualProps = {
	bgColor: UsedAs<Color3>?,
	bgTransparency: UsedAs<number>?,
	strokeThickness: UsedAs<number>?,
	strokeColor: UsedAs<Color3>?,
	strokeTransparency: UsedAs<number>?,
	strokeGradient: Instance?,
}

const NON_PASSTHROUGH_KEYS = {
	BackgroundColor3 = true,
	BackgroundTransparency = true,
}

const UICORNER_HAS_INDIVIDUAL_RADII = (function()
	local reflectionService = game:GetService("ReflectionService")
	local reflection: { [any]: any } = reflectionService:GetPropertiesOfClass("UICorner")

	return
		reflection.TopLeftRadius and reflection.TopRightRadius and
		reflection.BottomLeftRadius and reflection.BottomRightRadius
end)()

local function GetPassthroughProps(props: { [any]: any }, supercornerKeys: { [string]: boolean }): { [any]: any }
	local result = {}

	for key, value in props do
		if key == Children then continue end
		if supercornerKeys[key] then continue end
		if NON_PASSTHROUGH_KEYS[key] then continue end

		result[key] = value
	end

	return result
end

local function CreateCornerClip(
	scope: Scope,
	radius: UsedAs<number>,
	clipPosition: UDim2,
	fillPosition: UDim2,
	visual: VisualProps
): Instance
	local fillChildren: { [any]: any } = {
		UICorner = scope:New("UICorner") {
			CornerRadius = scope:Computed(function(use)
				return UDim.new(0, use(radius))
			end),
		},
	}

	local peekedStrokeThickness = Peek(visual.strokeThickness)

	if (peekedStrokeThickness or 0) > 0 then
		local strokeChildren: { [any]: any }? = nil

		if visual.strokeGradient then
			strokeChildren = { StrokeGradient = visual.strokeGradient:Clone() }
		end

		fillChildren.UIStroke = scope:New("UIStroke") {
			Thickness = visual.strokeThickness,
			Color = if visual.strokeGradient then Color3.new(1, 1, 1) else visual.strokeColor,
			Transparency = visual.strokeTransparency,
			ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
			BorderStrokePosition = Enum.BorderStrokePosition.Inner,
			[Children] = strokeChildren,
		}
	end

	return scope:New("Frame") {
		Size = UDim2.new(0.5, 0, 0.5, 0),
		Position = clipPosition,
		BackgroundTransparency = 1,
		ClipsDescendants = true,

		[Children] = {
			Fill = scope:New("Frame") {
				Size = UDim2.new(2, 0, 2, 0),
				Position = fillPosition,
				BackgroundColor3 = visual.bgColor,
				BackgroundTransparency = visual.bgTransparency,
				ZIndex = 0,
				[Children] = fillChildren,
			},
		},
	}
end

local function RenderClippedCornersFallback(
	scope: Scope,
	radii: ResolvedRadii,
	passthroughProps: { [any]: any },
	userChildren: Child?,
	visual: VisualProps
): Instance
	local rootProps: { [any]: any } = {
		BackgroundTransparency = 1,

		[Children] = {
			TopLeftClip = CreateCornerClip(
				scope, radii.topLeft,
				UDim2.new(0, 0, 0, 0), UDim2.new(0, 0, 0, 0),
				visual
			),

			TopRightClip = CreateCornerClip(
				scope, radii.topRight,
				UDim2.new(0.5, 0, 0, 0), UDim2.new(-1, 0, 0, 0),
				visual
			),

			BottomLeftClip = CreateCornerClip(
				scope, radii.bottomLeft,
				UDim2.new(0, 0, 0.5, 0), UDim2.new(0, 0, -1, 0),
				visual
			),

			BottomRightClip = CreateCornerClip(
				scope, radii.bottomRight,
				UDim2.new(0.5, 0, 0.5, 0), UDim2.new(-1, 0, -1, 0),
				visual
			),

			ChildrenFrame = if userChildren then scope:New("Frame") {
				Size = UDim2.new(1, 0, 1, 0),
				BackgroundTransparency = 1,
				ZIndex = 2,
				[Children] = userChildren,
			} else nil,
		},
	}

	for key, value in passthroughProps do
		rootProps[key] = value
	end

	return scope:New("ImageLabel")(rootProps)
end

local function RenderFallback(
	scope: Scope,
	radii: ResolvedRadii,
	passthroughProps: { [any]: any },
	userChildren: Child?,
	visual: VisualProps
): Instance
	local needsStroke = scope:Computed(function(use)
		return (use(visual.strokeThickness) or 0) > 0
	end)

	if UICORNER_HAS_INDIVIDUAL_RADII then
		local allChildren: { [any]: any } = {
			UICorner = scope:New("UICorner") {
				TopLeftRadius = scope:Computed(function(use)
					return UDim.new(0, use(radii.topLeft))
				end),

				TopRightRadius = scope:Computed(function(use)
					return UDim.new(0, use(radii.topRight))
				end),

				BottomRightRadius = scope:Computed(function(use)
					return UDim.new(0, use(radii.bottomRight))
				end),

				BottomLeftRadius = scope:Computed(function(use)
					return UDim.new(0, use(radii.bottomLeft))
				end),
			},

			UIStroke = scope:Computed(function(use, innerScope: Fusion.Scope): Instance?
				if not use(needsStroke) then return nil end

				local strokeChildren: { [any]: any }? = nil

				if visual.strokeGradient then
					strokeChildren = { StrokeGradient = visual.strokeGradient:Clone() }
				end

				return innerScope:New("UIStroke") {
					Thickness = visual.strokeThickness,
					Color = if visual.strokeGradient then Color3.new(1, 1, 1) else visual.strokeColor,
					Transparency = visual.strokeTransparency,
					ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
					BorderStrokePosition = Enum.BorderStrokePosition.Inner,
					[Children] = strokeChildren,
				}
			end),

			ChildrenFrame = if userChildren then scope:New("Frame") {
				Size = UDim2.new(1, 0, 1, 0),
				BackgroundTransparency = 1,
				ZIndex = 2,
				[Children] = userChildren,
			} else nil,
		}

		local rootProps: { [any]: any } = {
			BackgroundColor3 = visual.bgColor,
			BackgroundTransparency = visual.bgTransparency,
			[Children] = allChildren,
		}

		for key, value in passthroughProps do
			rootProps[key] = value
		end

		return scope:New("ImageLabel")(rootProps)

	else
		return RenderClippedCornersFallback(scope, radii, passthroughProps, userChildren, visual)
	end
end

return {
	NON_PASSTHROUGH_KEYS = NON_PASSTHROUGH_KEYS,
	UICORNER_HAS_INDIVIDUAL_RADII = UICORNER_HAS_INDIVIDUAL_RADII,
	GetPassthroughProps = GetPassthroughProps,
	RenderFallback = RenderFallback,
}


local PackagesIndex = script.Parent._Index

return require(PackagesIndex.SUPERCORNER_FUSION03["supercorner-fusion03"])


--!strict

local React = require(script.Parent.Parent.Parent._Index["jsdotlua_react@17.2.1"].react)
local Supercorner =
    require(script.Parent.Parent.Parent._Index["cameronpcampbell_supercorner@0.0.8"].supercorner)

local CreateElement = React.createElement

const CREATE_ONLY_PROPS = { "MaxCornerRadius", "MaxTopLeftRadius", "MaxTopRightRadius", "MaxBottomRightRadius", "MaxBottomLeftRadius", "Redrawable" }

const SUPERCORNER_KEYS = {
    CornerRadius = true, CornerSmoothing = true,
    TopLeftRadius = true, TopRightRadius = true,
    BottomRightRadius = true, BottomLeftRadius = true,
    MaxCornerRadius = true, MaxTopLeftRadius = true,
    MaxTopRightRadius = true, MaxBottomRightRadius = true,
    MaxBottomLeftRadius = true, PreserveSmoothing = true,
    AntiAlias = true, Redrawable = true,
    StrokeThickness = true, StrokeColor = true,
    StrokeTransparency = true, StrokeGradient = true,
}

const NON_PASSTHROUGH_KEYS = {
    BackgroundColor3 = true,
    BackgroundTransparency = true,
    children = true,
}

const UICORNER_HAS_INDIVIDUAL_RADII = (function()
    local reflectionService = game:GetService("ReflectionService")
    local reflection: { [any]: any } = reflectionService:GetPropertiesOfClass("UICorner")

    return
        reflection.TopLeftRadius and reflection.TopRightRadius and
        reflection.BottomLeftRadius and reflection.BottomRightRadius
end)()

local function RemoveNils(tble: { any }): { any }
    local idx = 1

    while idx <= #tble do
        if tble[idx] == nil then
            tble[idx] = tble[#tble]
            tble[#tble] = nil

        else
            idx += 1
        end
    end

    return tble
end

local function TryCreateSupercorner(props: { [any]: any }, stroke: number): Supercorner.Supercorner?
    local _ok, supercorner = pcall(Supercorner.new, {
        CornerRadius = props.CornerRadius,
        CornerSmoothing = props.CornerSmoothing,
        TopLeftRadius = props.TopLeftRadius,
        TopRightRadius = props.TopRightRadius,
        BottomRightRadius = props.BottomRightRadius,
        BottomLeftRadius = props.BottomLeftRadius,
        MaxCornerRadius = props.MaxCornerRadius,
        MaxTopLeftRadius = props.MaxTopLeftRadius,
        MaxTopRightRadius = props.MaxTopRightRadius,
        MaxBottomRightRadius = props.MaxBottomRightRadius,
        MaxBottomLeftRadius = props.MaxBottomLeftRadius,
        PreserveSmoothing = props.PreserveSmoothing,
        AntiAlias = props.AntiAlias,
        Redrawable = props.Redrawable,
        Stroke = stroke,
    })

    if not supercorner then
        warn("[Supercorner React] Failed to create Supercorner, possibly due to the editable memory budget being reached. Falling back to UICorner.")
    end

    return supercorner
end

local function GetPassthroughProps(props: { [any]: any }): { [any]: any }
    local result = {}

    for key, value in props do
        if not SUPERCORNER_KEYS[key] and not NON_PASSTHROUGH_KEYS[key] then
            result[key] = value
        end
    end

    return result
end

local function RenderSupercorner(
    props: { [any]: any },
    fillSupercorner: Supercorner.Supercorner?,
    strokeSupercorner: Supercorner.Supercorner?
)
    local passthroughProps = GetPassthroughProps(props)
    local children: { [any]: any }? = props.children

    if fillSupercorner and strokeSupercorner then
        local fillProps = {
            ImageContent = fillSupercorner:GetContent(),
            ScaleType = Enum.ScaleType.Slice,
            SliceCenter = fillSupercorner:GetSliceCenter(),
            SliceScale = fillSupercorner:GetSliceScale(),
            BackgroundTransparency = 1,
            ImageColor3 = props.BackgroundColor3,
            ImageTransparency = props.BackgroundTransparency,
        }

        for key, value in passthroughProps do
            fillProps[key] = value
        end

        local allChildren: { [any]: any } = {
            StrokeImage = CreateElement("ImageLabel", {
                ImageContent = strokeSupercorner:GetContent(),
                ScaleType = Enum.ScaleType.Slice,
                SliceCenter = strokeSupercorner:GetSliceCenter(),
                SliceScale = strokeSupercorner:GetSliceScale(),
                BackgroundTransparency = 1,
                ImageColor3 = props.StrokeColor,
                ImageTransparency = props.StrokeTransparency,
                Size = UDim2.new(1, 0, 1, 0),
            }, if props.StrokeGradient then { StrokeGradient = props.StrokeGradient } else nil),
        }

        if children then
            allChildren.ChildrenFrame = CreateElement("Frame", {
                Size = UDim2.new(1, 0, 1, 0),
                BackgroundTransparency = 1,
                ZIndex = 2,
            }, children)
        end

        return CreateElement("ImageLabel", fillProps, allChildren)

    elseif fillSupercorner then
        local fillProps = {
            ImageContent = fillSupercorner:GetContent(),
            ScaleType = Enum.ScaleType.Slice,
            SliceCenter = fillSupercorner:GetSliceCenter(),
            SliceScale = fillSupercorner:GetSliceScale(),
            BackgroundTransparency = 1,
            ImageColor3 = props.BackgroundColor3,
            ImageTransparency = props.BackgroundTransparency,
        }

        for key, value in passthroughProps do
            fillProps[key] = value
        end

        if children then
            return CreateElement("ImageLabel", fillProps, {
                ChildrenFrame = CreateElement("Frame", {
                    Size = UDim2.new(1, 0, 1, 0),
                    BackgroundTransparency = 1,
                    ZIndex = 2,
                }, children),
            })
        end

        return CreateElement("ImageLabel", fillProps)

    elseif strokeSupercorner then
        local strokeProps = {
            ImageContent = strokeSupercorner:GetContent(),
            ScaleType = Enum.ScaleType.Slice,
            SliceCenter = strokeSupercorner:GetSliceCenter(),
            SliceScale = strokeSupercorner:GetSliceScale(),
            BackgroundTransparency = 1,
            ImageColor3 = props.StrokeColor,
            ImageTransparency = props.StrokeTransparency,
        }

        for key, value in passthroughProps do
            strokeProps[key] = value
        end

        local allChildren: { [any]: any }? = nil

        if props.StrokeGradient or children then
            allChildren = {}

            if props.StrokeGradient then
                allChildren.StrokeGradient = props.StrokeGradient
            end

            if children then
                allChildren.ChildrenFrame = CreateElement("Frame", {
                    Size = UDim2.new(1, 0, 1, 0),
                    BackgroundTransparency = 1,
                    ZIndex = 2,
                }, children)
            end
        end

        return CreateElement("ImageLabel", strokeProps, allChildren)
    end

    return nil
end

local function CreateCornerClip(
    radius: number,
    clipPosition: UDim2,
    fillPosition: UDim2,
    backgroundColor: Color3?,
    backgroundTransparency: number?,
    strokeThickness: number?,
    strokeColor: Color3?,
    strokeTransparency: number?,
    strokeGradient: any?
)
    local fillChildren: { [any]: any } = {
        UICorner = CreateElement("UICorner", {
            CornerRadius = UDim.new(0, radius),
        }),
    }

    if (strokeThickness or 0) > 0 then
        local gradientClone = if strokeGradient
            then React.cloneElement(strokeGradient)
            else nil

        fillChildren.UIStroke = CreateElement("UIStroke", {
            Thickness = strokeThickness,
            Color = if strokeGradient then Color3.new(1, 1, 1) else strokeColor,
            Transparency = strokeTransparency,
            ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
            BorderStrokePosition = Enum.BorderStrokePosition.Inner,
        }, if gradientClone then { StrokeGradient = gradientClone } else nil)
    end

    return CreateElement("Frame", {
        Size = UDim2.new(0.5, 0, 0.5, 0),
        Position = clipPosition,
        BackgroundTransparency = 1,
        ClipsDescendants = true,
    }, {
        Fill = CreateElement("Frame", {
            Size = UDim2.new(2, 0, 2, 0),
            Position = fillPosition,
            BackgroundColor3 = backgroundColor,
            BackgroundTransparency = backgroundTransparency,
            ZIndex = 0,
        }, fillChildren),
    })
end

local function RenderClippedCornersFallback(
    props: { [any]: any },
    imageLabelProps: { [any]: any },
    allChildren: { [any]: any }
)
    local defaultRadius = props.CornerRadius or 0
    local bgColor = props.BackgroundColor3
    local bgTransparency = props.BackgroundTransparency
    local strokeThickness = props.StrokeThickness
    local strokeColor = props.StrokeColor
    local strokeTransparency = props.StrokeTransparency
    local strokeGradient = props.StrokeGradient

    imageLabelProps.BackgroundTransparency = 1

    allChildren.TopLeftClip = CreateCornerClip(
        props.TopLeftRadius or defaultRadius,
        UDim2.new(0, 0, 0, 0),
        UDim2.new(0, 0, 0, 0),
        bgColor, bgTransparency,
        strokeThickness, strokeColor, strokeTransparency, strokeGradient
    )

    allChildren.TopRightClip = CreateCornerClip(
        props.TopRightRadius or defaultRadius,
        UDim2.new(0.5, 0, 0, 0),
        UDim2.new(-1, 0, 0, 0),
        bgColor, bgTransparency,
        strokeThickness, strokeColor, strokeTransparency, strokeGradient
    )

    allChildren.BottomLeftClip = CreateCornerClip(
        props.BottomLeftRadius or defaultRadius,
        UDim2.new(0, 0, 0.5, 0),
        UDim2.new(0, 0, -1, 0),
        bgColor, bgTransparency,
        strokeThickness, strokeColor, strokeTransparency, strokeGradient
    )

    allChildren.BottomRightClip = CreateCornerClip(
        props.BottomRightRadius or defaultRadius,
        UDim2.new(0.5, 0, 0.5, 0),
        UDim2.new(-1, 0, -1, 0),
        bgColor, bgTransparency,
        strokeThickness, strokeColor, strokeTransparency, strokeGradient
    )

    return CreateElement("ImageLabel", imageLabelProps, allChildren)
end

local function RenderFallback(props: { [any]: any })
    local imageLabelProps = {}

    for key, value in props do
        if not SUPERCORNER_KEYS[key] and key ~= "children" then
            imageLabelProps[key] = value
        end
    end

    local needsStroke = (props.StrokeThickness or 0) > 0

    local uiStroke = if needsStroke
        then CreateElement("UIStroke", {
            Thickness = props.StrokeThickness,
            Color = if props.StrokeGradient then Color3.new(1, 1, 1) else props.StrokeColor,
            Transparency = props.StrokeTransparency,
            ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
            BorderStrokePosition = Enum.BorderStrokePosition.Inner,
        }, if props.StrokeGradient then { StrokeGradient = props.StrokeGradient } else nil)
        else nil

    local allChildren: { [any]: any } = {}

    if props.children then
        allChildren.ChildrenFrame = CreateElement("Frame", {
            Size = UDim2.new(1, 0, 1, 0),
            BackgroundTransparency = 1,
            ZIndex = 2,
        }, props.children :: { [any]: any }?)
    end

    if UICORNER_HAS_INDIVIDUAL_RADII then
        allChildren.UIStroke = uiStroke
        allChildren.UICorner = CreateElement("UICorner", {
            TopLeftRadius = UDim.new(0, props.TopLeftRadius or props.CornerRadius or 0),
            TopRightRadius = UDim.new(0, props.TopRightRadius or props.CornerRadius or 0),
            BottomRightRadius = UDim.new(0, props.BottomRightRadius or props.CornerRadius or 0),
            BottomLeftRadius = UDim.new(0, props.BottomLeftRadius or props.CornerRadius or 0),
        })

        return CreateElement("ImageLabel", imageLabelProps, allChildren)

    else
        return RenderClippedCornersFallback(props, imageLabelProps, allChildren)
    end
end

local function WarnOnCreateOnlyPropChange(props: { [any]: any })
    local createOnlyRef = React.useRef({
        MaxCornerRadius = props.MaxCornerRadius,
        MaxTopLeftRadius = props.MaxTopLeftRadius,
        MaxTopRightRadius = props.MaxTopRightRadius,
        MaxBottomRightRadius = props.MaxBottomRightRadius,
        MaxBottomLeftRadius = props.MaxBottomLeftRadius,
        Redrawable = props.Redrawable,
    })

    local prevProps = createOnlyRef.current

    for propName in CREATE_ONLY_PROPS do
        if props[propName] == prevProps[propName] then continue end

        warn(`[Supercorner React] '{propName}' cannot be updated after mount.`)
    end

    createOnlyRef.current = {
        MaxCornerRadius = props.MaxCornerRadius,
        MaxTopLeftRadius = props.MaxTopLeftRadius,
        MaxTopRightRadius = props.MaxTopRightRadius,
        MaxBottomRightRadius = props.MaxBottomRightRadius,
        MaxBottomLeftRadius = props.MaxBottomLeftRadius,
        Redrawable = props.Redrawable,
    }
end

local function RedrawFill(
    props: { [any]: any },
    supercorner: Supercorner.Supercorner?,
    setRedrawVersion: (updater: (v: number) -> number) -> ()
)
    local hasMounted = React.useRef(false)

    local redrawParams = React.useRef(nil :: Supercorner.RedrawParams?)
    redrawParams.current = {
        CornerRadius = props.CornerRadius,
        CornerSmoothing = props.CornerSmoothing,
        TopLeftRadius = props.TopLeftRadius,
        TopRightRadius = props.TopRightRadius,
        BottomRightRadius = props.BottomRightRadius,
        BottomLeftRadius = props.BottomLeftRadius,
        PreserveSmoothing = props.PreserveSmoothing,
        AntiAlias = props.AntiAlias,
    }

    React.useEffect(function()
        if not hasMounted.current then
            hasMounted.current = true
            return
        end

        if not supercorner then return end

        local ok, err = (pcall :: any)(supercorner.Redraw, supercorner, redrawParams.current)

        if not ok then
            warn(`[Supercorner React] Failed to redraw Supercorner. Make sure the component was created with \`Redrawable = true\`. Error: {err}`)
            return
        end

        setRedrawVersion(function(v: number) return v + 1 end)
    end, RemoveNils({
        props.CornerRadius,
        props.CornerSmoothing,
        props.TopLeftRadius,
        props.TopRightRadius,
        props.BottomRightRadius,
        props.BottomLeftRadius,
        props.PreserveSmoothing,
        props.AntiAlias,
    }) :: any)
end

local function RedrawStroke(
    props: { [any]: any },
    supercorner: Supercorner.Supercorner?,
    setRedrawVersion: (updater: (v: number) -> number) -> ()
)
    local hasMounted = React.useRef(false)

    local redrawParams = React.useRef(nil :: Supercorner.RedrawParams?)
    redrawParams.current = {
        CornerRadius = props.CornerRadius,
        CornerSmoothing = props.CornerSmoothing,
        TopLeftRadius = props.TopLeftRadius,
        TopRightRadius = props.TopRightRadius,
        BottomRightRadius = props.BottomRightRadius,
        BottomLeftRadius = props.BottomLeftRadius,
        PreserveSmoothing = props.PreserveSmoothing,
        AntiAlias = props.AntiAlias,
        Stroke = props.StrokeThickness,
    }

    React.useEffect(function()
        if not hasMounted.current then
            hasMounted.current = true
            return
        end

        if not supercorner then return end

        local ok, err = (pcall :: any)(supercorner.Redraw, supercorner, redrawParams.current)

        if not ok then
            warn(`[Supercorner React] Failed to redraw stroke Supercorner. Make sure the component was created with \`Redrawable = true\`. Error: {err}`)
            return
        end

        setRedrawVersion(function(v: number) return v + 1 end)
    end, RemoveNils({
        props.CornerRadius,
        props.CornerSmoothing,
        props.TopLeftRadius,
        props.TopRightRadius,
        props.BottomRightRadius,
        props.BottomLeftRadius,
        props.PreserveSmoothing,
        props.AntiAlias,
        props.StrokeThickness,
    }) :: any)
end

return function(props: Supercorner.CreateParams & {
    BackgroundColor3: Color3?,
    BackgroundTransparency: number?,
    StrokeThickness: number?,
    StrokeColor: Color3?,
    StrokeTransparency: number?,
    StrokeGradient: UIGradient?,
    [any]: any
})
    local needsFill = (props.BackgroundTransparency or 0) ~= 1
    local needsStroke = (props.StrokeThickness or 0) > 0

    local fillRef = React.useRef(nil :: Supercorner.Supercorner?)
    local strokeRef = React.useRef(nil :: Supercorner.Supercorner?)

    local _version, setVersion = React.useState(0)

    local bumpVersion = function()
        setVersion(function(v: number) return v + 1 end)
    end

    -- Initial creation (runs once, avoids blank first frame)
    React.useState(function()
        if needsFill then
            fillRef.current = TryCreateSupercorner(props, 0)
        end

        if needsStroke then
            strokeRef.current = TryCreateSupercorner(props, props.StrokeThickness :: number)
        end

        return nil
    end)

    -- Toggle fill on/off
    React.useEffect(function()
        if needsFill and not fillRef.current then
            fillRef.current = TryCreateSupercorner(props, 0)
            bumpVersion()

        elseif not needsFill and fillRef.current then
            fillRef.current:Destroy()
            fillRef.current = nil
            bumpVersion()
        end
    end, { needsFill })

    -- Toggle stroke on/off
    React.useEffect(function()
        if needsStroke and not strokeRef.current then
            strokeRef.current = TryCreateSupercorner(props, props.StrokeThickness :: number)
            bumpVersion()

        elseif not needsStroke and strokeRef.current then
            strokeRef.current:Destroy()
            strokeRef.current = nil
            bumpVersion()
        end
    end, { needsStroke })

    -- Unmount cleanup
    React.useEffect(function()
        return function()
            if fillRef.current then fillRef.current:Destroy() end
            if strokeRef.current then strokeRef.current:Destroy() end
        end
    end, {})

    local _fillRedrawVersion, setFillRedrawVersion = React.useState(0)
    local _strokeRedrawVersion, setStrokeRedrawVersion = React.useState(0)

    WarnOnCreateOnlyPropChange(props)
    RedrawFill(props, fillRef.current, setFillRedrawVersion)
    RedrawStroke(props, strokeRef.current, setStrokeRedrawVersion)

    local fill = fillRef.current
    local stroke = strokeRef.current

    return
        if fill or stroke then RenderSupercorner(props, fill, stroke)
        else RenderFallback(props)
end


local PackagesIndex = script.Parent._Index

return require(PackagesIndex.SUPERCORNER_REACT["supercorner-react"])


local StdLib = {}

-- Console
StdLib.console = {
    log = function(...)
        print(...)
    end,
    warn = function(...)
        warn(...)
    end,
    error = function(...)
        error(table.concat({...}, " "), 0)
    end,
}

-- Math object mapping to Lua's math library
StdLib.Math = {
    abs = math.abs,
    floor = math.floor,
    ceil = math.ceil,
    max = math.max,
    min = math.min,
    pow = math.pow or function(a, b) return a ^ b end,
    random = math.random,
    sqrt = math.sqrt,
    sin = math.sin,
    cos = math.cos,
    tan = math.tan,
    PI = math.pi,
    E = math.exp(1),
}

-- JSON using HttpService
local HttpService = game:GetService("HttpService")
StdLib.JSON = {
    stringify = function(tbl)
        return HttpService:JSONEncode(tbl)
    end,
    parse = function(str)
        return HttpService:JSONDecode(str)
    end,
}

-- Timing utilities similar to setTimeout / setInterval
local TimerManager = {}
local intervalThreads = {}

function StdLib.setTimeout(callback, ms)
    return task.delay((ms or 0)/1000, callback)
end

function StdLib.clearTimeout(thread)
    if thread then
        task.cancel(thread)
    end
end

function StdLib.setInterval(callback, ms)
    local running = true
    local thread
    thread = task.spawn(function()
        while running do
            task.wait((ms or 0)/1000)
            callback()
        end
    end)
    intervalThreads[thread] = function()
        running = false
    end
    return thread
end

function StdLib.clearInterval(thread)
    local closer = intervalThreads[thread]
    if closer then closer() end
    if thread then task.cancel(thread) end
end

-- Global helpers
function StdLib.parseInt(value, base)
    base = base or 10
    local num = tonumber(value, base)
    if num then return math.floor(num) end
    return nil
end

function StdLib.parseFloat(value)
    return tonumber(value)
end

function StdLib.isNaN(value)
    return value ~= value
end

-- String utilities to match JS trim
globalTrim = function(s) return (s:gsub("^%s*(.-)%s*$", "%1")) end
StdLib.string = {
    trim = globalTrim,
    trimStart = function(s) return (s:gsub("^%s*", "")) end,
    trimEnd = function(s) return (s:gsub("%s*$", "")) end,
    toLowerCase = function(s) return string.lower(s) end,
    toUpperCase = function(s) return string.upper(s) end,
}

-- table (Object) helpers
StdLib.Object = {
    keys = function(tbl)
        local arr = {}
        for k in pairs(tbl) do table.insert(arr, k) end
        return arr
    end,
    values = function(tbl)
        local arr = {}
        for _, v in pairs(tbl) do table.insert(arr, v) end
        return arr
    end,
    entries = function(tbl)
        local arr = {}
        for k, v in pairs(tbl) do table.insert(arr, {k, v}) end
        return arr
    end,
}

-- DOM helpers
StdLib.DOM = {
    -- Create a new element
    createElement = function(tagName)
        return {
            tagName = tagName,
            attributes = {},
            children = {},
            style = {},

            -- Methods
            setAttribute = function(self, name, value)
                self.attributes[name] = value
                return self
            end,

            appendChild = function(self, child)
                table.insert(self.children, child)
                return self
            end,

            setStyle = function(self, property, value)
                self.style[property] = value
                return self
            end,

            setInnerHTML = function(self, html)
                self.innerHTML = html
                return self
            end,

            setInnerText = function(self, text)
                self.innerText = text
                return self
            end
        }
    end,

    -- Query selector helpers
    querySelector = function(selector)
        -- This is a stub - the actual implementation is in JSEngine
        return nil
    end,

    querySelectorAll = function(selector)
        -- This is a stub - the actual implementation is in JSEngine
        return {}
    end,

    -- Event handling
    addEventListener = function(element, eventType, handler)
        -- This is a stub - the actual implementation is in JSEngine
    end,

    -- Form validation
    validateForm = function(formElement)
        -- This is a stub - the actual implementation is in JSEngine
        return true
    end
}

-- Window helpers
StdLib.Window = {
    alert = function(message)
        print("ALERT: " .. tostring(message))
    end,

    confirm = function(message)
        print("CONFIRM: " .. tostring(message))
        return true -- Always return true in this implementation
    end,

    prompt = function(message, defaultValue)
        print("PROMPT: " .. tostring(message) .. " (default: " .. tostring(defaultValue) .. ")")
        return defaultValue
    end,

    open = function(url)
        print("Opening URL: " .. tostring(url))
        -- This would normally open a new window, but we'll just print it
    end,

    location = {
        href = "",
        host = "",
        pathname = "",
        search = "",
        hash = "",

        reload = function()
            print("Reloading page")
        end,

        replace = function(url)
            print("Replacing location with: " .. tostring(url))
        end
    }
}

return StdLib