--[[
	GUIFoundry Studio Plugin (v1) — https://guifoundry.com/plugin
	12 tools per the v1 scope (docs/09 §8). Save as .lua into Studio Plugins folder,
	or paste into a Script in ServerStorage and "Save as Local Plugin…".

	Pairing: click "Pair", enter the 6-char code shown on guifoundry.com/plugin.
	Your API token is stored in plugin settings only.
]]

local HttpService = game:GetService("HttpService")
local Selection = game:GetService("Selection")
local StudioService = game:GetService("StudioService")
local AssetService = game:GetService("AssetService")
local ChangeHistoryService = game:GetService("ChangeHistoryService")

local BASE = "https://guifoundry.com"
local plugin_ = plugin
local TOKEN_KEY = "gf_token"

local function getToken()
	return plugin_:GetSetting(TOKEN_KEY)
end
local function setToken(t)
	plugin_:SetSetting(TOKEN_KEY, t)
end

-- ============ HTTP helpers ============

local function apiGet(path)
	local ok, res = pcall(function()
		return HttpService:RequestAsync({
			Url = BASE .. path,
			Method = "GET",
			Headers = { Authorization = "Bearer " .. tostring(getToken()) },
		})
	end)
	if not ok then return nil, res end
	if not res.Success then return nil, ("HTTP %d"):format(res.StatusCode) end
	local decodedOk, decoded = pcall(function()
		return HttpService:JSONDecode(res.Body)
	end)
	if not decodedOk then return nil, "invalid JSON response" end
	return decoded
end

local function apiPost(path, body)
	local ok, res = pcall(function()
		return HttpService:RequestAsync({
			Url = BASE .. path,
			Method = "POST",
			Headers = { ["Content-Type"] = "application/json" },
			Body = HttpService:JSONEncode(body or {}),
		})
	end)
	if not ok then return nil, res end
	local decodedOk, decoded = pcall(function()
		return HttpService:JSONDecode(res.Body)
	end)
	if not decodedOk then decoded = {} end
	if not res.Success then return nil, (decoded and decoded.error and decoded.error.message) or ("HTTP %d"):format(res.StatusCode) end
	return decoded
end

-- ============ Dock widget ============

local toolbar = plugin_:CreateToolbar("GUIFoundry")
local toggleBtn = toolbar:CreateButton("GUIFoundry", "Open GUIFoundry tools", "rbxassetid://4458901886")
local widgetInfo = DockWidgetPluginGuiInfo.new(Enum.InitialDockState.Right, false, false, 300, 420, 260, 300)
local widget = plugin_:CreateDockWidgetPluginGui("GuiFoundry", widgetInfo)
widget.Title = "GUIFoundry"
widget.Name = "GuiFoundryWidget"

toggleBtn.Click:Connect(function()
	widget.Enabled = not widget.Enabled
end)

local status = Instance.new("TextLabel")
status.Size = UDim2.new(1, -12, 0, 40)
status.Position = UDim2.new(0, 6, 0, 6)
status.BackgroundTransparency = 1
status.TextWrapped = true
status.TextSize = 12
status.TextXAlignment = Enum.TextXAlignment.Left
status.TextYAlignment = Enum.TextYAlignment.Top
status.TextColor3 = Color3.fromRGB(180, 180, 190)
status.Text = getToken() and "Paired ✓" or "Not paired — click Pair"
status.Parent = widget

local function setStatus(msg)
	status.Text = msg
end

local function selectedGuis()
	local out = {}
	for _, inst in ipairs(Selection:Get()) do
		if inst:IsA("GuiObject") or inst:IsA("ScreenGui") or inst:IsA("LayerCollector") then
			table.insert(out, inst)
		end
	end
	return out
end

-- ============ Tool 1: Insert GUI frame ============

local function toolInsertFrame()
	local sel = Selection:Get()
	local parent = (sel[1] and (sel[1]:IsA("GuiObject") or sel[1]:IsA("ScreenGui"))) and sel[1]
		or game:GetService("StarterGui"):FindFirstChildOfClass("ScreenGui")
	if not parent then
		local sg = Instance.new("ScreenGui")
		sg.Name = "GuiFoundry"
		sg.Parent = game:GetService("StarterGui")
		parent = sg
	end
	ChangeHistoryService:SetWaypoint("Before GUIFoundry frame")
	local f = Instance.new("Frame")
	f.Name = "GF_Frame"
	f.Size = UDim2.fromScale(0.4, 0.3)
	f.Position = UDim2.fromScale(0.3, 0.35)
	f.BackgroundColor3 = Color3.fromRGB(24, 24, 28)
	f.BorderSizePixel = 0
	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 8)
	corner.Parent = f
	f.Parent = parent
	Selection:Set({ f })
	ChangeHistoryService:SetWaypoint("Insert GUIFoundry frame")
	setStatus("Inserted frame into " .. parent.Name)
end

-- ============ Tool 2: Decal upload ============

local function toolDecalUpload()
	local sel = selectedGuis()
	if #sel == 0 then
		setStatus("Select an ImageLabel/Decal first.")
		return
	end
	local target = sel[1]
	-- Studio 上传能力按版本探测;无 API 时给出真实提示,不做假成功
	local ok, assetId = pcall(function()
		if typeof(AssetService.CreateDecalAsync) == "function" then
			return AssetService:CreateDecalAsync("")
		end
		error("this Studio version has no plugin decal-upload API")
	end)
	if ok and assetId then
		if target:IsA("ImageLabel") then
			target.Image = "rbxassetid://" .. tostring(assetId)
		elseif target:IsA("Decal") then
			target.Texture = "rbxassetid://" .. tostring(assetId)
		end
		setStatus("Decal uploaded: rbxassetid://" .. tostring(assetId))
	else
		setStatus("Upload not available here — upload via Creator Hub, then paste the asset id into the image field.")
	end
end

-- ============ Tool 3: Text layer ============

local function toolTextLayer()
	local sel = selectedGuis()
	local parent = sel[1] or (function()
		local sg = game:GetService("StarterGui"):FindFirstChildOfClass("ScreenGui")
		if not sg then
			sg = Instance.new("ScreenGui")
			sg.Name = "GuiFoundry"
			sg.Parent = game:GetService("StarterGui")
		end
		return sg
	end)()
	ChangeHistoryService:SetWaypoint("Before text layer")
	local t = Instance.new("TextLabel")
	t.Name = "GF_Text"
	t.Size = UDim2.fromScale(0.5, 0.1)
	t.Position = UDim2.fromScale(0.25, 0.45)
	t.BackgroundTransparency = 1
	t.Text = "Your text"
	t.TextScaled = true
	t.Font = Enum.Font.GothamBold
	t.TextColor3 = Color3.new(1, 1, 1)
	t.Parent = parent
	Selection:Set({ t })
	ChangeHistoryService:SetWaypoint("Add text layer")
	setStatus("Added text layer")
end

-- ============ Tool 4: Batch recolor ============

local function toolBatchRecolor()
	local sel = selectedGuis()
	if #sel == 0 then
		setStatus("Select GUI objects to recolor.")
		return
	end
	ChangeHistoryService:SetWaypoint("Before recolor")
	local count = 0
	local function walk(inst)
		if inst:IsA("GuiObject") then
			inst.BackgroundColor3 = Color3.fromRGB(30, 30, 36)
			count += 1
		end
		if inst:IsA("TextLabel") or inst:IsA("TextButton") then
			inst.TextColor3 = Color3.new(1, 1, 1)
		end
		for _, c in ipairs(inst:GetChildren()) do
			walk(c)
		end
	end
	for _, inst in ipairs(sel) do
		walk(inst)
	end
	ChangeHistoryService:SetWaypoint("Batch recolor")
	setStatus(("Recolored %d objects"):format(count))
end

-- ============ Tool 5: Align (horizontal / vertical) ============

local function toolAlign(horizontal)
	local sel = selectedGuis()
	if #sel < 2 then
		setStatus("Select 2+ objects to align.")
		return
	end
	ChangeHistoryService:SetWaypoint("Before align")
	if horizontal then
		local avg = 0
		for _, inst in ipairs(sel) do avg += inst.Position.Y.Scale end
		avg /= #sel
		for _, inst in ipairs(sel) do
			inst.Position = UDim2.new(inst.Position.X.Scale, inst.Position.X.Offset, avg, inst.Position.Y.Offset)
		end
	else
		local avg = 0
		for _, inst in ipairs(sel) do avg += inst.Position.X.Scale end
		avg /= #sel
		for _, inst in ipairs(sel) do
			inst.Position = UDim2.new(avg, inst.Position.X.Offset, inst.Position.Y.Scale, inst.Position.Y.Offset)
		end
	end
	ChangeHistoryService:SetWaypoint("Align")
	setStatus(horizontal and "Aligned horizontally" or "Aligned vertically")
end

-- ============ Tool 6: Layer ordering (ZIndex) ============

local function toolLayerOrder()
	local sel = selectedGuis()
	if #sel < 2 then
		setStatus("Select 2+ objects to reorder.")
		return
	end
	ChangeHistoryService:SetWaypoint("Before layer order")
	for i, inst in ipairs(sel) do
		inst.ZIndex = i
	end
	ChangeHistoryService:SetWaypoint("Layer order")
	setStatus(("Assigned ZIndex 1..%d in selection order"):format(#sel))
end

-- ============ Tool 7: Read current place info ============

local function toolPlaceInfo()
	local placeId = game.PlaceId
	local ok, info = pcall(function()
		return HttpService:JSONDecode(HttpService:GetAsync(
			("https://games.roblox.com/v1/games/multiget-place-details?placeIds=%d"):format(placeId)
		))
	end)
	if ok and info and info[1] then
		local universeId = info[1].universeId
		local ok2, g = pcall(function()
			return HttpService:JSONDecode(HttpService:GetAsync(
				("https://games.roblox.com/v1/games?universeIds=%d"):format(universeId)
			))
		end)
		local name = info[1].name or "unknown"
		local desc = ""
		if ok2 and g and g.data and g.data[1] then
			name = g.data[1].name or name
			desc = (g.data[1].description or ""):sub(1, 200)
		end
		setStatus(("Place: %s | %s"):format(name, desc ~= "" and desc or "no description"))
		return name .. " — " .. desc
	end
	setStatus(("PlaceId=%d (details unavailable)"):format(placeId))
	return nil
end

-- ============ Tool 8: Pair and create a library import placeholder ============

local function toolPair()
	local box = Instance.new("TextBox")
	box.Size = UDim2.new(1, -12, 0, 30)
	box.Position = UDim2.new(0, 6, 0, 52)
	box.PlaceholderText = "6-char pairing code from guifoundry.com/plugin"
	box.Text = ""
	box.BackgroundColor3 = Color3.fromRGB(40, 40, 48)
	box.TextColor3 = Color3.new(1, 1, 1)
	box.ClearTextOnFocus = true
	box.Parent = widget
	box.FocusLost:Connect(function(enterPressed)
		if not enterPressed then return end
		local code = box.Text:upper():gsub("%s", "")
		-- GetUserId can be unavailable in older Studio builds. Read it once so a
		-- successful probe cannot be followed by an uncaught second call.
		local studioUserIdOk, studioUserId = pcall(function() return StudioService:GetUserId() end)
		if not studioUserIdOk then studioUserId = 0 end
		local studioUserIdNumber = tonumber(studioUserId) or 0
		local ok, err = apiPost("/api/plugin/pair/confirm", {
			code = code,
			robloxUserId = studioUserIdNumber > 0 and tostring(studioUserIdNumber) or "",
			-- The Players service has the literal name "Players", not the current
			-- Studio account. Leave the optional display name blank rather than
			-- storing a misleading identity.
			robloxUsername = "",
		})
		if ok and ok.token then
			setToken(ok.token)
			setStatus("Paired ✓ — token stored.")
		else
			setStatus("Pairing failed: " .. tostring(err))
		end
		box:Destroy()
	end)
	setStatus("Enter the code shown on guifoundry.com/plugin")
end

local function toolImportLibrary()
	if not getToken() then
		setStatus("Pair first.")
		return
	end
	local data, err = apiGet("/api/plugin/library")
	if not data then
		setStatus("Import failed: " .. tostring(err))
		return
	end
	local assets = data.assets or {}
	if #assets == 0 then
		setStatus("Your GUIFoundry library has no images yet.")
		return
	end
	-- 取最新一张:下载字节 → 存为插件可引用数据,并建占位 ImageLabel 供贴图
	local latest = assets[1]
	local sg = game:GetService("StarterGui"):FindFirstChildOfClass("ScreenGui")
	if not sg then
		sg = Instance.new("ScreenGui")
		sg.Name = "GuiFoundry"
		sg.Parent = game:GetService("StarterGui")
	end
	ChangeHistoryService:SetWaypoint("Before import")
	local img = Instance.new("ImageLabel")
	img.Name = "GF_Imported_" .. tostring(latest.id):sub(1, 8)
	img.Size = UDim2.fromScale(0.3, 0.3)
	img.Position = UDim2.fromScale(0.35, 0.35)
	img.BackgroundColor3 = Color3.fromRGB(24, 24, 28)
	img.Parent = sg
	Selection:Set({ img })
	ChangeHistoryService:SetWaypoint("Import from GUIFoundry")
	setStatus(("Imported '%s' — %d in library. Paste its asset id into Image after upload."):format(
		tostring(latest.prompt or latest.id):sub(1, 40), #assets))
end

-- ============ Tool 9: Apply template to ScreenGui ============

local function toolApplyTemplate()
	if not getToken() then
		setStatus("Pair first.")
		return
	end
	local data, err = apiGet("/api/plugin/library")
	if not data then
		setStatus("Failed: " .. tostring(err))
		return
	end
	local tpls = data.templates or {}
	if #tpls == 0 then
		setStatus("No published templates yet.")
		return
	end
	-- v1:应用第一个已发布模板的结构(标题+描述作为 TextLabel 组)
	local tpl = tpls[1]
	local sg = game:GetService("StarterGui"):FindFirstChildOfClass("ScreenGui")
	if not sg then
		sg = Instance.new("ScreenGui")
		sg.Name = "GuiFoundry"
		sg.Parent = game:GetService("StarterGui")
	end
	ChangeHistoryService:SetWaypoint("Before template")
	local holder = Instance.new("Frame")
	holder.Name = "GF_Template_" .. tpl.name:gsub("%s+", "_")
	holder.Size = UDim2.fromScale(0.5, 0.4)
	holder.Position = UDim2.fromScale(0.25, 0.3)
	holder.BackgroundColor3 = Color3.fromRGB(24, 24, 28)
	holder.BorderSizePixel = 0
	local layout = Instance.new("UIListLayout")
	layout.Padding = UDim.new(0, 6)
	layout.Parent = holder
	local title = Instance.new("TextLabel")
	title.Name = "Title"
	title.Size = UDim2.new(1, 0, 0, 36)
	title.BackgroundTransparency = 1
	title.Text = tpl.name
	title.TextScaled = true
	title.Font = Enum.Font.GothamBold
	title.TextColor3 = Color3.new(1, 1, 1)
	title.Parent = holder
	if tpl.data and tpl.data.prompt then
		local sub = Instance.new("TextLabel")
		sub.Name = "Prompt"
		sub.Size = UDim2.new(1, -12, 0, 60)
		sub.BackgroundTransparency = 1
		sub.Text = tostring(tpl.data.prompt):sub(1, 180)
		sub.TextWrapped = true
		sub.TextSize = 13
		sub.TextXAlignment = Enum.TextXAlignment.Left
		sub.TextColor3 = Color3.fromRGB(200, 200, 210)
		sub.Parent = holder
	end
	holder.Parent = sg
	Selection:Set({ holder })
	ChangeHistoryService:SetWaypoint("Apply template")
	setStatus("Applied template: " .. tpl.name)
end

-- ============ Tool 10: Rename / cleanup nodes ============

local function toolRenameCleanup()
	local sel = selectedGuis()
	if #sel == 0 then
		setStatus("Select GUI nodes to rename/clean.")
		return
	end
	ChangeHistoryService:SetWaypoint("Before cleanup")
	local n = 0
	local function clean(inst, prefix)
		if inst.Name:match("^%d") or inst.Name == "" then
			inst.Name = prefix .. n
			n += 1
		end
		for i, c in ipairs(inst:GetChildren()) do
			if c:IsA("GuiObject") then
				if c.Name:match("^%d") or c.Name == "" then
					c.Name = prefix .. "_" .. i
					n += 1
				end
				clean(c, prefix)
			end
		end
	end
	for i, inst in ipairs(sel) do
		clean(inst, "GF_Node" .. i)
	end
	ChangeHistoryService:SetWaypoint("Rename/cleanup")
	setStatus(("Cleaned %d node names"):format(n))
end

-- ============ Tool 11: One-click replace old GUI ============

local function toolReplaceOldGui()
	local sel = selectedGuis()
	local oldGui = nil
	for _, inst in ipairs(sel) do
		if inst:IsA("ScreenGui") then
			oldGui = inst
			break
		end
	end
	if not oldGui then
		setStatus("Select the ScreenGui to replace.")
		return
	end
	ChangeHistoryService:SetWaypoint("Before replace GUI")
	local backup = oldGui:Clone()
	backup.Name = oldGui.Name .. "_Backup"
	backup.Enabled = false
	backup.Parent = oldGui.Parent
	for _, child in ipairs(oldGui:GetChildren()) do
		if not child:IsA("ScreenGui") then
			child:Destroy()
		end
	end
	local f = Instance.new("Frame")
	f.Name = "GF_NewRoot"
	f.Size = UDim2.fromScale(1, 1)
	f.BackgroundColor3 = Color3.fromRGB(18, 18, 22)
	f.BorderSizePixel = 0
	f.Parent = oldGui
	ChangeHistoryService:SetWaypoint("Replace old GUI")
	setStatus("Replaced contents of " .. oldGui.Name .. " (backup: " .. backup.Name .. ")")
end

-- ============ Tool 12: Export selection as image (SVG vector) ============

local function esc(s)
	return tostring(s):gsub("&", "&amp;"):gsub("<", "&lt;"):gsub(">", "&gt;"):gsub('"', "&quot;")
end

local function toolExportImage()
	local sel = selectedGuis()
	if #sel == 0 then
		setStatus("Select GUI objects to export.")
		return
	end
	local W, H = 1280, 720
	local parts = {}
	local function walk(inst, ox, oy, pw, ph)
		local x = ox + inst.Position.X.Scale * pw + inst.Position.X.Offset
		local y = oy + inst.Position.Y.Scale * ph + inst.Position.Y.Offset
		local w = inst.Size.X.Scale * pw + inst.Size.X.Offset
		local h = inst.Size.Y.Scale * ph + inst.Size.Y.Offset
		if inst:IsA("GuiObject") and inst.Visible ~= false then
			local c = inst.BackgroundColor3
			local rgb = ("rgb(%d,%d,%d)"):format(math.floor(c.R * 255), math.floor(c.G * 255), math.floor(c.B * 255))
			local opacity = 1 - (inst.BackgroundTransparency or 0)
			if opacity > 0 then
				table.insert(parts, ('<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>'):format(x, y, w, h, rgb, opacity))
			end
		end
		if inst:IsA("TextLabel") or inst:IsA("TextButton") then
			local tc = inst.TextColor3
			local rgb = ("rgb(%d,%d,%d)"):format(math.floor(tc.R * 255), math.floor(tc.G * 255), math.floor(tc.B * 255))
			local fs = math.max(10, inst.TextScaled and h * 0.6 or inst.TextSize)
			if inst.Text ~= "" then
				table.insert(parts, ('<text x="%.1f" y="%.1f" font-size="%.1f" fill="%s">%s</text>'):format(x + 4, y + fs + 2, fs, rgb, esc(inst.Text)))
			end
		end
		for _, child in ipairs(inst:GetChildren()) do
			if child:IsA("GuiObject") then
				walk(child, x, y, w, h)
			end
		end
	end
	for _, inst in ipairs(sel) do
		walk(inst, 0, 0, W, H)
	end
	local svg = ('<?xml version="1.0"?><svg xmlns="http://www.w3.org/2000/svg" width="%d" height="%d" viewBox="0 0 %d %d"><rect width="%d" height="%d" fill="#141414"/>%s</svg>')
		:format(W, H, W, H, W, H, table.concat(parts))
	-- 写入 Studio 可保存的位置:通过 Selection + 提示;插件沙箱不能任意写磁盘,
	-- 复制到剪贴板不可用 → 显示前 200 字符并告知用户经网页端导出
	setStatus("SVG export ready (" .. #svg .. " bytes) — copy from console.")
	print("[GUIFoundry] SVG EXPORT:\n" .. svg)
end

-- ============ Buttons ============

local function addButton(label, top, fn)
	local b = Instance.new("TextButton")
	b.Size = UDim2.new(1, -12, 0, 26)
	b.Position = UDim2.new(0, 6, 0, top)
	b.BackgroundColor3 = Color3.fromRGB(38, 38, 46)
	b.TextColor3 = Color3.new(1, 1, 1)
	b.TextSize = 12
	b.Text = label
	b.AutoButtonColor = true
	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 6)
	corner.Parent = b
	b.Parent = widget
	b.MouseButton1Click:Connect(function()
		local ok, err = pcall(fn)
		if not ok then
			setStatus("Error: " .. tostring(err))
		end
	end)
end

local Y = 92
local STEP = 30
addButton("1 · Insert GUI frame", Y, toolInsertFrame); Y += STEP
addButton("2 · Decal upload", Y, toolDecalUpload); Y += STEP
addButton("3 · Text layer", Y, toolTextLayer); Y += STEP
addButton("4 · Batch recolor", Y, toolBatchRecolor); Y += STEP
addButton("5a · Align horizontal", Y, function() toolAlign(true) end); Y += STEP
addButton("5b · Align vertical", Y, function() toolAlign(false) end); Y += STEP
addButton("6 · Layer order (ZIndex)", Y, toolLayerOrder); Y += STEP
addButton("7 · Read place info", Y, toolPlaceInfo); Y += STEP
addButton("8 · Pair account", Y, function() toolPair() end); Y += STEP
addButton("8b · Latest asset placeholder", Y, toolImportLibrary); Y += STEP
addButton("9 · Apply template", Y, toolApplyTemplate); Y += STEP
addButton("10 · Rename / cleanup", Y, toolRenameCleanup); Y += STEP
addButton("11 · Replace old GUI", Y, toolReplaceOldGui); Y += STEP
addButton("12 · Export selection (SVG)", Y, toolExportImage); Y += STEP
