-- BloxMind Studio v4.7.1
-- One-workspace Roblox Studio copilot for builds, selected-script tools, and project review.
-- AI build requests return validated blueprints. Nothing is inserted or overwritten
-- until the creator explicitly presses Preview / Add to Studio / Apply.

assert(plugin, "BloxMind Studio must run as a Roblox Studio plugin")

local HttpService = game:GetService("HttpService")
local Selection = game:GetService("Selection")
local StudioService = game:GetService("StudioService")
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local KeyframeSequenceProvider = game:GetService("KeyframeSequenceProvider")
local ServerStorage = game:GetService("ServerStorage")

local PLUGIN_VERSION = "4.7.1"
local API_BASE = "https://bloxmind.onrender.com"
local TOKEN_SETTING = "BloxMindStudioSessionToken"
local LOGO_SETTING = "BloxMindBrandImageAssetId"
local FIRST_OPEN_SETTING = "BloxMindStudioHasOpened"
local FALLBACK_TOOLBAR_ICON = "rbxassetid://104919049969988"
local SETTINGS_ICON = "rbxassetid://104919049969988" -- Roblox-documented gear icon
local MAX_NODES = 1600
local MAX_SELECTED_SCRIPTS = 12

local COLORS = {
    -- Match the BloxMind web app: near-black surfaces + warm gold accent.
    accent = Color3.fromRGB(232, 168, 56),
    accentSoft = Color3.fromRGB(166, 111, 31),
    cyan = Color3.fromRGB(255, 201, 77),
    success = Color3.fromRGB(74, 222, 128),
    warning = Color3.fromRGB(255, 191, 89),
    danger = Color3.fromRGB(239, 92, 102),
    dark = Color3.fromRGB(7, 7, 10),
    card = Color3.fromRGB(15, 15, 20),
    card2 = Color3.fromRGB(22, 22, 30),
    input = Color3.fromRGB(10, 11, 15),
    text = Color3.fromRGB(242, 242, 245),
    dim = Color3.fromRGB(142, 146, 158),
    line = Color3.fromRGB(42, 42, 54),
}

local state = {
    token = plugin:GetSetting(TOKEN_SETTING),
    projects = {},
    projectIndex = 1,
    entitlement = nil,
    latestSnapshotId = nil,
    latestAuditId = nil,
    latestSourceAttachmentId = nil,
    syncedSourceAttachmentsByPath = {},
    buildMode = "auto",
    activePage = "create",
    lastBlueprint = nil,
    lastBuildKind = nil,
    lastBuildId = nil,
    lastBuildTarget = nil,
    lastCode = "",
    lastCodeSourcePath = nil,
    lastCodeOriginalSource = nil,
    busy = false,
    progressGeneration = 0,
    previewInstances = {},
    settingsOpen = false,
    errorExpanded = false,
    modeMenuOpen = false,
    importOpen = false,
    toolsOpen = false,
    ideasOpen = false,
    lastPreflightSummary = nil,
    previewAnimationTrack = nil,
    previewAnimationObject = nil,
    previewAnimationSequence = nil,
    previewAnimatorCreated = nil,
    applyConfirm = false,
    doctorConfirm = false,
    fixConfirm = false,
}

local function assetUri(value)
    local raw = tostring(value or ""):gsub("%s+", "")
    local id = raw:match("(%d+)")
    return id and ("rbxassetid://" .. id) or ""
end

local function configuredLogoUri()
    return assetUri(plugin:GetSetting(LOGO_SETTING))
end

print("[BloxMind Studio] Loading plugin v" .. PLUGIN_VERSION)

local firstOpen = plugin:GetSetting(FIRST_OPEN_SETTING) ~= true
local toolbar = plugin:CreateToolbar("BloxMind")
local initialToolbarIcon = configuredLogoUri()
if initialToolbarIcon == "" then initialToolbarIcon = FALLBACK_TOOLBAR_ICON end
local toolbarButton = toolbar:CreateButton(
    "BloxMindStudioV3",
    "Open BloxMind Studio",
    initialToolbarIcon,
    "BloxMind"
)
toolbarButton.ClickableWhenViewportHidden = true
toolbarButton.Enabled = true

-- Bindable Studio action. Creators can assign their own shortcut in Studio's
-- Customize Shortcuts window without BloxMind taking over a default keybind.
local openBloxMindAction = plugin:CreatePluginAction(
    "BloxMindOpenStudio",
    "Open BloxMind",
    "Open the BloxMind Studio workspace",
    initialToolbarIcon,
    true
)

local widgetInfo = DockWidgetPluginGuiInfo.new(
    Enum.InitialDockState.Right,
    firstOpen,
    false,
    560,
    720,
    430,
    500
)

local widget = plugin:CreateDockWidgetPluginGuiAsync("BloxMindStudioV3", widgetInfo)
widget.Title = "BloxMind"
if firstOpen then
    widget.Enabled = true
    plugin:SetSetting(FIRST_OPEN_SETTING, true)
end
toolbarButton:SetActive(widget.Enabled)
print("[BloxMind Studio] Widget ready. Enabled=" .. tostring(widget.Enabled))

local function round(gui, radius)
    local corner = Instance.new("UICorner")
    corner.CornerRadius = UDim.new(0, radius or 10)
    corner.Parent = gui
    return corner
end

local function outline(gui, color, transparency, thickness)
    local stroke = Instance.new("UIStroke")
    stroke.Color = color or COLORS.line
    stroke.Transparency = transparency or 0
    stroke.Thickness = thickness or 1
    stroke.Parent = gui
    return stroke
end

local function pad(gui, top, right, bottom, left)
    local p = Instance.new("UIPadding")
    p.PaddingTop = UDim.new(0, top or 0)
    p.PaddingRight = UDim.new(0, right or 0)
    p.PaddingBottom = UDim.new(0, bottom or 0)
    p.PaddingLeft = UDim.new(0, left or 0)
    p.Parent = gui
    return p
end

local function label(text, size, bold, color)
    local item = Instance.new("TextLabel")
    item.BackgroundTransparency = 1
    item.Size = UDim2.new(1, 0, 0, size or 22)
    item.AutomaticSize = Enum.AutomaticSize.Y
    item.Text = text or ""
    item.TextWrapped = true
    item.TextXAlignment = Enum.TextXAlignment.Left
    item.TextYAlignment = Enum.TextYAlignment.Center
    item.Font = bold and Enum.Font.BuilderSansBold or Enum.Font.BuilderSans
    item.TextSize = bold and 16 or 14
    item.TextColor3 = color or COLORS.text
    return item
end

local function button(text, height, accent)
    local item = Instance.new("TextButton")
    item.Size = UDim2.new(1, 0, 0, height or 40)
    item.BackgroundColor3 = accent and COLORS.accent or COLORS.card2
    item.TextColor3 = COLORS.text
    item.Text = text
    item.Font = Enum.Font.BuilderSansBold
    item.TextSize = 14
    item.BorderSizePixel = 0
    item.AutoButtonColor = true
    round(item, 10)
    outline(item, accent and COLORS.accent or COLORS.line, accent and 0.2 or 0.35)
    return item
end

local function imageButton(image, tooltip, size)
    local item = Instance.new("ImageButton")
    item.Size = UDim2.fromOffset(size or 32, size or 32)
    item.BackgroundColor3 = COLORS.card2
    item.BorderSizePixel = 0
    item.Image = image or ""
    item.ImageColor3 = COLORS.text
    item.ImageTransparency = 0.05
    item.AutoButtonColor = true
    item:SetAttribute("Tooltip", tooltip or "")
    round(item, 10)
    outline(item, COLORS.line, 0.35)
    return item
end

local function textBox(placeholder, height)
    local item = Instance.new("TextBox")
    item.Size = UDim2.new(1, 0, 0, height or 42)
    item.BackgroundColor3 = COLORS.input
    item.TextColor3 = COLORS.text
    item.PlaceholderColor3 = COLORS.dim
    item.PlaceholderText = placeholder or ""
    item.Text = ""
    item.ClearTextOnFocus = false
    item.Font = Enum.Font.BuilderSans
    item.TextSize = 14
    item.TextXAlignment = Enum.TextXAlignment.Left
    item.TextYAlignment = Enum.TextYAlignment.Top
    item.TextWrapped = (height or 42) > 48
    item.MultiLine = (height or 42) > 48
    item.BorderSizePixel = 0
    round(item, 11)
    outline(item, COLORS.line, 0.3)
    pad(item, 10, 12, 10, 12)
    return item
end

local function card(height)
    local item = Instance.new("Frame")
    item.Size = UDim2.new(1, 0, 0, height or 64)
    item.BackgroundColor3 = COLORS.card
    item.BorderSizePixel = 0
    round(item, 13)
    outline(item, COLORS.line, 0.45)
    return item
end

local root = Instance.new("Frame")
root.Size = UDim2.fromScale(1, 1)
root.BackgroundColor3 = COLORS.dark
root.BorderSizePixel = 0
root.Parent = widget


-- Startup loader. This sits above the workspace only while BloxMind restores
-- the saved Studio session. It uses real connection state instead of a fake
-- percentage and disappears even when the creator needs to pair again.
local startupOverlay = Instance.new("Frame")
startupOverlay.Size = UDim2.fromScale(1, 1)
startupOverlay.BackgroundColor3 = COLORS.dark
startupOverlay.BorderSizePixel = 0
startupOverlay.ZIndex = 200
startupOverlay.Visible = true
startupOverlay.Parent = root

local startupLogo = Instance.new("ImageLabel")
startupLogo.Size = UDim2.fromOffset(58, 48)
startupLogo.AnchorPoint = Vector2.new(0.5, 0.5)
startupLogo.Position = UDim2.new(0.5, 0, 0.42, -28)
startupLogo.BackgroundTransparency = 1
startupLogo.ScaleType = Enum.ScaleType.Fit
startupLogo.Image = configuredLogoUri()
startupLogo.Visible = startupLogo.Image ~= ""
startupLogo.ZIndex = 202
startupLogo.Parent = startupOverlay

local startupBrand = label("BloxMind", 28, true)
startupBrand.AnchorPoint = Vector2.new(0.5, 0.5)
startupBrand.Position = UDim2.new(0.5, 0, 0.42, 14)
startupBrand.Size = UDim2.new(0.8, 0, 0, 28)
startupBrand.TextXAlignment = Enum.TextXAlignment.Center
startupBrand.TextSize = 20
startupBrand.ZIndex = 202
startupBrand.Parent = startupOverlay

local startupStatus = label("Opening Studio workspace…", 22, false, COLORS.dim)
startupStatus.AnchorPoint = Vector2.new(0.5, 0.5)
startupStatus.Position = UDim2.new(0.5, 0, 0.42, 42)
startupStatus.Size = UDim2.new(0.82, 0, 0, 22)
startupStatus.TextXAlignment = Enum.TextXAlignment.Center
startupStatus.TextSize = 11
startupStatus.ZIndex = 202
startupStatus.Parent = startupOverlay

local startupTrack = Instance.new("Frame")
startupTrack.Size = UDim2.fromOffset(150, 4)
startupTrack.AnchorPoint = Vector2.new(0.5, 0.5)
startupTrack.Position = UDim2.new(0.5, 0, 0.42, 70)
startupTrack.BackgroundColor3 = COLORS.card2
startupTrack.BorderSizePixel = 0
startupTrack.ZIndex = 202
round(startupTrack, 4)
startupTrack.Parent = startupOverlay

local startupFill = Instance.new("Frame")
startupFill.Size = UDim2.new(0.28, 0, 1, 0)
startupFill.Position = UDim2.fromScale(0, 0)
startupFill.BackgroundColor3 = COLORS.accent
startupFill.BorderSizePixel = 0
startupFill.ZIndex = 203
round(startupFill, 4)
startupFill.Parent = startupTrack

local startupGeneration = 0
local function showStartupLoading(message)
    startupGeneration += 1
    local generation = startupGeneration
    startupStatus.Text = message or "Opening Studio workspace…"
    startupOverlay.Visible = true
    task.spawn(function()
        local direction = 1
        while startupOverlay.Visible and generation == startupGeneration do
            local target = direction == 1 and 0.72 or 0
            startupFill:TweenPosition(UDim2.new(target, 0, 0, 0), Enum.EasingDirection.InOut, Enum.EasingStyle.Quad, 0.55, true)
            direction *= -1
            task.wait(0.58)
        end
    end)
end

local function hideStartupLoading(message)
    startupGeneration += 1
    if message then startupStatus.Text = message end
    startupFill.Position = UDim2.fromScale(0, 0)
    startupFill.Size = UDim2.new(1, 0, 1, 0)
    task.delay(0.18, function()
        if startupOverlay.Parent then
            startupOverlay.Visible = false
            startupFill.Size = UDim2.new(0.28, 0, 1, 0)
        end
    end)
end

local header = Instance.new("Frame")
header.Size = UDim2.new(1, 0, 0, 68)
header.BackgroundColor3 = COLORS.dark
header.BorderSizePixel = 0
header.Parent = root

local brandLogo = Instance.new("ImageLabel")
brandLogo.Size = UDim2.fromOffset(40, 32)
brandLogo.Position = UDim2.fromOffset(14, 16)
brandLogo.BackgroundTransparency = 1
brandLogo.ScaleType = Enum.ScaleType.Fit
brandLogo.Image = configuredLogoUri()
brandLogo.Visible = brandLogo.Image ~= ""
brandLogo.Parent = header

local brand = label("BloxMind", 22, true)
brand.Position = UDim2.fromOffset(brandLogo.Visible and 60 or 16, 12)
brand.Size = UDim2.new(0.5, -(brandLogo.Visible and 60 or 16), 0, 24)
brand.TextSize = 18
brand.Parent = header

local projectMini = label("Connecting…", 20, false, COLORS.dim)
projectMini.Position = UDim2.fromOffset(brandLogo.Visible and 60 or 16, 35)
projectMini.Size = UDim2.new(0.55, -(brandLogo.Visible and 60 or 16), 0, 20)
projectMini.TextSize = 12
projectMini.Parent = header

local creditsPill = button("Credits —", 32, false)
creditsPill.Size = UDim2.fromOffset(80, 32)
creditsPill.AnchorPoint = Vector2.new(1, 0)
creditsPill.Position = UDim2.new(1, -56, 0, 17)
creditsPill.TextSize = 12
creditsPill.AutoButtonColor = false
creditsPill.Parent = header

local settingsButton = imageButton(SETTINGS_ICON, "Studio settings", 32)
settingsButton.Size = UDim2.fromOffset(34, 32)
settingsButton.AnchorPoint = Vector2.new(1, 0)
settingsButton.Position = UDim2.new(1, -16, 0, 17)
settingsButton.Parent = header

local headerLine = Instance.new("Frame")
headerLine.Size = UDim2.new(1, 0, 0, 1)
headerLine.Position = UDim2.new(0, 0, 1, -1)
headerLine.BackgroundColor3 = COLORS.line
headerLine.BorderSizePixel = 0
headerLine.Parent = header

local scroll = Instance.new("ScrollingFrame")
scroll.Position = UDim2.fromOffset(0, 68)
scroll.Size = UDim2.new(1, 0, 1, -68)
scroll.BackgroundTransparency = 1
scroll.BorderSizePixel = 0
scroll.ScrollBarThickness = 5
scroll.ScrollBarImageColor3 = COLORS.line
scroll.CanvasSize = UDim2.new()
scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
scroll.Parent = root
pad(scroll, 14, 16, 20, 16)

local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0, 10)
layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = scroll

-- Connection/settings card. Hidden after pairing unless the creator opens Settings.
local UI = {}
UI.connectionCard = card(304)
UI.connectionCard.LayoutOrder = 1
UI.connectionCard.Parent = scroll
UI.connectionTitle = label("Connect BloxMind", 24, true)
UI.connectionTitle.Position = UDim2.fromOffset(14, 12)
UI.connectionTitle.Size = UDim2.new(1, -28, 0, 24)
UI.connectionTitle.Parent = UI.connectionCard
UI.connectionHint = label("Pair once, then BloxMind remembers this Studio. Advanced connection controls stay out of your way.", 40, false, COLORS.dim)
UI.connectionHint.Position = UDim2.fromOffset(14, 38)
UI.connectionHint.Size = UDim2.new(1, -28, 0, 40)
UI.connectionHint.TextSize = 12
UI.connectionHint.Parent = UI.connectionCard
UI.pairCodeBox = textBox("One-time pairing code", 40)
UI.pairCodeBox.Position = UDim2.fromOffset(14, 84)
UI.pairCodeBox.Size = UDim2.new(1, -28, 0, 40)
UI.pairCodeBox.Parent = UI.connectionCard
UI.pairButton = button("Pair this Studio", 40, true)
UI.pairButton.Position = UDim2.fromOffset(14, 132)
UI.pairButton.Size = UDim2.new(1, -28, 0, 40)
UI.pairButton.Parent = UI.connectionCard
UI.projectButton = button("Project: loading…", 36, false)
UI.projectButton.Position = UDim2.fromOffset(14, 180)
UI.projectButton.Size = UDim2.new(0.62, -18, 0, 36)
UI.projectButton.Parent = UI.connectionCard
UI.syncButton = button("Sync now", 36, false)
UI.syncButton.Position = UDim2.new(0.62, 4, 0, 180)
UI.syncButton.Size = UDim2.new(0.38, -18, 0, 36)
UI.syncButton.Parent = UI.connectionCard
UI.brandAssetBox = textBox("BloxMind logo Roblox image asset ID (optional)", 36)
UI.brandAssetBox.Position = UDim2.fromOffset(14, 222)
UI.brandAssetBox.Size = UDim2.new(0.7, -18, 0, 36)
UI.brandAssetBox.Text = tostring(plugin:GetSetting(LOGO_SETTING) or "")
UI.brandAssetBox.Parent = UI.connectionCard
UI.saveBrandButton = button("Use logo", 36, false)
UI.saveBrandButton.Position = UDim2.new(0.7, 4, 0, 222)
UI.saveBrandButton.Size = UDim2.new(0.3, -18, 0, 36)
UI.saveBrandButton.Parent = UI.connectionCard
UI.disconnectButton = button("Disconnect Studio", 26, false)
UI.disconnectButton.Position = UDim2.fromOffset(14, 266)
UI.disconnectButton.Size = UDim2.new(1, -28, 0, 26)
UI.disconnectButton.TextSize = 11
UI.disconnectButton.TextColor3 = COLORS.dim
UI.disconnectButton.Parent = UI.connectionCard

UI.tabBar = Instance.new("Frame")
UI.tabBar.LayoutOrder = 2
UI.tabBar.Size = UDim2.new(1, 0, 0, 0)
UI.tabBar.Visible = false
UI.tabBar.BackgroundTransparency = 1
UI.tabBar.Parent = scroll
UI.tabLayout = Instance.new("UIListLayout")
UI.tabLayout.FillDirection = Enum.FillDirection.Horizontal
UI.tabLayout.Padding = UDim.new(0, 8)
UI.tabLayout.Parent = UI.tabBar
UI.createTab = button("Create", 40, true)
UI.createTab.Size = UDim2.new(1/3, -6, 0, 40)
UI.createTab.Parent = UI.tabBar
UI.codeTab = button("Code", 40, false)
UI.codeTab.Size = UDim2.new(1/3, -6, 0, 40)
UI.codeTab.Parent = UI.tabBar
UI.reviewTab = button("Review", 40, false)
UI.reviewTab.Size = UDim2.new(1/3, -6, 0, 40)
UI.reviewTab.Parent = UI.tabBar

UI.selectionCard = card(62)
UI.selectionCard.LayoutOrder = 2
UI.selectionCard.Parent = scroll
UI.selectionTitle = label("Nothing selected", 22, true)
UI.selectionTitle.Position = UDim2.fromOffset(12, 8)
UI.selectionTitle.Size = UDim2.new(1, -24, 0, 22)
UI.selectionTitle.TextSize = 13
UI.selectionTitle.Parent = UI.selectionCard
UI.selectionSub = label("Select a character, object, UI, or script to give BloxMind context.", 22, false, COLORS.dim)
UI.selectionSub.Position = UDim2.fromOffset(12, 31)
UI.selectionSub.Size = UDim2.new(1, -24, 0, 22)
UI.selectionSub.TextSize = 11
UI.selectionSub.Parent = UI.selectionCard

-- CREATE PAGE
UI.createPage = Instance.new("Frame")
UI.createPage.LayoutOrder = 3
UI.createPage.Size = UDim2.new(1, 0, 0, 600)
UI.createPage.AutomaticSize = Enum.AutomaticSize.Y
UI.createPage.BackgroundColor3 = COLORS.card
UI.createPage.BackgroundTransparency = 0
UI.createPage.BorderSizePixel = 0
round(UI.createPage, 16)
outline(UI.createPage, COLORS.line, 0.35)
pad(UI.createPage, 16, 16, 16, 16)
UI.createPage.Parent = scroll
UI.createLayout = Instance.new("UIListLayout")
UI.createLayout.Padding = UDim.new(0, 10)
UI.createLayout.Parent = UI.createPage

UI.createHero = label("Ask BloxMind", 32, true)
UI.createHero.TextSize = 21
UI.createHero.Parent = UI.createPage
UI.createHint = label("One prompt. Smart Build picks the likely workflow, or choose a build type when you want control.", 30, false, COLORS.dim)
UI.createHint.TextSize = 12
UI.createHint.Parent = UI.createPage

UI.actionRow = Instance.new("Frame")
UI.actionRow.Size = UDim2.new(1, 0, 0, 38)
UI.actionRow.BackgroundTransparency = 1
UI.actionRow.Parent = UI.createPage
UI.actionLayout = Instance.new("UIListLayout")
UI.actionLayout.FillDirection = Enum.FillDirection.Horizontal
UI.actionLayout.Padding = UDim.new(0, 8)
UI.actionLayout.Parent = UI.actionRow
UI.modePickerButton = button("Smart Build", 38, true)
UI.modePickerButton.Size = UDim2.new(0.5, -4, 0, 38)
UI.modePickerButton.Parent = UI.actionRow
UI.importToggleButton = button("Import", 38, false)
UI.importToggleButton.Size = UDim2.new(0.25, -4, 0, 38)
UI.importToggleButton.Parent = UI.actionRow
UI.toolsToggleButton = button("Tools", 38, false)
UI.toolsToggleButton.Size = UDim2.new(0.25, -4, 0, 38)
UI.toolsToggleButton.Parent = UI.actionRow

UI.modeMenu = card(124)
UI.modeMenu.Visible = false
UI.modeMenu.AutomaticSize = Enum.AutomaticSize.Y
UI.modeMenu.Parent = UI.createPage
pad(UI.modeMenu, 8, 8, 8, 8)
UI.modeLayout = Instance.new("UIGridLayout")
UI.modeLayout.CellPadding = UDim2.fromOffset(6, 6)
UI.modeLayout.CellSize = UDim2.new(1/3, -4, 0, 36)
UI.modeLayout.FillDirectionMaxCells = 3
UI.modeLayout.Parent = UI.modeMenu
UI.autoButton = button("Smart", 36, true); UI.autoButton.Parent = UI.modeMenu
UI.effectButton = button("Effect", 36, false); UI.effectButton.Parent = UI.modeMenu
UI.modelButton = button("Model", 36, false); UI.modelButton.Parent = UI.modeMenu
UI.uiButton = button("UI", 36, false); UI.uiButton.Parent = UI.modeMenu
UI.scriptButton = button("System", 36, false); UI.scriptButton.Parent = UI.modeMenu
UI.soundButton = button("Sound · Plus", 36, false); UI.soundButton.Parent = UI.modeMenu
UI.animationButton = button("Animation · Pro Beta", 36, false); UI.animationButton.Parent = UI.modeMenu

UI.importCard = card(122)
UI.importCard.Visible = false
UI.importCard.AutomaticSize = Enum.AutomaticSize.Y
UI.importCard.Parent = UI.createPage
pad(UI.importCard, 12, 12, 12, 12)
UI.importLayout = Instance.new("UIListLayout")
UI.importLayout.Padding = UDim.new(0, 8)
UI.importLayout.Parent = UI.importCard
UI.importTitle = label("Import into Studio", 22, true)
UI.importTitle.TextSize = 14
UI.importTitle.Parent = UI.importCard
UI.importHint = label("Use Roblox's native importer. FBX Animation requires a selected character rig.", 28, false, COLORS.dim)
UI.importHint.TextSize = 11
UI.importHint.Parent = UI.importCard
UI.importButtons = Instance.new("Frame")
UI.importButtons.Size = UDim2.new(1, 0, 0, 36)
UI.importButtons.BackgroundTransparency = 1
UI.importButtons.Parent = UI.importCard
UI.importButtonsLayout = Instance.new("UIListLayout")
UI.importButtonsLayout.FillDirection = Enum.FillDirection.Horizontal
UI.importButtonsLayout.Padding = UDim.new(0, 8)
UI.importButtonsLayout.Parent = UI.importButtons
UI.importRigButton = button("Import FBX rig (R15)", 36, false)
UI.importRigButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.importRigButton.Parent = UI.importButtons
UI.importAnimationButton = button("Import FBX animation", 36, false)
UI.importAnimationButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.importAnimationButton.Parent = UI.importButtons

UI.createPrompt = textBox("Describe what you want BloxMind to build in Studio…", 118)
UI.createPrompt.Parent = UI.createPage

UI.ideasToggleButton = button("Ideas & presets", 32, false)
UI.ideasToggleButton.TextSize = 11
UI.ideasToggleButton.Parent = UI.createPage

UI.ideasCard = card(92)
UI.ideasCard.Visible = false
UI.ideasCard.AutomaticSize = Enum.AutomaticSize.Y
UI.ideasCard.Parent = UI.createPage
pad(UI.ideasCard, 8, 8, 8, 8)
UI.ideasGrid = Instance.new("UIGridLayout")
UI.ideasGrid.CellPadding = UDim2.fromOffset(7, 7)
UI.ideasGrid.CellSize = UDim2.new(0.5, -4, 0, 34)
UI.ideasGrid.FillDirectionMaxCells = 2
UI.ideasGrid.Parent = UI.ideasCard
UI.quickTask1 = button("Preset", 34, false); UI.quickTask1.Parent = UI.ideasCard
UI.quickTask2 = button("Preset", 34, false); UI.quickTask2.Parent = UI.ideasCard
UI.quickTask3 = button("Preset", 34, false); UI.quickTask3.Parent = UI.ideasCard
UI.quickTask4 = button("Preset", 34, false); UI.quickTask4.Parent = UI.ideasCard

UI.exampleText = label("Describe the result in plain English. BloxMind will use the selected object as context when needed.", 28, false, COLORS.dim)
UI.exampleText.TextSize = 11
UI.exampleText.Parent = UI.createPage
UI.buildButton = button("Generate Effect", 46, true)
UI.buildButton.Parent = UI.createPage

UI.progressCard = card(132)
UI.progressCard.Visible = false
UI.progressCard.Parent = UI.createPage
UI.progressTitle = label("Building your effect", 24, true)
UI.progressTitle.Position = UDim2.fromOffset(14, 12)
UI.progressTitle.Size = UDim2.new(1, -28, 0, 24)
UI.progressTitle.Parent = UI.progressCard
UI.progressStage = label("Understanding request…", 22, false, COLORS.dim)
UI.progressStage.Position = UDim2.fromOffset(14, 40)
UI.progressStage.Size = UDim2.new(1, -28, 0, 22)
UI.progressStage.Parent = UI.progressCard
UI.progressTrack = Instance.new("Frame")
UI.progressTrack.Position = UDim2.fromOffset(14, 82)
UI.progressTrack.Size = UDim2.new(1, -28, 0, 8)
UI.progressTrack.BackgroundColor3 = COLORS.input
UI.progressTrack.BorderSizePixel = 0
round(UI.progressTrack, 4)
UI.progressTrack.Parent = UI.progressCard
UI.progressFill = Instance.new("Frame")
UI.progressFill.Size = UDim2.new(0.28, 0, 1, 0)
UI.progressFill.BackgroundColor3 = COLORS.accent
UI.progressFill.BorderSizePixel = 0
round(UI.progressFill, 4)
UI.progressFill.Parent = UI.progressTrack
UI.progressElapsed = label("0.0s · BloxMind Cloud", 18, false, COLORS.accent)
UI.progressElapsed.Position = UDim2.fromOffset(14, 98)
UI.progressElapsed.Size = UDim2.new(1, -28, 0, 18)
UI.progressElapsed.TextSize = 10
UI.progressElapsed.Parent = UI.progressCard
UI.progressFoot = label("Working on a validated Roblox-native result. Failed generations are not charged.", 18, false, COLORS.dim)
UI.progressFoot.Position = UDim2.fromOffset(14, 114)
UI.progressFoot.Size = UDim2.new(1, -28, 0, 18)
UI.progressFoot.TextSize = 9
UI.progressFoot.Parent = UI.progressCard

UI.buildResultCard = card(180)
UI.buildResultCard.Visible = false
UI.buildResultCard.AutomaticSize = Enum.AutomaticSize.Y
UI.buildResultCard.Parent = UI.createPage
pad(UI.buildResultCard, 14, 14, 14, 14)
UI.buildResultLayout = Instance.new("UIListLayout")
UI.buildResultLayout.Padding = UDim.new(0, 8)
UI.buildResultLayout.Parent = UI.buildResultCard
UI.buildResultTitle = label("Build ready", 26, true)
UI.buildResultTitle.TextSize = 18
UI.buildResultTitle.Parent = UI.buildResultCard
UI.buildResultSummary = label("", 40, false, COLORS.dim)
UI.buildResultSummary.Parent = UI.buildResultCard
UI.buildStats = label("", 24, false, COLORS.cyan)
UI.buildStats.TextSize = 12
UI.buildStats.Parent = UI.buildResultCard
UI.buildActions = Instance.new("Frame")
UI.buildActions.Size = UDim2.new(1, 0, 0, 40)
UI.buildActions.BackgroundTransparency = 1
UI.buildActions.Parent = UI.buildResultCard
UI.buildActionsLayout = Instance.new("UIListLayout")
UI.buildActionsLayout.FillDirection = Enum.FillDirection.Horizontal
UI.buildActionsLayout.Padding = UDim.new(0, 8)
UI.buildActionsLayout.Parent = UI.buildActions
UI.previewButton = button("Preview", 38, false)
UI.previewButton.Size = UDim2.new(0.5, -4, 0, 38)
UI.previewButton.Parent = UI.buildActions
UI.insertButton = button("Add to Studio", 38, true)
UI.insertButton.Size = UDim2.new(0.5, -4, 0, 38)
UI.insertButton.Parent = UI.buildActions

UI.buildSecondaryActions = Instance.new("Frame")
UI.buildSecondaryActions.Size = UDim2.new(1, 0, 0, 38)
UI.buildSecondaryActions.BackgroundTransparency = 1
UI.buildSecondaryActions.Parent = UI.buildResultCard
UI.buildSecondaryLayout = Instance.new("UIListLayout")
UI.buildSecondaryLayout.FillDirection = Enum.FillDirection.Horizontal
UI.buildSecondaryLayout.Padding = UDim.new(0, 8)
UI.buildSecondaryLayout.Parent = UI.buildSecondaryActions
UI.regenerateButton = button("Regenerate", 36, false)
UI.regenerateButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.regenerateButton.Parent = UI.buildSecondaryActions
UI.newBuildButton = button("Discard & new", 36, false)
UI.newBuildButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.newBuildButton.Parent = UI.buildSecondaryActions

UI.removePreviewButton = button("Remove preview", 34, false)
UI.removePreviewButton.Size = UDim2.new(1, 0, 0, 34)
UI.removePreviewButton.Visible = false
UI.removePreviewButton.TextColor3 = COLORS.dim
UI.removePreviewButton.Parent = UI.buildResultCard
UI.buildSecondaryActions.Visible = false
UI.buildVerify = label("", 56, false, COLORS.dim)
UI.buildVerify.TextSize = 11
UI.buildVerify.Parent = UI.buildResultCard

-- CODE PAGE
UI.codePage = Instance.new("Frame")
UI.codePage.LayoutOrder = 4
UI.codePage.Size = UDim2.new(1, 0, 0, 520)
UI.codePage.AutomaticSize = Enum.AutomaticSize.Y
UI.codePage.BackgroundColor3 = COLORS.card
UI.codePage.BackgroundTransparency = 0
UI.codePage.BorderSizePixel = 0
round(UI.codePage, 16)
outline(UI.codePage, COLORS.line, 0.35)
pad(UI.codePage, 16, 16, 16, 16)
UI.codePage.Visible = true
UI.codePage.Parent = scroll
UI.codeLayout = Instance.new("UIListLayout")
UI.codeLayout.Padding = UDim.new(0, 10)
UI.codeLayout.Parent = UI.codePage
UI.codeHero = label("Code tools", 30, true)
UI.codeHero.TextSize = 20
UI.codeHero.Parent = UI.codePage
UI.codeHint = label("Debug, explain, secure, optimize, refactor, or improve exactly what you selected. BloxMind never scans unrelated script source.", 36, false, COLORS.dim)
UI.codeHint.TextSize = 12
UI.codeHint.Parent = UI.codePage
UI.codePrompt = textBox("Optional instruction: keep the same behavior but make the server validation safer…", 78)
UI.codePrompt.Parent = UI.codePage
UI.codeButtons = Instance.new("Frame")
UI.codeButtons.Size = UDim2.new(1, 0, 0, 82)
UI.codeButtons.BackgroundTransparency = 1
UI.codeButtons.Parent = UI.codePage
UI.codeButtonsLayout = Instance.new("UIGridLayout")
UI.codeButtonsLayout.CellPadding = UDim2.fromOffset(8, 6)
UI.codeButtonsLayout.CellSize = UDim2.new(1/3, -6, 0, 38)
UI.codeButtonsLayout.FillDirectionMaxCells = 3
UI.codeButtonsLayout.Parent = UI.codeButtons
UI.debugButton = button("Debug", 38, true); UI.debugButton.Parent = UI.codeButtons
UI.explainButton = button("Explain", 38, false); UI.explainButton.Parent = UI.codeButtons
UI.improveButton = button("Improve", 38, false); UI.improveButton.Parent = UI.codeButtons
UI.securityButton = button("Security", 38, false); UI.securityButton.Parent = UI.codeButtons
UI.optimizeButton = button("Optimize", 38, false); UI.optimizeButton.Parent = UI.codeButtons
UI.refactorButton = button("Refactor", 38, false); UI.refactorButton.Parent = UI.codeButtons
UI.errorToggle = button("+ Add Studio error / Output", 30, false)
UI.errorToggle.TextSize = 11
UI.errorToggle.Parent = UI.codePage
UI.errorBox = textBox("Paste an Output error here…", 74)
UI.errorBox.Visible = false
UI.errorBox.Parent = UI.codePage

UI.codeResultCard = card(220)
UI.codeResultCard.Visible = false
UI.codeResultCard.AutomaticSize = Enum.AutomaticSize.Y
UI.codeResultCard.Parent = UI.codePage
pad(UI.codeResultCard, 12, 12, 12, 12)
UI.codeResultLayout = Instance.new("UIListLayout")
UI.codeResultLayout.Padding = UDim.new(0, 8)
UI.codeResultLayout.Parent = UI.codeResultCard
UI.codeResultTitle = label("BloxMind", 24, true)
UI.codeResultTitle.Parent = UI.codeResultCard
UI.codeOutput = Instance.new("TextBox")
UI.codeOutput.Size = UDim2.new(1, 0, 0, 210)
UI.codeOutput.BackgroundColor3 = COLORS.input
UI.codeOutput.TextColor3 = COLORS.text
UI.codeOutput.Text = ""
UI.codeOutput.TextWrapped = true
UI.codeOutput.TextXAlignment = Enum.TextXAlignment.Left
UI.codeOutput.TextYAlignment = Enum.TextYAlignment.Top
UI.codeOutput.Font = Enum.Font.Code
UI.codeOutput.TextSize = 12
UI.codeOutput.ClearTextOnFocus = false
UI.codeOutput.MultiLine = true
UI.codeOutput.TextEditable = false
UI.codeOutput.BorderSizePixel = 0
round(UI.codeOutput, 9)
pad(UI.codeOutput, 10, 10, 10, 10)
UI.codeOutput.Parent = UI.codeResultCard
UI.codeActionRow = Instance.new("Frame")
UI.codeActionRow.Size = UDim2.new(1, 0, 0, 38)
UI.codeActionRow.BackgroundTransparency = 1
UI.codeActionRow.Parent = UI.codeResultCard
UI.codeActionLayout = Instance.new("UIListLayout")
UI.codeActionLayout.FillDirection = Enum.FillDirection.Horizontal
UI.codeActionLayout.Padding = UDim.new(0, 8)
UI.codeActionLayout.Parent = UI.codeActionRow
UI.copyCodeButton = button("Copy code", 36, false)
UI.copyCodeButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.copyCodeButton.Parent = UI.codeActionRow
UI.applyCodeButton = button("Apply to selected", 36, true)
UI.applyCodeButton.Size = UDim2.new(0.5, -4, 0, 36)
UI.applyCodeButton.Parent = UI.codeActionRow

-- REVIEW PAGE
UI.reviewPage = Instance.new("Frame")
UI.reviewPage.LayoutOrder = 5
UI.reviewPage.Size = UDim2.new(1, 0, 0, 520)
UI.reviewPage.AutomaticSize = Enum.AutomaticSize.Y
UI.reviewPage.BackgroundColor3 = COLORS.card
UI.reviewPage.BackgroundTransparency = 0
UI.reviewPage.BorderSizePixel = 0
round(UI.reviewPage, 16)
outline(UI.reviewPage, COLORS.line, 0.35)
pad(UI.reviewPage, 16, 16, 16, 16)
UI.reviewPage.Visible = false
UI.reviewPage.Parent = scroll
UI.reviewLayout = Instance.new("UIListLayout")
UI.reviewLayout.Padding = UDim.new(0, 10)
UI.reviewLayout.Parent = UI.reviewPage
UI.reviewHero = label("Project health", 30, true)
UI.reviewHero.TextSize = 20
UI.reviewHero.Parent = UI.reviewPage
UI.reviewHint = label("BloxMind syncs hierarchy metadata when needed. Source is included only for Script/LocalScript/ModuleScript objects you explicitly select.", 38, false, COLORS.dim)
UI.reviewHint.TextSize = 12
UI.reviewHint.Parent = UI.reviewPage
UI.reviewPrompt = textBox("Optional focus: security, performance, architecture, data saving…", 70)
UI.reviewPrompt.Parent = UI.reviewPage
UI.analyzeButton = button("Run Project Review · 5 credits", 44, true)
UI.analyzeButton.Parent = UI.reviewPage
UI.doctorCard = card(124)
UI.doctorCard.Parent = UI.reviewPage
UI.doctorTitle = label("Game Doctor", 22, true)
UI.doctorTitle.Position = UDim2.fromOffset(12, 10)
UI.doctorTitle.Size = UDim2.new(1, -24, 0, 22)
UI.doctorTitle.Parent = UI.doctorCard
UI.doctorSub = label("Deep Pro audit for exploit risk, architecture, performance, data safety and maintainability.", 38, false, COLORS.dim)
UI.doctorSub.Position = UDim2.fromOffset(12, 35)
UI.doctorSub.Size = UDim2.new(1, -24, 0, 38)
UI.doctorSub.TextSize = 11
UI.doctorSub.Parent = UI.doctorCard
UI.doctorButton = button("Run Game Doctor · 30 credits", 36, false)
UI.doctorButton.Position = UDim2.fromOffset(12, 78)
UI.doctorButton.Size = UDim2.new(0.55, -8, 0, 36)
UI.doctorButton.Parent = UI.doctorCard
UI.fixButton = button("Fix top issue · 15", 36, false)
UI.fixButton.Position = UDim2.new(0.55, 4, 0, 78)
UI.fixButton.Size = UDim2.new(0.45, -16, 0, 36)
UI.fixButton.Parent = UI.doctorCard

UI.reviewResultCard = card(230)
UI.reviewResultCard.Visible = false
UI.reviewResultCard.AutomaticSize = Enum.AutomaticSize.Y
UI.reviewResultCard.Parent = UI.reviewPage
pad(UI.reviewResultCard, 12, 12, 12, 12)
UI.reviewResultLayout = Instance.new("UIListLayout")
UI.reviewResultLayout.Padding = UDim.new(0, 8)
UI.reviewResultLayout.Parent = UI.reviewResultCard
UI.reviewResultTitle = label("Review result", 24, true)
UI.reviewResultTitle.Parent = UI.reviewResultCard
UI.reviewOutput = Instance.new("TextBox")
UI.reviewOutput.Size = UDim2.new(1, 0, 0, 220)
UI.reviewOutput.BackgroundColor3 = COLORS.input
UI.reviewOutput.TextColor3 = COLORS.text
UI.reviewOutput.Text = ""
UI.reviewOutput.TextWrapped = true
UI.reviewOutput.TextXAlignment = Enum.TextXAlignment.Left
UI.reviewOutput.TextYAlignment = Enum.TextYAlignment.Top
UI.reviewOutput.Font = Enum.Font.Code
UI.reviewOutput.TextSize = 12
UI.reviewOutput.ClearTextOnFocus = false
UI.reviewOutput.MultiLine = true
UI.reviewOutput.TextEditable = false
UI.reviewOutput.BorderSizePixel = 0
round(UI.reviewOutput, 9)
pad(UI.reviewOutput, 10, 10, 10, 10)
UI.reviewOutput.Parent = UI.reviewResultCard

UI.footer = label("Smart Build is available on every plan · Sound requires Plus · Animation Beta, Game Doctor and Fix Mode require Pro.", 42, false, COLORS.dim)
UI.footer.LayoutOrder = 100
UI.footer.TextSize = 10
UI.footer.TextXAlignment = Enum.TextXAlignment.Center
UI.footer.Parent = scroll

local function currentProject()
    if #state.projects == 0 then return nil end
    if state.projectIndex < 1 or state.projectIndex > #state.projects then state.projectIndex = 1 end
    return state.projects[state.projectIndex]
end

local function entitlementValue(key, fallback)
    if state.entitlement and state.entitlement[key] ~= nil then return state.entitlement[key] end
    return fallback
end

local function applyBrandAsset()
    local uri = configuredLogoUri()
    brandLogo.Image = uri
    brandLogo.Visible = uri ~= ""
    local left = brandLogo.Visible and 60 or 16
    brand.Position = UDim2.fromOffset(left, 12)
    brand.Size = UDim2.new(0.5, -left, 0, 24)
    projectMini.Position = UDim2.fromOffset(left, 35)
    projectMini.Size = UDim2.new(0.55, -left, 0, 20)
    pcall(function() toolbarButton.Icon = (uri ~= "" and uri or FALLBACK_TOOLBAR_ICON) end)
end

local function updateHeader()
    local project = currentProject()
    local planLabel = tostring(entitlementValue("plan_label", entitlementValue("plan", "Free")) or "Free")
    if project then
        projectMini.Text = tostring(project.project_name or "Creator Project") .. " · " .. planLabel
    else
        projectMini.Text = (state.token and "No Creator Project" or "Not paired") .. " · " .. planLabel
    end
    local balance = entitlementValue("credit_balance", nil)
    creditsPill.Text = balance ~= nil and ("Credits " .. tostring(balance)) or "Credits —"
end

local function updateConnectionVisibility()
    UI.connectionCard.Visible = (not state.token or state.token == "" or state.settingsOpen)
    UI.pairCodeBox.Visible = not state.token or state.token == ""
    UI.pairButton.Visible = UI.pairCodeBox.Visible
    UI.brandAssetBox.Visible = not UI.pairCodeBox.Visible
    UI.saveBrandButton.Visible = not UI.pairCodeBox.Visible
    UI.connectionTitle.Text = UI.pairCodeBox.Visible and "Connect BloxMind" or "Studio settings"
    UI.connectionHint.Text = UI.pairCodeBox.Visible
        and "Generate a one-time pairing code on the BloxMind Studio Bridge page, paste it here, and pair once."
        or "Project, manual sync and connection controls. BloxMind handles normal context automatically."
end

local function setBusy(isBusy)
    state.busy = isBusy
    UI.buildButton.Active = not isBusy
    UI.analyzeButton.Active = not isBusy
    UI.debugButton.Active = not isBusy
    UI.explainButton.Active = not isBusy
    UI.improveButton.Active = not isBusy
    UI.securityButton.Active = not isBusy
    UI.optimizeButton.Active = not isBusy
    UI.refactorButton.Active = not isBusy
    UI.regenerateButton.Active = not isBusy
    UI.newBuildButton.Active = not isBusy
    UI.modePickerButton.Active = not isBusy
    UI.autoButton.Active = not isBusy
    UI.importToggleButton.Active = not isBusy
    UI.toolsToggleButton.Active = not isBusy
    UI.ideasToggleButton.Active = not isBusy
    UI.importRigButton.Active = not isBusy
    UI.importAnimationButton.Active = not isBusy
end

local function request(method, path, body, useAuth)
    local headers = { ["content-type"] = "application/json" }
    if useAuth then
        if not state.token or state.token == "" then error("Studio is not paired with BloxMind yet.") end
        headers["authorization"] = "Bearer " .. state.token
    end
    local options = { Url = API_BASE .. path, Method = method, Headers = headers }
    if body ~= nil then options.Body = HttpService:JSONEncode(body) end
    local ok, response = pcall(function() return HttpService:RequestAsync(options) end)
    if not ok then
        error("BloxMind could not reach the backend. Allow https://bloxmind.onrender.com when Studio asks for HTTP permission. " .. tostring(response))
    end
    local parsed = nil
    if response.Body and response.Body ~= "" then
        local decodeOk, value = pcall(function() return HttpService:JSONDecode(response.Body) end)
        if decodeOk then parsed = value end
    end
    if not response.Success then
        local detail = parsed and (parsed.detail or parsed.message)
        error(detail or ("BloxMind request failed with HTTP " .. tostring(response.StatusCode)))
    end
    return parsed or {}
end

local function updateProjectButton()
    local project = currentProject()
    UI.projectButton.Text = project and ("Project: " .. tostring(project.project_name or "Creator Project")) or "Project: none"
    updateHeader()
end

local function applyEntitlement(entitlement)
    if entitlement then state.entitlement = entitlement end
    local canDoctor = entitlementValue("can_game_doctor", false)
    local canFix = entitlementValue("can_fix_mode", false)
    UI.doctorButton.Text = canDoctor and "Run Game Doctor · 30 credits" or "Game Doctor · Pro"
    UI.doctorButton.Active = canDoctor and not state.busy
    UI.fixButton.Text = canFix and "Fix top issue · 15" or "Fix Mode · Pro"
    UI.fixButton.Active = canFix and not state.busy
    local currentPlan = tostring(entitlementValue("plan", "free") or "free")
    local remaining = entitlementValue("analyses_remaining_this_month", 0)
    if currentPlan == "free" then
        UI.analyzeButton.Text = "Project Review · 5 credits · " .. tostring(remaining or 0) .. "/2 left"
    else
        UI.analyzeButton.Text = "Run Project Review · 5 credits"
    end
    UI.analyzeButton.Active = entitlementValue("can_analyze", true) and not state.busy
    local canSound = entitlementValue("can_sound_lab", false)
    UI.soundButton.Text = canSound and "Sound" or "Sound · Plus"
    UI.soundButton.Active = canSound and not state.busy
    UI.soundButton.AutoButtonColor = canSound
    UI.soundButton.TextTransparency = canSound and 0 or 0.35
    local canAnimation = entitlementValue("can_animation_beta", false)
    UI.animationButton.Text = canAnimation and "Animation · Beta" or "Animation · Pro Beta"
    UI.animationButton.Active = canAnimation and not state.busy
    UI.animationButton.AutoButtonColor = canAnimation
    UI.animationButton.TextTransparency = canAnimation and 0 or 0.35
    updateHeader()
end

local function loadProjects()
    local data = request("GET", "/api/studio-bridge/plugin/projects", nil, true)
    state.projects = data.projects or {}
    if data.entitlement then applyEntitlement(data.entitlement) end
    state.projectIndex = 1
    for index, project in ipairs(state.projects) do
        if project.is_active then state.projectIndex = index; break end
    end
    updateProjectButton()
end

local function instancePath(instance)
    local ok, value = pcall(function() return instance:GetFullName() end)
    return ok and value or instance.Name
end

local function currentSelectionMeta()
    local selected = Selection:Get()
    local first = selected[1]
    if not first then return nil end
    return { name = first.Name, path = instancePath(first), class_name = first.ClassName }
end

local function selectedScriptWithInstance()
    for _, selected in ipairs(Selection:Get()) do
        if selected:IsA("LuaSourceContainer") then
            local ok, source = pcall(function() return selected.Source end)
            if ok then
                return {
                    name = selected.Name,
                    path = instancePath(selected),
                    class_name = selected.ClassName,
                    source = source,
                }, selected
            end
        end
    end
    return nil, nil
end

local function updateSelectionCard()
    local selected = Selection:Get()
    local first = selected[1]
    if not first then
        UI.selectionTitle.Text = "Nothing selected"
        UI.selectionSub.Text = "Select a character/object for builds or a script to reveal contextual Code tools."
        UI.codePage.Visible = false
        return
    end

    if #selected == 1 then
        UI.selectionTitle.Text = "Selected · " .. first.Name
    else
        UI.selectionTitle.Text = "Selected · " .. tostring(#selected) .. " objects"
    end

    local source, _ = selectedScriptWithInstance()
    if source then
        UI.codePage.Visible = true
        UI.selectionSub.Text = source.class_name .. " · Code tools target only the selected script. Other selected objects are not uploaded as source."
    else
        UI.codePage.Visible = false
        local suffix = #selected > 1 and " · Smart Build uses the first selected object as the primary placement target." or ""
        UI.selectionSub.Text = first.ClassName .. suffix .. " · Selection is used as placement/context metadata."
    end
end

local SCAN_ROOTS = {"Workspace", "ReplicatedStorage", "ServerScriptService", "ServerStorage", "StarterGui", "StarterPlayer", "Lighting", "SoundService"}

local function collectStructure()
    local nodes, scriptCount = {}, 0
    local maxNodes = math.min(MAX_NODES, tonumber(entitlementValue("max_nodes", MAX_NODES)) or MAX_NODES)
    local function walk(instance)
        if #nodes >= maxNodes then return false end
        table.insert(nodes, { name = instance.Name, path = instancePath(instance), class_name = instance.ClassName })
        if instance:IsA("LuaSourceContainer") then scriptCount += 1 end
        for _, child in ipairs(instance:GetChildren()) do
            if not walk(child) then return false end
        end
        return #nodes < maxNodes
    end
    for _, serviceName in ipairs(SCAN_ROOTS) do
        if #nodes >= maxNodes then break end
        local ok, service = pcall(function() return game:GetService(serviceName) end)
        if ok and service then walk(service) end
    end
    return nodes, scriptCount
end

local function collectSelectedScripts()
    local scripts, seen = {}, {}
    local maxScripts = math.min(MAX_SELECTED_SCRIPTS, tonumber(entitlementValue("max_selected_scripts", MAX_SELECTED_SCRIPTS)) or MAX_SELECTED_SCRIPTS)
    for _, selected in ipairs(Selection:Get()) do
        if #scripts >= maxScripts then break end
        if selected:IsA("LuaSourceContainer") then
            local path = instancePath(selected)
            if not seen[path] then
                local ok, source = pcall(function() return selected.Source end)
                if ok then
                    seen[path] = true
                    table.insert(scripts, { name = selected.Name, path = path, class_name = selected.ClassName, source = source })
                end
            end
        end
    end
    return scripts
end

local function syncProject(silent)
    local project = currentProject()
    if not project then
        if not silent then UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = "Create a Creator Project in BloxMind first." end
        return false
    end
    local nodes, scriptCount = collectStructure()
    local selectedScripts = collectSelectedScripts()
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/snapshot", {
            creator_project_id = project.id,
            universe_id = tostring(game.GameId or 0),
            place_id = tostring(game.PlaceId or 0),
            place_name = game.Name,
            root_name = game.Name,
            structure = nodes,
            selected_scripts = selectedScripts,
            script_count = scriptCount,
        }, true)
    end)
    if not ok then
        if not silent then UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = tostring(result) end
        return false
    end
    state.latestSnapshotId = result.snapshot and result.snapshot.id or nil
    local synced = result.synced_scripts or {}
    state.latestSourceAttachmentId = (#synced > 0 and synced[1].id) or nil
    state.syncedSourceAttachmentsByPath = {}
    for _, item in ipairs(synced) do
        if item.studio_source_path then state.syncedSourceAttachmentsByPath[item.studio_source_path] = item.id end
    end
    if result.entitlement then applyEntitlement(result.entitlement) end
    if not silent then
        UI.reviewResultCard.Visible = true
        UI.reviewResultTitle.Text = "Synced"
        UI.reviewOutput.Text = "Hierarchy: " .. tostring(result.snapshot and result.snapshot.node_count or #nodes) .. " nodes\nScripts found: " .. tostring(scriptCount) .. "\nSelected script sources: " .. tostring(#synced) .. "\n\nOnly scripts explicitly selected as Script/LocalScript/ModuleScript objects were uploaded. Selecting a Model or Folder does not upload descendant source."
    end
    return true
end

local function pairStudio()
    local code = UI.pairCodeBox.Text:gsub("%s+", "")
    if code == "" then UI.connectionHint.Text = "Enter the one-time pairing code from the BloxMind website first."; return end
    UI.pairButton.Text = "Pairing…"
    showStartupLoading("Pairing this Studio…")
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/pair", {
            code = code,
            device_label = "Roblox Studio",
            plugin_version = PLUGIN_VERSION,
        }, false)
    end)
    UI.pairButton.Text = "Pair this Studio"
    if not ok then
        hideStartupLoading("Pairing failed")
        UI.connectionHint.Text = tostring(result)
        return
    end
    state.token = result.token
    plugin:SetSetting(TOKEN_SETTING, state.token)
    state.projects = result.projects or {}
    state.projectIndex = 1
    if result.entitlement then applyEntitlement(result.entitlement) end
    UI.pairCodeBox.Text = ""
    state.settingsOpen = false
    updateProjectButton()
    updateConnectionVisibility()
    hideStartupLoading("Connected")
end

local function refreshSession()
    showStartupLoading(state.token and state.token ~= "" and "Restoring BloxMind session…" or "Preparing Studio workspace…")
    if not state.token or state.token == "" then
        updateConnectionVisibility()
        updateHeader()
        hideStartupLoading("Ready to pair")
        return
    end
    startupStatus.Text = "Checking account & Creator Project…"
    local ok, result = pcall(function() return request("GET", "/api/studio-bridge/plugin/session", nil, true) end)
    if not ok then
        state.token = nil
        plugin:SetSetting(TOKEN_SETTING, "")
        updateConnectionVisibility()
        projectMini.Text = "Pair Studio again"
        hideStartupLoading("Pair Studio again")
        return
    end
    state.projects = result.projects or {}
    state.projectIndex = 1
    for index, project in ipairs(state.projects) do if project.is_active then state.projectIndex = index; break end end
    if result.entitlement then applyEntitlement(result.entitlement) end
    startupStatus.Text = "Loading Creator workspace…"
    updateProjectButton()
    updateConnectionVisibility()
    hideStartupLoading("Ready")
end

local BUILD_LABELS = { auto = "Smart Build", effect = "Effect", model = "Model", ui = "UI", script = "System", sound = "Sound", animation = "Animation" }
local BUILD_EXAMPLES = {
    auto = "Describe the result. Smart Build uses your prompt and current Studio selection to choose Effect, Model, UI, System, Sound, or Animation.",
    effect = "Try: “Create a silver-white energy aura with electric arcs, rising wisps and a ground shockwave.”",
    model = "Try: “Build a low-poly sci-fi training terminal with neon accents and a screen.”",
    ui = "Try: “Create a polished combat HUD with health, stamina and an ability hotbar.”",
    script = "Try: “Create a secure server-authoritative sprint system with stamina regeneration.”",
    sound = "Try: “Create a deep energy charge with electric crackle and a heavy transformation impact.”",
    animation = "Try: “Create a sharp boxing slip-and-counter animation with anticipation, punch extension and recovery.”",
}
local BUILD_PLACEHOLDERS = {
    auto = "Describe what you want BloxMind to build in Studio…",
    effect = "Give the selected character a violent blue energy aura with lightning, rising particles and a ground shockwave…",
    model = "Build a stylized sci-fi chest with a hinged lid, neon strips and a ProximityPrompt…",
    ui = "Create a clean dark inventory UI with category tabs, item cards and a details panel…",
    script = "Create a secure server-side inventory save system with validation and retries…",
    sound = "Create an original anime-inspired transformation charge with rising energy, electric crackle and a bass impact…",
    animation = "Create a clean combat animation for the selected rig with anticipation, readable action and recovery…",
}
local BUILD_COSTS = { effect = 8, model = 8, ui = 6, script = 5, sound = 6, animation = 10 }
local QUICK_TASKS = {
    auto = {
        {"Aura", "Create a layered energy aura around the selected character with controlled lightning and a ground shockwave."},
        {"HUD", "Create a responsive combat HUD with health, stamina and ability slots."},
        {"Spawner", "Create a secure random spawn system using tagged spawn points, a configurable spawn interval, an alive cap and cleanup."},
        {"Rounds", "Create a secure round system with lobby countdown, spawn handling, cleanup and server-owned win conditions."},
    },
    effect = {
        {"Aura", "Create a layered silver-white energy aura around the selected character with rising wisps, fast sparks, controlled lightning and a ground shockwave."},
        {"Impact", "Create a punch impact effect on the selected part with a fast flash, directional sparks, dust burst and short expanding shockwave."},
        {"Transform", "Create a transformation VFX around the selected character with a restrained charge-up, energy pillar, lightning accents, burst flash and ground ring."},
        {"Trail", "Create a clean speed trail effect for the selected character with restrained streaks, short-lived particles and readable motion accents."},
    },
    model = {
        {"Terminal", "Build a polished interactive sci-fi terminal with a screen, neon accents and a ProximityPrompt."},
        {"Prop", "Build a low-poly arena training dummy with a stable base, readable proportions and editable Roblox-native parts."},
        {"Station", "Build a compact upgrade station with a clear interaction point, lights and a simple status display."},
        {"Arena", "Build a compact modular training arena centerpiece using editable Roblox-native geometry and clear traversal space."},
    },
    ui = {
        {"HUD", "Create a responsive combat HUD with health, stamina, ability slots and clear mobile-friendly spacing."},
        {"Inventory", "Create a polished inventory interface with categories, item grid, details panel and equip button."},
        {"Shop", "Create a clean in-game shop UI with item cards, currency display, details panel and purchase confirmation state."},
        {"Dialogue", "Create a responsive dialogue UI with speaker name, readable message area, choice buttons and mobile-friendly spacing."},
    },
    script = {
        {"Spawner", "Create a secure server-owned random spawn system. Use tagged spawn points or a configured region, keep a maximum alive count, avoid spawning on players, and clean up removed objects."},
        {"Rounds", "Create a complete round system with lobby countdown, match state, spawn handling, cleanup and server-owned win conditions."},
        {"Ability", "Create a secure ability system with client input, server validation, cooldowns, distance/state checks and a reusable module."},
        {"Data", "Create a retry-safe player data system with UpdateAsync-style conflict handling, schema defaults and server-owned state."},
    },
    sound = {
        {"Charge", "Create a deep rising energy charge with electrical texture and a restrained bass swell."},
        {"Impact", "Create a short heavy combat impact with a sharp transient, low-end thump and subtle debris texture."},
        {"Confirm", "Create a clean futuristic UI confirmation sound with a bright attack and short warm tail."},
        {"Whoosh", "Create a short directional combat whoosh with a fast attack, airy movement and a restrained low-end tail."},
    },
    animation = {
        {"Combat", "Create a sharp combat strike animation with anticipation, readable extension, impact timing and controlled recovery."},
        {"Dodge", "Create a quick evasive dodge animation with a clear weight shift, compact motion and balanced recovery."},
        {"Idle", "Create a subtle combat idle animation with breathing, small weight shifts and restrained hand movement."},
        {"Run", "Create a readable athletic run cycle with coordinated arm drive, stable torso motion and clean looping timing."},
    },
}


local function updateQuickTasks(kind)
    local tasks = QUICK_TASKS[kind] or QUICK_TASKS.auto
    local buttons = {UI.quickTask1, UI.quickTask2, UI.quickTask3, UI.quickTask4}
    for index, item in ipairs(buttons) do
        local taskInfo = tasks[index]
        item.Text = taskInfo and taskInfo[1] or "Preset"
        item.Visible = taskInfo ~= nil
    end
end

local function applyQuickTask(index)
    local tasks = QUICK_TASKS[state.buildMode] or QUICK_TASKS.auto
    local taskInfo = tasks[index]
    if taskInfo then
        UI.createPrompt.Text = taskInfo[2]
        state.ideasOpen = false
        UI.ideasCard.Visible = false
        UI.ideasToggleButton.BackgroundColor3 = COLORS.card2
        pcall(function() UI.createPrompt:CaptureFocus() end)
    end
end

local function setBuildMode(kind)
    if kind == "sound" and not entitlementValue("can_sound_lab", false) then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Sound Lab requires Plus"
        UI.buildResultSummary.Text = "Upgrade to BloxMind Plus or Pro to generate original sound effects in Studio and Sound Lab."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.buildSecondaryActions.Visible = false
        return
    end
    if kind == "animation" and not entitlementValue("can_animation_beta", false) then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Animation Beta requires Pro"
        UI.buildResultSummary.Text = "Animation generation is a Pro-only beta. It creates an editable KeyframeSequence for the selected rig."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.buildSecondaryActions.Visible = false
        return
    end
    state.buildMode = kind
    local buttons = { auto = UI.autoButton, effect = UI.effectButton, model = UI.modelButton, ui = UI.uiButton, script = UI.scriptButton, sound = UI.soundButton, animation = UI.animationButton }
    for key, item in pairs(buttons) do
        item.BackgroundColor3 = key == kind and COLORS.accent or COLORS.card2
        item.TextColor3 = COLORS.text
    end
    UI.createPrompt.PlaceholderText = BUILD_PLACEHOLDERS[kind] or BUILD_PLACEHOLDERS.auto
    UI.exampleText.Text = BUILD_EXAMPLES[kind] or BUILD_EXAMPLES.auto
    updateQuickTasks(kind)
    if kind == "auto" then
        UI.modePickerButton.Text = "Smart Build · auto"
        UI.buildButton.Text = "Build with BloxMind"
    else
        local costs = entitlementValue("creator_build_credit_costs", BUILD_COSTS) or BUILD_COSTS
        local cost = tostring(costs[kind] or BUILD_COSTS[kind])
        local suffix = kind == "animation" and " · Pro Beta" or ""
        UI.modePickerButton.Text = BUILD_LABELS[kind] .. " · " .. cost .. " cr" .. suffix
        UI.buildButton.Text = "Generate"
    end
    state.modeMenuOpen = false
    UI.modeMenu.Visible = false
end

local function containsAny(text, terms)
    for _, term in ipairs(terms) do
        if string.find(text, term, 1, true) then return true end
    end
    return false
end

local function inferBuildMode(prompt)
    local text = string.lower(tostring(prompt or ""))
    local selected = Selection:Get()[1]
    if containsAny(text, {"sound", "sfx", "audio", "whoosh", "impact sound", "music sting"}) then return "sound" end
    if containsAny(text, {"animation", "animate", "keyframe", "idle motion", "walk cycle", "run cycle", "punch motion", "dodge motion"}) then return "animation" end
    if containsAny(text, {" ui", "gui", "hud", "menu", "inventory screen", "shop screen", "interface", "button layout"}) then return "ui" end
    if selected and selected:IsA("LuaSourceContainer") then return "script" end
    if containsAny(text, {
        "system", "script", "datastore", "inventory system", "round system", "ability system",
        "remoteevent", "remotefunction", "server-authoritative", "save system", "spawn system",
        "spawner", "randomly spawn", "random spawn", "spawn random", "npc system", "wave system",
        "loot system", "collectible system", "checkpoint system", "leaderboard system", "leaderstats"
    }) then return "script" end
    if containsAny(text, {"model", "prop", "weapon", "sword", "terminal", "chest", "building", "arena", "low-poly", "mesh-like"}) then return "model" end
    return "effect"
end

local function progressStages(kind)
    if kind == "effect" then return {"Understanding the effect…", "Designing energy layers…", "Balancing particles & light…", "Building safe blueprint…", "Final optimization…"} end
    if kind == "model" then return {"Understanding the model…", "Planning proportions…", "Building geometry…", "Checking editability…", "Final polish…"} end
    if kind == "ui" then return {"Understanding the interface…", "Planning hierarchy…", "Designing layout…", "Checking responsiveness…", "Final polish…"} end
    if kind == "sound" then return {"Understanding the sound…", "Designing layers…", "Shaping pitch & texture…", "Rendering WAV…", "Saving to Sound Lab…"} end
    if kind == "animation" then return {"Reading the selected rig…", "Planning key poses…", "Shaping motion arcs…", "Checking joint limits…", "Preparing preview…"} end
    return {"Understanding the system…", "Planning architecture…", "Writing secure Luau…", "Checking placement…", "Final verification…"}
end

local function startProgress(kind)
    state.progressGeneration += 1
    local generation = state.progressGeneration
    local stages = progressStages(kind)
    local startedAt = os.clock()
    local stageIndex = 1
    local nextStageAt = 1.35
    UI.progressCard.Visible = true
    UI.progressTitle.Text = "BloxMind is building " .. string.lower(BUILD_LABELS[kind])
    UI.progressStage.Text = stages[1]
    UI.progressElapsed.Text = "0.0s · BloxMind Cloud"
    UI.progressFill.Size = UDim2.new(0.28, 0, 1, 0)
    UI.progressFill.Position = UDim2.fromScale(0, 0)
    UI.buildButton.Text = "Working…"
    task.spawn(function()
        while state.busy and generation == state.progressGeneration do
            local elapsed = os.clock() - startedAt
            UI.progressElapsed.Text = string.format("%.1fs · BloxMind Cloud", elapsed)
            if elapsed >= nextStageAt and stageIndex < #stages then
                stageIndex += 1
                UI.progressStage.Text = stages[stageIndex]
                nextStageAt += 1.35
            end
            -- Indeterminate movement: unlike the old bar this does not pretend
            -- to know how far an upstream AI request has actually progressed.
            local phase = (elapsed % 1.5) / 1.5
            local x = phase <= 0.5 and (phase * 2 * 0.72) or ((1 - phase) * 2 * 0.72)
            UI.progressFill.Position = UDim2.new(x, 0, 0, 0)
            task.wait(0.08)
        end
    end)
end

local function finishProgress(success)
    state.progressGeneration += 1
    UI.buildButton.Text = state.buildMode == "auto" and "Build with BloxMind" or "Generate"
    if success then
        UI.progressStage.Text = "Ready"
        UI.progressElapsed.Text = "Complete · BloxMind Cloud"
        UI.progressFill.Position = UDim2.fromScale(0, 0)
        UI.progressFill:TweenSize(UDim2.new(1, 0, 1, 0), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.22, true)
        task.delay(0.42, function()
            if UI.progressCard.Parent then
                UI.progressCard.Visible = false
                UI.progressFill.Size = UDim2.new(0.28, 0, 1, 0)
            end
        end)
    else
        UI.progressCard.Visible = false
        UI.progressFill.Size = UDim2.new(0.28, 0, 1, 0)
        UI.progressFill.Position = UDim2.fromScale(0, 0)
    end
end

local CLIENT_BLUEPRINT_MAX_NODES = 140
local CLIENT_SYSTEM_SERVICES = {
    ReplicatedStorage = true,
    ServerScriptService = true,
    ServerStorage = true,
    StarterPlayerScripts = true,
    StarterCharacterScripts = true,
    StarterGui = true,
}

local function blueprintPlanSummary(blueprint)
    local nodes = type(blueprint) == "table" and blueprint.nodes or {}
    local counts = {scripts = 0, modules = 0, remotes = 0, gui = 0, parts = 0, effects = 0}
    for _, node in ipairs(nodes or {}) do
        local className = tostring(node.class or "")
        if className == "Script" or className == "LocalScript" then counts.scripts += 1 end
        if className == "ModuleScript" then counts.modules += 1 end
        if className == "RemoteEvent" or className == "RemoteFunction" then counts.remotes += 1 end
        if className == "ScreenGui" or className == "BillboardGui" or className == "SurfaceGui" then counts.gui += 1 end
        if className == "Part" or className == "WedgePart" or className == "CornerWedgePart" then counts.parts += 1 end
        if className == "ParticleEmitter" or className == "Beam" or className == "Trail" or className == "Highlight" then counts.effects += 1 end
    end
    local details = {}
    if counts.scripts > 0 then table.insert(details, tostring(counts.scripts) .. " script" .. (counts.scripts == 1 and "" or "s")) end
    if counts.modules > 0 then table.insert(details, tostring(counts.modules) .. " module" .. (counts.modules == 1 and "" or "s")) end
    if counts.remotes > 0 then table.insert(details, tostring(counts.remotes) .. " remote" .. (counts.remotes == 1 and "" or "s")) end
    if counts.gui > 0 then table.insert(details, tostring(counts.gui) .. " GUI root" .. (counts.gui == 1 and "" or "s")) end
    if counts.parts > 0 then table.insert(details, tostring(counts.parts) .. " part" .. (counts.parts == 1 and "" or "s")) end
    if counts.effects > 0 then table.insert(details, tostring(counts.effects) .. " VFX node" .. (counts.effects == 1 and "" or "s")) end
    local suffix = #details > 0 and (" · " .. table.concat(details, " · ")) or ""
    return "Studio preflight · " .. tostring(#(nodes or {})) .. " objects" .. suffix
end

local function validateBlueprintForStudio(blueprint, kind)
    if type(blueprint) ~= "table" or type(blueprint.nodes) ~= "table" then
        error("The generated build has no valid Studio node list.")
    end
    if #blueprint.nodes < 1 then error("The generated build is empty.") end
    if #blueprint.nodes > CLIENT_BLUEPRINT_MAX_NODES then
        error("The generated build is larger than BloxMind Studio's local safety limit.")
    end

    local byId = {}
    for index, node in ipairs(blueprint.nodes) do
        if type(node) ~= "table" then error("Build node " .. tostring(index) .. " is invalid.") end
        local id = tostring(node.id or "")
        local className = tostring(node.class or "")
        if id == "" then error("Build node " .. tostring(index) .. " has no ID.") end
        if byId[id] then error("Build contains a duplicate node ID: " .. id) end
        if className == "" then error("Build node " .. id .. " has no Roblox class.") end
        local classOk, temporary = pcall(function() return Instance.new(className) end)
        if not classOk or not temporary then error("Studio cannot create class " .. className .. ".") end
        temporary:Destroy()
        byId[id] = node

        if kind == "script" and not node.parent then
            local service = tostring(node.service or "")
            if service ~= "" and not CLIENT_SYSTEM_SERVICES[service] then
                error("System build requested an unsupported Studio service: " .. service)
            end
        end
    end

    for id, node in pairs(byId) do
        local parentId = node.parent and tostring(node.parent) or ""
        if parentId ~= "" and not byId[parentId] then error("Build node " .. id .. " references a missing parent.") end
        if parentId == id then error("Build node " .. id .. " cannot parent itself.") end

        local seen = {[id] = true}
        local current = node
        local depth = 0
        while current and current.parent do
            depth += 1
            if depth > CLIENT_BLUEPRINT_MAX_NODES then error("Build hierarchy is too deep.") end
            local nextId = tostring(current.parent)
            if seen[nextId] then error("Build hierarchy contains a parent cycle.") end
            seen[nextId] = true
            current = byId[nextId]
        end
    end
    return blueprintPlanSummary(blueprint)
end

local function blueprintPlanDetails(blueprint)
    local lines = {blueprintPlanSummary(blueprint), "Nothing has been inserted yet."}
    local shown = 0
    for _, node in ipairs(blueprint.nodes or {}) do
        if not node.parent and shown < 8 then
            shown += 1
            local service = tostring(node.service or "")
            local prefix = service ~= "" and (service .. " / ") or ""
            table.insert(lines, "• " .. prefix .. tostring(node.name or node.class or "Object") .. " (" .. tostring(node.class or "Instance") .. ")")
        end
    end
    if shown == 0 then table.insert(lines, "• Build contains nested objects only; Add to Studio will use BloxMind's safe default placement.") end
    return table.concat(lines, "\n")
end

local function renderBuildResult(result)
    local blueprint = result.blueprint or {}
    state.lastBlueprint = blueprint
    state.lastBuildKind = result.kind or state.buildMode
    state.lastBuildId = result.build_id
    state.lastPreflightSummary = nil
    if result.entitlement then applyEntitlement(result.entitlement) end

    if state.lastBuildKind ~= "sound" and state.lastBuildKind ~= "animation" then
        local preflightOk, preflightResult = pcall(function()
            return validateBlueprintForStudio(blueprint, state.lastBuildKind)
        end)
        if not preflightOk then
            UI.buildResultCard.Visible = true
            UI.buildResultTitle.Text = "Studio preflight blocked this build"
            UI.buildResultSummary.Text = tostring(preflightResult)
            UI.buildStats.Text = "Nothing was inserted into Studio."
            UI.buildVerify.Text = "Regenerate the build. If this repeats, copy this message into Support."
            UI.previewButton.Visible = false
            UI.insertButton.Visible = false
            UI.buildActions.Visible = false
            UI.buildSecondaryActions.Visible = true
            return
        end
        state.lastPreflightSummary = preflightResult
    end

    UI.buildResultCard.Visible = true
    UI.buildResultTitle.Text = "✓ " .. tostring(blueprint.title or "Build ready")
    UI.buildResultSummary.Text = tostring(blueprint.summary or "Ready to preview or add to Studio.")
    if state.lastBuildKind == "sound" then
        UI.buildStats.Text = tostring(blueprint.duration_seconds or "?") .. "s WAV · " .. tostring(result.credits_used or 0) .. " credits used · " .. tostring(result.credit_balance or "?") .. " credits left"
    elseif state.lastBuildKind == "animation" then
        UI.buildStats.Text = tostring(blueprint.keyframe_count or #(blueprint.keyframes or {})) .. " keyframes · " .. tostring(blueprint.duration or "?") .. "s · " .. tostring(result.credits_used or 0) .. " credits used · " .. tostring(result.credit_balance or "?") .. " credits left"
    else
        UI.buildStats.Text = tostring(blueprint.node_count or #(blueprint.nodes or {})) .. " Roblox objects · " .. tostring(result.credits_used or 0) .. " credits used · " .. tostring(result.credit_balance or "?") .. " credits left"
    end
    local verify = blueprint.verify or {}
    if #verify > 0 then
        local lines = {}
        if state.lastPreflightSummary then table.insert(lines, state.lastPreflightSummary) end
        table.insert(lines, "Verify:")
        for index = 1, math.min(3, #verify) do table.insert(lines, "• " .. tostring(verify[index])) end
        UI.buildVerify.Text = table.concat(lines, "\n")
    else
        UI.buildVerify.Text = (state.lastPreflightSummary and (state.lastPreflightSummary .. "\n") or "") .. "Preview or inspect the plan first, then Add to Studio when you are happy. Ctrl+Z can undo inserted objects."
    end
    UI.buildActions.Visible = true
    local visualBuild = state.lastBuildKind == "effect" or state.lastBuildKind == "model" or state.lastBuildKind == "ui" or state.lastBuildKind == "animation"
    local inspectableBuild = state.lastBuildKind == "script"
    local insertableBuild = state.lastBuildKind ~= "sound"
    UI.previewButton.Visible = visualBuild or inspectableBuild
    UI.previewButton.Text = inspectableBuild and "Inspect Plan" or "Preview"
    UI.previewButton.Size = UDim2.new(0.5, -4, 0, 38)
    UI.insertButton.Visible = insertableBuild
    UI.insertButton.Size = (visualBuild or inspectableBuild) and UDim2.new(0.5, -4, 0, 38) or UDim2.new(1, 0, 0, 38)
    UI.buildActions.Visible = visualBuild or inspectableBuild or insertableBuild
    if state.lastBuildKind == "sound" then
        UI.buildVerify.Text = "Saved to BloxMind Sound Lab. Open /sound-lab on the website to preview/download the WAV, then upload it to Roblox to get an asset ID."
    elseif state.lastBuildKind == "animation" then
        UI.buildVerify.Text = UI.buildVerify.Text .. "\n\nBeta: Preview plays on the selected rig. Add to Studio saves an editable KeyframeSequence to ServerStorage/BloxMindAnimations; publish/refine it with Roblox Animation Editor."
    end
    UI.regenerateButton.Text = "Regenerate"
    UI.buildSecondaryActions.Visible = true
    UI.removePreviewButton.Visible = #state.previewInstances > 0
end

local function selectedRig()
    local selected = Selection:Get()[1]
    if not selected then return nil, nil end
    local model = selected:IsA("Model") and selected or selected:FindFirstAncestorOfClass("Model")
    if not model then return nil, nil end
    local humanoid = model:FindFirstChildOfClass("Humanoid")
    if not humanoid then return nil, nil end
    return model, humanoid
end

local function rigDescriptor()
    local rig, humanoid = selectedRig()
    if not rig or not humanoid then return nil end
    local joints, seen = {}, {}
    for _, item in ipairs(rig:GetDescendants()) do
        if item:IsA("Motor6D") and item.Part1 and item.Part0 then
            local part = item.Part1.Name
            if not seen[part] then
                seen[part] = true
                table.insert(joints, {part = part, parent = item.Part0.Name})
                if #joints >= 32 then break end
            end
        end
    end
    if #joints == 0 then return nil end
    return {
        rig = rig,
        humanoid = humanoid,
        rig_name = rig.Name,
        rig_path = instancePath(rig),
        rig_type = tostring(humanoid.RigType.Name),
        joints = joints,
    }
end

local function runBuild()
    if state.busy then return end
    local project = currentProject()
    if not state.token or state.token == "" then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Pair BloxMind first"
        UI.buildResultSummary.Text = "Open Studio settings, paste the one-time pairing code, and pair this Studio before generating."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.removePreviewButton.Visible = false
        UI.buildSecondaryActions.Visible = false
        state.settingsOpen = true; updateConnectionVisibility()
        return
    end
    if not project then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Choose a Creator Project"
        UI.buildResultSummary.Text = "BloxMind needs an active Creator Project before it can generate Studio builds."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.removePreviewButton.Visible = false
        UI.buildSecondaryActions.Visible = false
        state.settingsOpen = true; updateConnectionVisibility()
        return
    end

    local prompt = UI.createPrompt.Text:gsub("^%s+", ""):gsub("%s+$", "")
    if #prompt < 3 then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Describe what you want"
        UI.buildResultSummary.Text = "Give BloxMind a short prompt first."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.buildSecondaryActions.Visible = false
        return
    end

    local buildKind = state.buildMode == "auto" and inferBuildMode(prompt) or state.buildMode
    if state.buildMode == "auto" then
        UI.exampleText.Text = "Smart Build selected " .. tostring(BUILD_LABELS[buildKind] or buildKind) .. " for this request. You can override it from the build-type menu."
    end

    if buildKind == "sound" and not entitlementValue("can_sound_lab", false) then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Sound Lab requires Plus"
        UI.buildResultSummary.Text = "Smart Build recognized a sound request. Upgrade to BloxMind Plus or Pro, or choose another build type. No credits were used."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.buildSecondaryActions.Visible = false
        return
    end
    if buildKind == "animation" and not entitlementValue("can_animation_beta", false) then
        UI.buildResultCard.Visible = true
        UI.buildResultTitle.Text = "Animation Beta requires Pro"
        UI.buildResultSummary.Text = "Smart Build recognized an animation request. Upgrade to BloxMind Pro, or choose another build type. No credits were used."
        UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.buildSecondaryActions.Visible = false
        return
    end

    state.lastBuildTarget = Selection:Get()[1]
    if buildKind == "effect" then
        local target = state.lastBuildTarget
        local usable = target and (target:IsA("BasePart") or target:IsA("Model") or target:FindFirstAncestorWhichIsA("BasePart") or target:FindFirstAncestorOfClass("Model"))
        if not usable then
            UI.buildResultCard.Visible = true
            UI.buildResultTitle.Text = "Select an effect target"
            UI.buildResultSummary.Text = "Smart Build chose Effect. Select a character Rig/Model or BasePart in Explorer, or manually choose a different build type. No credits were used."
            UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.removePreviewButton.Visible = false
            UI.buildSecondaryActions.Visible = false
            return
        end
    end

    local animationRig = nil
    if buildKind == "animation" then
        animationRig = rigDescriptor()
        if not animationRig then
            UI.buildResultCard.Visible = true
            UI.buildResultTitle.Text = "Select a character rig"
            UI.buildResultSummary.Text = "Animation Beta needs a selected R6/R15-style rig with a Humanoid and Motor6D joints. No credits were used."
            UI.previewButton.Visible = false; UI.insertButton.Visible = false; UI.removePreviewButton.Visible = false
            UI.buildSecondaryActions.Visible = false
            return
        end
        state.lastBuildTarget = animationRig.rig
    end

    setBusy(true)
    startProgress(buildKind)
    UI.buildResultCard.Visible = false
    local ok, result = pcall(function()
        if buildKind == "animation" then
            return request("POST", "/api/studio-bridge/plugin/animation", {
                creator_project_id = project.id,
                prompt = prompt,
                rig_name = animationRig.rig_name,
                rig_path = animationRig.rig_path,
                rig_type = animationRig.rig_type,
                joints = animationRig.joints,
            }, true)
        end
        return request("POST", "/api/studio-bridge/plugin/build", {
            creator_project_id = project.id,
            kind = buildKind,
            prompt = prompt,
            selection = currentSelectionMeta(),
        }, true)
    end)
    setBusy(false)
    finishProgress(ok)
    if not ok then
        UI.buildResultCard.Visible = true
        state.lastBlueprint = nil
        state.lastBuildId = nil
        UI.buildResultTitle.Text = "Build failed"
        UI.buildResultSummary.Text = tostring(result)
        UI.buildStats.Text = "No credits are kept for a failed generation."
        UI.previewButton.Visible = false
        UI.insertButton.Visible = false
        UI.removePreviewButton.Visible = false
        UI.regenerateButton.Text = "Try again"
        UI.buildSecondaryActions.Visible = true
        return
    end
    renderBuildResult(result)
end

local function typedValue(value, objects)
    if type(value) ~= "table" then return value end
    local kind = value.type
    if kind == "Color3" then return Color3.fromRGB(tonumber(value.r) or 255, tonumber(value.g) or 255, tonumber(value.b) or 255) end
    if kind == "Vector2" then return Vector2.new(tonumber(value.x) or 0, tonumber(value.y) or 0) end
    if kind == "Vector3" then return Vector3.new(tonumber(value.x) or 0, tonumber(value.y) or 0, tonumber(value.z) or 0) end
    if kind == "UDim" then return UDim.new(tonumber(value.scale) or 0, tonumber(value.offset) or 0) end
    if kind == "UDim2" then return UDim2.new(tonumber(value.xs) or 0, tonumber(value.xo) or 0, tonumber(value.ys) or 0, tonumber(value.yo) or 0) end
    if kind == "CFrame" then
        local base = CFrame.new(tonumber(value.x) or 0, tonumber(value.y) or 0, tonumber(value.z) or 0)
        return base * CFrame.Angles(math.rad(tonumber(value.rx) or 0), math.rad(tonumber(value.ry) or 0), math.rad(tonumber(value.rz) or 0))
    end
    if kind == "NumberRange" then return NumberRange.new(tonumber(value.min) or 0, tonumber(value.max) or tonumber(value.min) or 0) end
    if kind == "Enum" then
        local enumType = Enum[tostring(value.enum or "")]
        return enumType and enumType[tostring(value.value or "")] or nil
    end
    if kind == "Ref" then return objects[tostring(value.id or "")] end
    if kind == "Rect" then return Rect.new(tonumber(value.minX) or 0, tonumber(value.minY) or 0, tonumber(value.maxX) or 0, tonumber(value.maxY) or 0) end
    if kind == "NumberSequence" then
        local points = {}
        for _, point in ipairs(value.keypoints or {}) do
            table.insert(points, NumberSequenceKeypoint.new(tonumber(point.time) or 0, tonumber(point.value) or 0, tonumber(point.envelope) or 0))
        end
        if #points >= 2 then return NumberSequence.new(points) end
    end
    if kind == "ColorSequence" then
        local points = {}
        for _, point in ipairs(value.keypoints or {}) do
            table.insert(points, ColorSequenceKeypoint.new(tonumber(point.time) or 0, Color3.fromRGB(tonumber(point.r) or 255, tonumber(point.g) or 255, tonumber(point.b) or 255)))
        end
        if #points >= 2 then return ColorSequence.new(points) end
    end
    return nil
end

local function selectedEffectTargets()
    local first = state.lastBuildTarget
    if not first or not first.Parent then first = Selection:Get()[1] end
    if not first then return nil, nil end
    if first:IsA("BasePart") then return first, first:FindFirstAncestorOfClass("Model") end
    if first:IsA("Model") then
        local part = first:FindFirstChild("HumanoidRootPart", true) or first.PrimaryPart or first:FindFirstChildWhichIsA("BasePart", true)
        return part, first
    end
    local part = first:FindFirstAncestorWhichIsA("BasePart")
    local model = first:FindFirstAncestorOfClass("Model")
    return part, model
end

local function requestedServiceParent(serviceName)
    local name = tostring(serviceName or "")
    if name == "StarterPlayerScripts" then return game:GetService("StarterPlayer").StarterPlayerScripts end
    if name == "StarterCharacterScripts" then return game:GetService("StarterPlayer").StarterCharacterScripts end
    if name == "StarterGui" then return game:GetService("StarterGui") end
    if name == "ReplicatedStorage" then return game:GetService("ReplicatedStorage") end
    if name == "ServerScriptService" then return game:GetService("ServerScriptService") end
    if name == "ServerStorage" then return game:GetService("ServerStorage") end
    return nil
end

local function defaultParent(kind, className)
    if kind == "effect" then
        local targetPart, targetModel = selectedEffectTargets()
        if not targetPart then error("Select a character or BasePart before previewing/inserting an Effect.") end
        if className == "Highlight" then return targetModel or targetPart end
        if className == "Part" or className == "WedgePart" or className == "CornerWedgePart" or className == "Model" or className == "Folder" then return workspace end
        return targetPart
    elseif kind == "ui" then
        return game:GetService("StarterGui")
    elseif kind == "script" then
        if className == "LocalScript" then return game:GetService("StarterPlayer").StarterPlayerScripts end
        if className == "ModuleScript" or className == "RemoteEvent" or className == "RemoteFunction" or className == "Folder" then return game:GetService("ReplicatedStorage") end
        return game:GetService("ServerScriptService")
    end
    return workspace
end

local function clearPreview()
    if state.previewAnimationTrack then
        pcall(function() state.previewAnimationTrack:Stop(0.08) end)
        pcall(function() state.previewAnimationTrack:Destroy() end)
        state.previewAnimationTrack = nil
    end
    if state.previewAnimationObject then pcall(function() state.previewAnimationObject:Destroy() end); state.previewAnimationObject = nil end
    if state.previewAnimationSequence then pcall(function() state.previewAnimationSequence:Destroy() end); state.previewAnimationSequence = nil end
    if state.previewAnimatorCreated then pcall(function() state.previewAnimatorCreated:Destroy() end); state.previewAnimatorCreated = nil end
    for _, instance in ipairs(state.previewInstances) do
        pcall(function() if instance and instance.Parent then instance:Destroy() end end)
    end
    state.previewInstances = {}
    UI.removePreviewButton.Visible = false
end

local function resetBuildWorkspace(clearPrompt)
    clearPreview()
    state.lastBlueprint = nil
    state.lastBuildKind = nil
    state.lastBuildId = nil
    state.lastBuildTarget = nil
    state.lastPreflightSummary = nil
    UI.buildResultCard.Visible = false
    UI.progressCard.Visible = false
    UI.buildSecondaryActions.Visible = false
    if clearPrompt then
        UI.createPrompt.Text = ""
        task.defer(function() pcall(function() UI.createPrompt:CaptureFocus() end) end)
    end
end

local function regenerateBuild()
    if state.busy then return end
    clearPreview()
    state.lastBlueprint = nil
    state.lastBuildId = nil
    runBuild()
end

local function buildBlueprint(preview)
    local createdForCleanup = {}
    local ok, rootsOrError, createdResult = pcall(function()
    local blueprint = state.lastBlueprint
    if not blueprint or type(blueprint.nodes) ~= "table" then error("Generate a BloxMind build first.") end
    local kind = state.lastBuildKind or blueprint.kind or "model"
    if preview and kind == "script" then error("Script builds do not use visual Preview. Press Insert to create the proposed script(s).") end
    if preview then clearPreview() end

    local objects, roots, created = {}, {}, createdForCleanup
    local deferredRefs = {}
    for _, node in ipairs(blueprint.nodes) do
        local className = tostring(node.class or "")
        local ok, instance = pcall(function() return Instance.new(className) end)
        if not ok or not instance then error("Studio could not create " .. className .. ".") end
        instance.Name = (preview and "[BloxMind Preview] " or "") .. tostring(node.name or className)
        objects[tostring(node.id)] = instance
        table.insert(created, instance)
    end

    -- Apply primitive properties before parenting.
    for _, node in ipairs(blueprint.nodes) do
        local instance = objects[tostring(node.id)]
        for prop, raw in pairs(node.properties or {}) do
            if type(raw) == "table" and raw.type == "Ref" then
                table.insert(deferredRefs, {instance = instance, prop = prop, raw = raw})
            else
                local value = typedValue(raw, objects)
                if value ~= nil then pcall(function() instance[prop] = value end) end
            end
        end
        if instance:IsA("LuaSourceContainer") and tostring(node.source or "") ~= "" then
            pcall(function() instance.Source = tostring(node.source) end)
            if preview then
                pcall(function() instance.Enabled = false end)
                pcall(function() instance.Disabled = true end)
            end
        end
    end

    -- Parent hierarchy.
    for _, node in ipairs(blueprint.nodes) do
        local instance = objects[tostring(node.id)]
        local parent = node.parent and objects[tostring(node.parent)] or nil
        if parent then
            instance.Parent = parent
        else
            local target = nil
            if kind == "script" then
                target = requestedServiceParent(node.service)
            end
            target = target or defaultParent(kind, instance.ClassName)
            instance.Parent = target
            table.insert(roots, instance)
        end
    end

    for _, ref in ipairs(deferredRefs) do
        local value = typedValue(ref.raw, objects)
        if value then pcall(function() ref.instance[ref.prop] = value end) end
    end

    -- Effects/model CFrames are authored around the origin. Move top-level geometry near selection.
    if kind == "effect" then
        local targetPart = selectedEffectTargets()
        if targetPart then
            for _, rootItem in ipairs(roots) do
                if rootItem:IsA("Model") then
                    pcall(function() rootItem:PivotTo(targetPart.CFrame * rootItem:GetPivot()) end)
                elseif rootItem:IsA("BasePart") then
                    rootItem.CFrame = targetPart.CFrame * rootItem.CFrame
                end
            end
        end
    elseif kind == "model" then
        local selected = state.lastBuildTarget
        if not selected or not selected.Parent then selected = Selection:Get()[1] end
        local targetPart = nil
        if selected then
            if selected:IsA("BasePart") then targetPart = selected
            elseif selected:IsA("Model") then targetPart = selected.PrimaryPart or selected:FindFirstChildWhichIsA("BasePart", true) end
        end
        if targetPart then
            for _, rootItem in ipairs(roots) do
                if rootItem:IsA("Model") then
                    pcall(function() rootItem:PivotTo(targetPart.CFrame * CFrame.new(0, 0, -8)) end)
                elseif rootItem:IsA("BasePart") then
                    rootItem.CFrame = targetPart.CFrame * CFrame.new(0, 0, -8) * rootItem.CFrame
                end
            end
        end
    end

    if preview then
        state.previewInstances = roots
        UI.removePreviewButton.Visible = true
        if #roots > 0 then Selection:Set(roots) end
    else
        if #roots > 0 then Selection:Set(roots) end
    end
    return roots, created
    end)
    if not ok then
        for _, instance in ipairs(createdForCleanup) do
            pcall(function() if instance then instance:Destroy() end end)
        end
        error(rootsOrError)
    end
    return rootsOrError, createdResult
end

local function animationPriority(name)
    local value = Enum.AnimationPriority[tostring(name or "Action")]
    return value or Enum.AnimationPriority.Action
end

local function buildAnimationSequence(blueprint, rig)
    if type(blueprint) ~= "table" or type(blueprint.keyframes) ~= "table" then error("Generate an Animation Beta build first.") end
    local joints = {}
    local childNames = {}
    for _, item in ipairs(rig:GetDescendants()) do
        if item:IsA("Motor6D") and item.Part0 and item.Part1 then
            table.insert(joints, {parent = item.Part0.Name, part = item.Part1.Name})
            childNames[item.Part1.Name] = true
        end
    end
    if #joints == 0 then error("The selected rig has no Motor6D joints to animate.") end

    local sequence = Instance.new("KeyframeSequence")
    sequence.Name = tostring(blueprint.title or "BloxMind Animation")
    sequence.Loop = blueprint.loop == true
    sequence.Priority = animationPriority(blueprint.priority)

    for _, frame in ipairs(blueprint.keyframes) do
        local keyframe = Instance.new("Keyframe")
        keyframe.Time = tonumber(frame.time) or 0
        keyframe.Name = "Keyframe_" .. tostring(math.floor(keyframe.Time * 1000 + 0.5))
        keyframe.Parent = sequence

        local values = {}
        for _, item in ipairs(frame.poses or {}) do values[tostring(item.part or "")] = item end
        local poseByName = {}
        local allNames = {}
        for _, joint in ipairs(joints) do
            allNames[joint.parent] = true
            allNames[joint.part] = true
        end
        for name in pairs(allNames) do
            local pose = Instance.new("Pose")
            pose.Name = name
            poseByName[name] = pose
            local info = values[name]
            if info then
                pose.CFrame = CFrame.new(tonumber(info.x) or 0, tonumber(info.y) or 0, tonumber(info.z) or 0)
                    * CFrame.Angles(math.rad(tonumber(info.rx) or 0), math.rad(tonumber(info.ry) or 0), math.rad(tonumber(info.rz) or 0))
                local style = Enum.PoseEasingStyle[tostring(info.easing_style or "Cubic")]
                local direction = Enum.PoseEasingDirection[tostring(info.easing_direction or "InOut")]
                if style then pose.EasingStyle = style end
                if direction then pose.EasingDirection = direction end
                pcall(function() pose.Weight = tonumber(info.weight) or 1 end)
            end
        end
        for _, joint in ipairs(joints) do
            local childPose = poseByName[joint.part]
            local parentPose = poseByName[joint.parent]
            if childPose and not childPose.Parent then
                childPose.Parent = parentPose or keyframe
            end
        end
        for name, pose in pairs(poseByName) do
            if not pose.Parent then pose.Parent = keyframe end
        end
    end
    return sequence
end

local function previewAnimationBuild()
    clearPreview()
    local rig, humanoid = selectedRig()
    if not rig or not humanoid or (state.lastBuildTarget and rig ~= state.lastBuildTarget) then
        error("Select the same character rig used for this Animation Beta generation before previewing it.")
    end
    local sequence = buildAnimationSequence(state.lastBlueprint, rig)
    local contentId = KeyframeSequenceProvider:RegisterKeyframeSequence(sequence)
    local animation = Instance.new("Animation")
    animation.Name = "BloxMindAnimationPreview"
    animation.AnimationId = contentId
    local animator = humanoid:FindFirstChildOfClass("Animator")
    if not animator then
        animator = Instance.new("Animator")
        animator.Parent = humanoid
        state.previewAnimatorCreated = animator
    end
    local track = animator:LoadAnimation(animation)
    track.Looped = state.lastBlueprint.loop == true
    track.Priority = animationPriority(state.lastBlueprint.priority)
    track:Play(0.08, 1, 1)
    state.previewAnimationTrack = track
    state.previewAnimationObject = animation
    state.previewAnimationSequence = sequence
    UI.removePreviewButton.Visible = true
    return track
end

local function insertAnimationBuild()
    clearPreview()
    local rig, _ = selectedRig()
    if not rig or (state.lastBuildTarget and rig ~= state.lastBuildTarget) then
        error("Select the same character rig used for this Animation Beta generation before inserting it.")
    end
    local sequence = buildAnimationSequence(state.lastBlueprint, rig)
    local folder = ServerStorage:FindFirstChild("BloxMindAnimations")
    if not folder then
        folder = Instance.new("Folder")
        folder.Name = "BloxMindAnimations"
        folder.Parent = ServerStorage
    end
    sequence.Name = tostring(state.lastBlueprint.title or "BloxMind Animation"):gsub("[^%w%s_%-]", ""):sub(1, 80)
    sequence:SetAttribute("BloxMindBeta", true)
    sequence:SetAttribute("BloxMindPrompt", tostring(UI.createPrompt.Text or ""):sub(1, 500))
    sequence.Parent = folder
    Selection:Set({sequence})
    return sequence
end

local function previewBuild()
    if state.lastBuildKind == "script" then
        local ok, details = pcall(function()
            validateBlueprintForStudio(state.lastBlueprint, "script")
            return blueprintPlanDetails(state.lastBlueprint)
        end)
        if not ok then UI.buildResultSummary.Text = tostring(details); return end
        UI.buildResultSummary.Text = "System plan inspected. Nothing changed in Studio."
        UI.buildVerify.Text = details .. "\n\nPress Add to Studio only when the structure looks right."
        return
    end
    if state.lastBuildKind == "animation" then
        local ok, result = pcall(previewAnimationBuild)
        if not ok then UI.buildResultSummary.Text = tostring(result); return end
        UI.buildResultSummary.Text = "Animation preview is playing on the selected rig. Use Remove preview to stop it, or Add to Studio to save the KeyframeSequence."
        return
    end
    local ok, result = pcall(function() return buildBlueprint(true) end)
    if not ok then UI.buildResultSummary.Text = tostring(result); return end
    UI.buildResultSummary.Text = "Preview inserted temporarily. Inspect it, then Add to Studio to keep a clean final copy or Remove Preview."
end

local function beginStudioRecording(name, displayName)
    local ok, recording = pcall(function()
        return ChangeHistoryService:TryBeginRecording(name, displayName)
    end)
    if ok and recording then return recording end
    return nil
end

local function finishStudioRecording(recording, commit, fallbackWaypoint)
    if recording then
        pcall(function()
            ChangeHistoryService:FinishRecording(
                recording,
                commit and Enum.FinishRecordingOperation.Commit or Enum.FinishRecordingOperation.Cancel
            )
        end)
    elseif commit and fallbackWaypoint then
        -- Compatibility fallback for older Studio builds. Roblox is moving plugins
        -- toward TryBeginRecording/FinishRecording, so this should rarely be used.
        pcall(function() ChangeHistoryService:SetWaypoint(fallbackWaypoint) end)
    end
end

local function insertBuild()
    clearPreview()
    local recording = beginStudioRecording("BloxMindInsert", "BloxMind: Add to Studio")
    local ok, result
    if state.lastBuildKind == "animation" then
        ok, result = pcall(insertAnimationBuild)
    else
        ok, result = pcall(function() return buildBlueprint(false) end)
    end
    if not ok then
        finishStudioRecording(recording, false)
        UI.buildResultSummary.Text = tostring(result)
        return
    end
    finishStudioRecording(recording, true, "BloxMind Insert")
    print("[BloxMind Studio] Inserted " .. tostring(state.lastBuildKind or "build") .. ".")
    UI.buildResultSummary.Text = state.lastBuildKind == "animation"
        and "Saved as an editable KeyframeSequence in ServerStorage/BloxMindAnimations. Refine/publish it with Roblox Animation Editor."
        or "Inserted into Studio as one undoable BloxMind action. Use Ctrl+Z if you change your mind."
end

local function runCodeAction(action)
    if state.busy then return end
    local project = currentProject()
    local source, _ = selectedScriptWithInstance()
    if not project then state.settingsOpen = true; updateConnectionVisibility(); return end
    if not source then UI.codeResultCard.Visible = true; UI.codeOutput.Text = "Select one Script, LocalScript or ModuleScript in Explorer first."; return end
    setBusy(true)
    UI.codeResultCard.Visible = true
    local runningLabels = {debug="Debugging…", explain="Explaining…", improve="Improving…", security="Checking security…", optimize="Optimizing…", refactor="Refactoring…"}
    UI.codeResultTitle.Text = runningLabels[action] or "Working…"
    UI.codeOutput.Text = "BloxMind is reading only the selected script…"
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/assistant", {
            creator_project_id = project.id,
            action = action,
            prompt = UI.codePrompt.Text,
            source = source,
            error_message = UI.errorBox.Text,
        }, true)
    end)
    setBusy(false)
    if not ok then UI.codeResultTitle.Text = "Request failed"; UI.codeOutput.Text = tostring(result); return end
    if result.entitlement then applyEntitlement(result.entitlement) end
    state.lastCode = tostring(result.code or "")
    state.lastCodeSourcePath = source.path
    state.lastCodeOriginalSource = source.source
    state.applyConfirm = false
    UI.codeResultTitle.Text = "BloxMind · " .. string.upper(string.sub(action, 1, 1)) .. string.sub(action, 2)
    UI.codeOutput.Text = tostring(result.answer or "No response returned.") .. "\n\nCredits used: " .. tostring(result.credits_used or 0) .. " · Balance: " .. tostring(result.credit_balance or "?") .. " credits"
    UI.applyCodeButton.Visible = state.lastCode ~= "" and action ~= "explain"
    UI.copyCodeButton.Visible = state.lastCode ~= ""
end

local function copyLastCode()
    if state.lastCode == "" then return end
    local ok = pcall(function() StudioService:CopyToClipboard(state.lastCode) end)
    UI.copyCodeButton.Text = ok and "Copied ✓" or "Select code + Ctrl+C"
    task.delay(1.2, function() if UI.copyCodeButton.Parent then UI.copyCodeButton.Text = "Copy code" end end)
end

local function applyCodeToSelected()
    if state.lastCode == "" then return end
    local source, instance = selectedScriptWithInstance()
    if not source or not instance then UI.codeOutput.Text ..= "\n\nSelect the script you want to apply this proposal to."; return end
    if state.lastCodeSourcePath and source.path ~= state.lastCodeSourcePath then
        UI.codeOutput.Text ..= "\n\nSelection changed. Re-run the Code action on this script before applying a replacement."
        return
    end
    if state.lastCodeOriginalSource ~= nil and source.source ~= state.lastCodeOriginalSource then
        UI.codeOutput.Text ..= "\n\nThis script changed after BloxMind generated the proposal. Re-run the Code action so newer edits are not overwritten."
        state.applyConfirm = false
        UI.applyCodeButton.Text = "Apply to selected"
        UI.applyCodeButton.BackgroundColor3 = COLORS.accent
        return
    end
    if not state.applyConfirm then
        state.applyConfirm = true
        UI.applyCodeButton.Text = "Click again to replace"
        UI.applyCodeButton.BackgroundColor3 = COLORS.warning
        task.delay(4, function()
            if state.applyConfirm then state.applyConfirm = false; UI.applyCodeButton.Text = "Apply to selected"; UI.applyCodeButton.BackgroundColor3 = COLORS.accent end
        end)
        return
    end
    state.applyConfirm = false
    local recording = beginStudioRecording("BloxMindCodeApply", "BloxMind: Apply code proposal")
    local ok, err = pcall(function() instance.Source = state.lastCode end)
    finishStudioRecording(recording, ok, "BloxMind Code Apply")
    UI.applyCodeButton.Text = ok and "Applied ✓" or "Apply failed"
    UI.applyCodeButton.BackgroundColor3 = ok and COLORS.success or COLORS.danger
    if not ok then UI.codeOutput.Text ..= "\n\nStudio refused the source update: " .. tostring(err) end
    task.delay(1.6, function() if UI.applyCodeButton.Parent then UI.applyCodeButton.Text = "Apply to selected"; UI.applyCodeButton.BackgroundColor3 = COLORS.accent end end)
end

local function analyzeProject()
    if state.busy then return end
    local project = currentProject()
    if not project then state.settingsOpen = true; updateConnectionVisibility(); return end
    if not entitlementValue("can_analyze", true) then UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = "Free Studio Bridge includes 2 project analyses per month. Upgrade to Plus for ongoing analysis."; return end
    setBusy(true)
    UI.reviewResultCard.Visible = true
    UI.reviewResultTitle.Text = "Syncing & reviewing…"
    UI.reviewOutput.Text = "BloxMind is refreshing project context before the review."
    local syncedOk = syncProject(true)
    if not syncedOk then
        setBusy(false)
        UI.reviewResultTitle.Text = "Review stopped"
        UI.reviewOutput.Text = "Studio sync failed, so BloxMind did not start a paid review. Fix the connection and try again."
        return
    end
    local prompt = UI.reviewPrompt.Text:gsub("^%s+", ""):gsub("%s+$", "")
    if prompt == "" then prompt = "Review the synced Studio structure and selected scripts. Identify the highest-impact issues first and give safe, concrete improvements." end
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/analyze", {
            creator_project_id = project.id,
            snapshot_id = state.latestSnapshotId,
            prompt = prompt,
        }, true)
    end)
    setBusy(false)
    if not ok then UI.reviewResultTitle.Text = "Review failed"; UI.reviewOutput.Text = tostring(result); return end
    if result.entitlement then applyEntitlement(result.entitlement) end
    UI.reviewResultTitle.Text = "Project Review"
    UI.reviewOutput.Text = tostring(result.answer or "No analysis returned.") .. "\n\nCredits used: " .. tostring(result.credits_used or 0) .. " · Balance: " .. tostring(result.credit_balance or "?") .. " credits"
end

local function formatAudit(audit)
    if not audit then return "No Game Doctor audit exists yet." end
    local lines = {"Project Health · " .. tostring(audit.overall_score or "?") .. "/100", tostring(audit.summary or ""), ""}
    local issues = audit.critical_issues or {}
    for index = 1, math.min(5, #issues) do
        local issue = issues[index]
        table.insert(lines, tostring(index) .. ". [" .. tostring(issue.severity or "Issue") .. "] " .. tostring(issue.title or "Finding"))
        if issue.reason then table.insert(lines, "   " .. tostring(issue.reason)) end
    end
    return table.concat(lines, "\n")
end

local function runGameDoctor()
    if state.busy then return end
    if not entitlementValue("can_game_doctor", false) then UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = "Game Doctor inside Studio is available on Pro."; return end
    local project = currentProject()
    if not project then return end
    if not state.doctorConfirm then
        state.doctorConfirm = true
        UI.doctorButton.Text = "Confirm 30-credit audit"
        UI.doctorButton.BackgroundColor3 = COLORS.warning
        task.delay(4, function() if state.doctorConfirm then state.doctorConfirm = false; applyEntitlement(state.entitlement); UI.doctorButton.BackgroundColor3 = COLORS.card2 end end)
        return
    end
    state.doctorConfirm = false
    setBusy(true)
    UI.reviewResultCard.Visible = true
    UI.reviewResultTitle.Text = "Game Doctor is examining the project…"
    UI.reviewOutput.Text = "Syncing current project context…"
    local syncedOk = syncProject(true)
    if not syncedOk then
        setBusy(false)
        UI.doctorButton.BackgroundColor3 = COLORS.card2
        applyEntitlement(state.entitlement)
        UI.reviewResultTitle.Text = "Game Doctor stopped"
        UI.reviewOutput.Text = "Studio sync failed, so no 30-credit audit was started."
        return
    end
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/game-doctor", {
            creator_project_id = project.id,
            snapshot_id = state.latestSnapshotId,
            confirm_credits = true,
        }, true)
    end)
    setBusy(false)
    UI.doctorButton.BackgroundColor3 = COLORS.card2
    if not ok then UI.reviewResultTitle.Text = "Game Doctor failed"; UI.reviewOutput.Text = tostring(result); applyEntitlement(state.entitlement); return end
    state.latestAuditId = result.audit_id
    if result.entitlement then applyEntitlement(result.entitlement) end
    UI.reviewResultTitle.Text = "Game Doctor"
    UI.reviewOutput.Text = formatAudit(result.audit) .. "\n\nCredits used: " .. tostring(result.credits_used or 0) .. " · Balance: " .. tostring(result.credit_balance or "?") .. " credits"
end

local function formatStringList(items)
    if type(items) ~= "table" then return tostring(items or "") end
    local lines = {}
    for _, item in ipairs(items) do table.insert(lines, "• " .. tostring(item)) end
    return table.concat(lines, "\n")
end

local function generateTopFix()
    if state.busy then return end
    if not entitlementValue("can_fix_mode", false) then UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = "Fix Mode inside Studio is available on Pro."; return end
    local project = currentProject()
    if not project then return end
    if not state.latestAuditId then
        local ok, latest = pcall(function() return request("GET", "/api/studio-bridge/plugin/audits/latest/" .. HttpService:UrlEncode(project.id), nil, true) end)
        if ok and latest.audit then state.latestAuditId = latest.audit.id else UI.reviewResultCard.Visible = true; UI.reviewOutput.Text = "Run Game Doctor first so BloxMind has a finding to fix."; return end
    end
    if not state.fixConfirm then
        state.fixConfirm = true
        UI.fixButton.Text = "Confirm 15 credits"
        UI.fixButton.BackgroundColor3 = COLORS.warning
        task.delay(4, function() if state.fixConfirm then state.fixConfirm = false; applyEntitlement(state.entitlement); UI.fixButton.BackgroundColor3 = COLORS.card2 end end)
        return
    end
    state.fixConfirm = false

    -- Bind a code fix only to the exact script that is selected and synced for this request.
    local fixSource = selectedScriptWithInstance()
    if fixSource then
        UI.reviewResultCard.Visible = true
        UI.reviewResultTitle.Text = "Syncing selected script…"
        UI.reviewOutput.Text = "BloxMind is syncing the current version of only the selected script before generating the fix."
        local syncedOk = syncProject(true)
        if not syncedOk then
            UI.reviewResultTitle.Text = "Fix stopped"
            UI.reviewOutput.Text = "The selected script could not be synced, so no 15-credit fix request was started."
            UI.fixButton.BackgroundColor3 = COLORS.card2
            applyEntitlement(state.entitlement)
            return
        end
    end
    local sourceAttachmentId = fixSource and state.syncedSourceAttachmentsByPath[fixSource.path] or nil
    local boundPath = fixSource and fixSource.path or nil
    local boundOriginalSource = fixSource and fixSource.source or nil

    setBusy(true)
    UI.reviewResultCard.Visible = true
    UI.reviewResultTitle.Text = "Generating fix proposal…"
    UI.reviewOutput.Text = "BloxMind is preparing a reviewable fix. Nothing will be applied automatically."
    local ok, result = pcall(function()
        return request("POST", "/api/studio-bridge/plugin/fix", {
            creator_project_id = project.id,
            audit_id = state.latestAuditId,
            issue_index = 0,
            source_attachment_id = sourceAttachmentId,
            confirm_credits = true,
        }, true)
    end)
    setBusy(false)
    UI.fixButton.BackgroundColor3 = COLORS.card2
    if not ok then UI.reviewResultTitle.Text = "Fix generation failed"; UI.reviewOutput.Text = tostring(result); applyEntitlement(state.entitlement); return end
    if result.entitlement then applyEntitlement(result.entitlement) end
    local fix = result.fix or {}
    state.lastCode = tostring(fix.proposed_code or "")
    state.lastCodeSourcePath = fix.source_path
    if state.lastCodeSourcePath and boundPath == state.lastCodeSourcePath then
        state.lastCodeOriginalSource = boundOriginalSource
    else
        state.lastCodeOriginalSource = nil
    end
    state.applyConfirm = false

    local verifyText = formatStringList(fix.verification_steps)
    local issueTitle = tostring(fix.issue_title or "Top issue")
    UI.reviewResultTitle.Text = "Fix proposal · " .. issueTitle
    UI.reviewOutput.Text = tostring(fix.explanation or "") .. "\n\nPlacement: " .. tostring(fix.placement or "") .. "\n\nVerification:\n" .. verifyText .. "\n\nCredits used: " .. tostring(result.credits_used or 0) .. " · Balance: " .. tostring(result.credit_balance or "?") .. " credits\n\nThe proposal is also ready in Code."

    UI.codeResultCard.Visible = true
    UI.codeResultTitle.Text = "Fix proposal · " .. issueTitle
    UI.codeOutput.Text = tostring(fix.explanation or "") .. "\n\nPlacement: " .. tostring(fix.placement or "") .. "\n\nVerification:\n" .. verifyText
    UI.copyCodeButton.Visible = state.lastCode ~= ""
    UI.applyCodeButton.Visible = state.lastCode ~= "" and state.lastCodeSourcePath ~= nil and state.lastCodeOriginalSource ~= nil
end

local function selectPage(name)
    -- v4 uses one continuous central workspace. Keep this helper for old event
    -- wiring without hiding any section.
    state.activePage = "workspace"
    UI.createPage.Visible = true
    UI.codePage.Visible = selectedScriptWithInstance() ~= nil
    UI.reviewPage.Visible = state.toolsOpen
end

local function importFbxRig()
    if state.busy then return end
    setBusy(true)
    UI.importHint.Text = "Choose an FBX rig file…"
    local ok, result = pcall(function() return plugin:ImportFbxRigAsync(true) end)
    setBusy(false)
    if not ok or not result then
        UI.importHint.Text = ok and "Import cancelled." or ("Import failed: " .. tostring(result))
        return
    end
    UI.importHint.Text = "Imported R15 rig into Workspace. Select it to build effects or use Animation Beta."
    pcall(function() Selection:Set({result}) end)
end

local function importFbxAnimation()
    if state.busy then return end
    local rig, humanoid = selectedRig()
    if not rig or not humanoid then
        UI.importHint.Text = "Select the target character rig first, then import the FBX animation."
        return
    end
    setBusy(true)
    UI.importHint.Text = "Choose an FBX animation for " .. rig.Name .. "…"
    local isR15 = humanoid.RigType == Enum.HumanoidRigType.R15
    local ok, result = pcall(function() return plugin:ImportFbxAnimationAsync(rig, isR15) end)
    setBusy(false)
    if not ok or not result then
        UI.importHint.Text = ok and "Import cancelled." or ("Animation import failed: " .. tostring(result))
        return
    end
    UI.importHint.Text = "FBX animation imported as a KeyframeSequence. You can refine/publish it with Roblox Animation Editor."
    pcall(function() Selection:Set({result}) end)
end

-- UI events
settingsButton.MouseButton1Click:Connect(function() state.settingsOpen = not state.settingsOpen; updateConnectionVisibility() end)
UI.saveBrandButton.MouseButton1Click:Connect(function()
    local value = tostring(UI.brandAssetBox.Text or ""):gsub("%s+", "")
    local id = value:match("(%d+)")
    if not id then
        UI.saveBrandButton.Text = "Paste image ID"
        task.delay(1.4, function() if UI.saveBrandButton.Parent then UI.saveBrandButton.Text = "Use logo" end end)
        return
    end
    plugin:SetSetting(LOGO_SETTING, id)
    UI.brandAssetBox.Text = id
    applyBrandAsset()
    UI.saveBrandButton.Text = "Logo set"
    task.delay(1.4, function() if UI.saveBrandButton.Parent then UI.saveBrandButton.Text = "Use logo" end end)
end)
UI.pairButton.MouseButton1Click:Connect(pairStudio)
UI.projectButton.MouseButton1Click:Connect(function()
    if #state.projects == 0 then return end
    state.projectIndex += 1
    if state.projectIndex > #state.projects then state.projectIndex = 1 end
    updateProjectButton()
end)
UI.syncButton.MouseButton1Click:Connect(function() syncProject(false) end)
UI.disconnectButton.MouseButton1Click:Connect(function()
    if state.token and state.token ~= "" then pcall(function() request("DELETE", "/api/studio-bridge/plugin/session", nil, true) end) end
    state.token = nil
    plugin:SetSetting(TOKEN_SETTING, "")
    state.projects = {}
    state.settingsOpen = false
    updateConnectionVisibility()
    updateHeader()
end)

UI.createTab.MouseButton1Click:Connect(function() selectPage("workspace") end)
UI.codeTab.MouseButton1Click:Connect(function() selectPage("code") end)
UI.reviewTab.MouseButton1Click:Connect(function() selectPage("review") end)
UI.modePickerButton.MouseButton1Click:Connect(function()
    state.modeMenuOpen = not state.modeMenuOpen
    UI.modeMenu.Visible = state.modeMenuOpen
end)
UI.importToggleButton.MouseButton1Click:Connect(function()
    state.importOpen = not state.importOpen
    UI.importCard.Visible = state.importOpen
    UI.importToggleButton.BackgroundColor3 = state.importOpen and COLORS.accentSoft or COLORS.card2
end)
UI.toolsToggleButton.MouseButton1Click:Connect(function()
    state.toolsOpen = not state.toolsOpen
    UI.reviewPage.Visible = state.toolsOpen
    UI.toolsToggleButton.BackgroundColor3 = state.toolsOpen and COLORS.accentSoft or COLORS.card2
end)
UI.ideasToggleButton.MouseButton1Click:Connect(function()
    state.ideasOpen = not state.ideasOpen
    UI.ideasCard.Visible = state.ideasOpen
    UI.ideasToggleButton.BackgroundColor3 = state.ideasOpen and COLORS.accentSoft or COLORS.card2
end)
UI.importRigButton.MouseButton1Click:Connect(importFbxRig)
UI.importAnimationButton.MouseButton1Click:Connect(importFbxAnimation)
UI.autoButton.MouseButton1Click:Connect(function() setBuildMode("auto") end)
UI.effectButton.MouseButton1Click:Connect(function() setBuildMode("effect") end)
UI.modelButton.MouseButton1Click:Connect(function() setBuildMode("model") end)
UI.uiButton.MouseButton1Click:Connect(function() setBuildMode("ui") end)
UI.scriptButton.MouseButton1Click:Connect(function() setBuildMode("script") end)
UI.soundButton.MouseButton1Click:Connect(function() setBuildMode("sound") end)
UI.animationButton.MouseButton1Click:Connect(function() setBuildMode("animation") end)
UI.quickTask1.MouseButton1Click:Connect(function() applyQuickTask(1) end)
UI.quickTask2.MouseButton1Click:Connect(function() applyQuickTask(2) end)
UI.quickTask3.MouseButton1Click:Connect(function() applyQuickTask(3) end)
UI.quickTask4.MouseButton1Click:Connect(function() applyQuickTask(4) end)
UI.buildButton.MouseButton1Click:Connect(runBuild)
UI.previewButton.MouseButton1Click:Connect(previewBuild)
UI.insertButton.MouseButton1Click:Connect(insertBuild)
UI.removePreviewButton.MouseButton1Click:Connect(clearPreview)
UI.regenerateButton.MouseButton1Click:Connect(regenerateBuild)
UI.newBuildButton.MouseButton1Click:Connect(function() resetBuildWorkspace(true) end)

UI.debugButton.MouseButton1Click:Connect(function() runCodeAction("debug") end)
UI.explainButton.MouseButton1Click:Connect(function() runCodeAction("explain") end)
UI.improveButton.MouseButton1Click:Connect(function() runCodeAction("improve") end)
UI.securityButton.MouseButton1Click:Connect(function() runCodeAction("security") end)
UI.optimizeButton.MouseButton1Click:Connect(function() runCodeAction("optimize") end)
UI.refactorButton.MouseButton1Click:Connect(function() runCodeAction("refactor") end)
UI.errorToggle.MouseButton1Click:Connect(function()
    state.errorExpanded = not state.errorExpanded
    UI.errorBox.Visible = state.errorExpanded
    UI.errorToggle.Text = state.errorExpanded and "− Hide Studio error / Output" or "+ Add Studio error / Output"
end)
UI.copyCodeButton.MouseButton1Click:Connect(copyLastCode)
UI.applyCodeButton.MouseButton1Click:Connect(applyCodeToSelected)
UI.analyzeButton.MouseButton1Click:Connect(analyzeProject)
UI.doctorButton.MouseButton1Click:Connect(runGameDoctor)
UI.fixButton.MouseButton1Click:Connect(generateTopFix)

Selection.SelectionChanged:Connect(updateSelectionCard)
toolbarButton.Click:Connect(function()
    widget.Enabled = not widget.Enabled
    toolbarButton:SetActive(widget.Enabled)
end)
openBloxMindAction.Triggered:Connect(function()
    widget.Enabled = true
    toolbarButton:SetActive(true)
    task.defer(function() pcall(function() UI.createPrompt:CaptureFocus() end) end)
end)
widget:GetPropertyChangedSignal("Enabled"):Connect(function()
    toolbarButton:SetActive(widget.Enabled)
    if widget.Enabled then
        updateSelectionCard()
        refreshSession()
    else
        clearPreview()
    end
end)

plugin.Unloading:Connect(function()
    state.progressGeneration += 1
    pcall(clearPreview)
    print("[BloxMind Studio] Plugin unloaded cleanly.")
end)

-- Initial state
applyBrandAsset()
updateQuickTasks(state.buildMode)
setBuildMode("auto")
selectPage("workspace")
updateSelectionCard()
updateConnectionVisibility()
applyEntitlement(state.entitlement)
refreshSession()
