--!strict
-- RoScripter Studio bridge. No account password, model key or Open Cloud key.
-- Transfer tokens remain in memory and expire after ten minutes.
local HttpService = game:GetService("HttpService")
local ScriptEditorService = game:GetService("ScriptEditorService")
local ChangeHistoryService = game:GetService("ChangeHistoryService")
local Selection = game:GetService("Selection")
local RunService = game:GetService("RunService")
local toolbar = plugin:CreateToolbar("RoScripter")
local button = toolbar:CreateButton("RoScripter", "Import a script from RoScripter", "")
local info = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Right, false, false, 420, 650, 320, 500)
local widget = plugin:CreateDockWidgetPluginGui("RoScripterBridgeV1", info)
widget.Title = "RoScripter"
button.Click:Connect(function() widget.Enabled = not widget.Enabled end)
local frame = Instance.new("ScrollingFrame")
frame.Size = UDim2.fromScale(1,1)
frame.AutomaticCanvasSize = Enum.AutomaticSize.Y
frame.CanvasSize = UDim2.new()
frame.BackgroundColor3 = Color3.fromRGB(26,29,35)
frame.ScrollBarThickness = 6
frame.Parent = widget
local layout = Instance.new("UIListLayout")
layout.Padding = UDim.new(0,12)
layout.SortOrder = Enum.SortOrder.LayoutOrder
layout.Parent = frame
local padding = Instance.new("UIPadding")
padding.PaddingTop = UDim.new(0,16)
padding.PaddingLeft = UDim.new(0,16)
padding.PaddingRight = UDim.new(0,16)
padding.PaddingBottom = UDim.new(0,16)
padding.Parent = frame
local function label(text: string, height: number): TextLabel
 local item = Instance.new("TextLabel")
 item.Size = UDim2.new(1,0,0,height)
 item.BackgroundTransparency = 1
 item.TextColor3 = Color3.fromRGB(243,245,248)
 item.TextWrapped = true
 item.TextXAlignment = Enum.TextXAlignment.Left
 item.Font = Enum.Font.SourceSans
 item.TextSize = 16
 item.Text = text
 item.Parent = frame
 return item
end
local function box(placeholder: string, height: number): TextBox
 local item = Instance.new("TextBox")
 item.Size = UDim2.new(1,0,0,height)
 item.BackgroundColor3 = Color3.fromRGB(46,51,60)
 item.TextColor3 = Color3.fromRGB(243,245,248)
 item.PlaceholderText = placeholder
 item.Text = ""
 item.ClearTextOnFocus = false
 item.MultiLine = height > 50
 item.TextWrapped = true
 item.TextSize = 16
 item.Font = Enum.Font.Code
 item.Parent = frame
 return item
end
local function action(text: string): TextButton
 local item = Instance.new("TextButton")
 item.Size = UDim2.new(1,0,0,40)
 item.BackgroundColor3 = Color3.fromRGB(34,194,115)
 item.TextColor3 = Color3.fromRGB(6,33,15)
 item.Font = Enum.Font.SourceSansBold
 item.TextSize = 17
 item.Text = text
 item.Parent = frame
 return item
end
label("1. Enter your RoScripter website URL. Local preview uses http://localhost:3000.", 55)
local origin = box("https://your-roscripter-site.example",40)
origin.Text = plugin:GetSetting("RoScripterOrigin") or "http://localhost:3000"
label("2. On a generated script, choose Insert via plugin. Paste its one-use transfer code here.",55)
local tokenBox = box("Transfer code (valid for 10 minutes)",40)
local fetchButton = action("Load script preview")
local preview = box("Your script preview appears here",180)
preview.TextEditable = false
local placement = label("No script loaded.",80)
local insertButton = action("Insert reviewed script (disabled)")
label("New Scripts and LocalScripts are inserted disabled. Review and enable them in Studio. Existing scripts are never replaced; Undo is supported.",75)
local output = box("Paste only the relevant Studio Output error to send back",100)
local reportButton = action("Send this error to website")
local status = label("Ready. No code runs until you choose to test it in Studio.",70)
local loaded: any = nil
local token = ""
local site = ""
local busy = false
local function request(data: any): any
 local response = HttpService:RequestAsync({Url=site.."/api/studio/transfer",Method="POST",Headers={["Content-Type"]="application/json"},Body=HttpService:JSONEncode(data)})
 local ok, body = pcall(function() return HttpService:JSONDecode(response.Body) end)
 if not ok or type(body) ~= "table" then error("The website returned an invalid response.") end
 if not response.Success or body.code ~= 0 then error(body.message or "Request failed.") end
 return body.data
end
fetchButton.MouseButton1Click:Connect(function()
 if busy then return end
 site = origin.Text:gsub("/+$", "")
 if not site:match("^https://[%w%.%-]+:?%d*$") and not site:match("^http://localhost:%d+$") and not site:match("^http://127%.0%.0%.1:%d+$") then status.Text="Use an HTTPS website origin, or localhost for development." return end
 token = tokenBox.Text:gsub("%s", "")
 if #token ~= 48 or token:find("[^a-f0-9]") then status.Text="Paste the complete transfer code from the website." return end
 loaded = nil
 preview.Text = ""
 busy = true
 status.Text = "Loading… Studio may ask permission to contact this website."
 local ok, err = pcall(function()
  local body = request({action="claim",token=token})
  local value = body.script
  if type(value)~="table" or type(value.code)~="string" or #value.code>120000 or (value.script_type~="Script" and value.script_type~="LocalScript" and value.script_type~="ModuleScript") then error("Invalid script payload.") end
  loaded = value
  preview.Text = value.code
  placement.Text = value.script_type.." → "..value.location.."\nNeeds: "..table.concat(value.requires or {}, "; ")
  plugin:SetSetting("RoScripterOrigin", site)
  tokenBox.Text = ""
  status.Text = "Review the script and placement, then choose Insert."
 end)
 if not ok then status.Text=tostring(err) end
 busy = false
end)
local function destination(path: string): Instance
 local pieces = {}
 for segment in path:gsub("^game%.", ""):gmatch("[^>./]+") do table.insert(pieces, segment:match("^%s*(.-)%s*$")) end
 local roots: {[string]: Instance} = {ServerScriptService=game:GetService("ServerScriptService"),ReplicatedStorage=game:GetService("ReplicatedStorage"),ServerStorage=game:GetService("ServerStorage"),Workspace=workspace,StarterGui=game:GetService("StarterGui"),StarterPlayer=game:GetService("StarterPlayer")}
 local target = roots[pieces[1]]
 if not target then error("Unsupported location. Copy the code and place it manually.") end
 for i=2,#pieces do
  local child = target:FindFirstChild(pieces[i])
  if not child then error("Missing "..pieces[i]..". Create the required objects before inserting.") end
  target = child
 end
 return target
end
insertButton.MouseButton1Click:Connect(function()
 if busy or not loaded then return end
 if RunService:IsRunning() then status.Text="Stop the play test before inserting a script." return end
 local value = loaded
 local created: LuaSourceContainer? = nil
 local recording: string? = nil
 local ok, err = pcall(function()
  local parent = destination(value.location)
  recording = ChangeHistoryService:TryBeginRecording("RoScripterInsert", "Insert RoScripter script")
  if not recording then error("Studio cannot start an undo recording right now.") end
  local item = Instance.new(value.script_type) :: LuaSourceContainer
  created = item
  item.Name = "RoScripter_"..value.script_type
  if item:IsA("BaseScript") then item.Enabled = false end
  item.Parent = parent
  ScriptEditorService:UpdateSourceAsync(item,function(_old) return value.code end)
  Selection:Set({item})
  ChangeHistoryService:FinishRecording(recording :: string, Enum.FinishRecordingOperation.Commit)
  recording = nil
  loaded = nil
  status.Text="Inserted. Review it, enable it if needed, then test in Studio. Use Undo to remove the insertion."
 end)
 if not ok then
  if created then created:Destroy() end
  if recording then pcall(function() ChangeHistoryService:FinishRecording(recording :: string,Enum.FinishRecordingOperation.Cancel) end) end
  status.Text=tostring(err)
 end
end)
reportButton.MouseButton1Click:Connect(function()
 if busy or token=="" then return end
 if #output.Text<1 or #output.Text>4000 then status.Text="Paste 1–4,000 characters of the relevant error only." return end
 busy=true
 local ok, err=pcall(function() request({action="report",token=token,output=output.Text}) end)
 status.Text=if ok then "Error sent. Return to the website and choose Fix with this error." else tostring(err)
 busy=false
end)
plugin.Unloading:Connect(function() token=""; loaded=nil end)
