-- Vine Loader — paste key → verify → auto-run your hub script
-- 1. Fill in Vine.Config below (ProjectId + vine_... project key).
-- 2. Set your hub's main script in the dashboard (Project settings →
--    "Main script"). It runs automatically after a correct key.
-- 3. Get keys at your key page: https://vinekey.space/key.html?project=...
--
-- Usage:
--   local Vine = loadstring(game:HttpGet("https://vinekey.space/vine_loader.lua"))()
--   Vine.Config.ProjectId = "YOUR_PROJECT_ID"
--   Vine.Config.ApiKey = "vine_..."
--   Vine.Prompt(function(ok, err)
--       if ok then print("Vine: hub loaded") else warn("Vine: " .. tostring(err)) end
--   end)
--
-- The UI is draggable (top bar), closable (x), has a Get Key button that
-- copies your key-page link with the device HWID prefilled, and enforces
-- a small cooldown between attempts so players can't spam verify.
--
-- The project key (x-api-key) is a per-project publishable key, safe to
-- ship in scripts. Never ship Supabase service_role keys in Roblox.

local Vine = {}
Vine.Config = {
    BaseUrl = "https://vinekey.space/v1", -- your deployment (no trailing slash)
    ProjectId = "YOUR_PROJECT_ID",
    ApiKey = "YOUR_PROJECT_API_KEY", -- vine_... (project key)
    CooldownSeconds = 5, -- client-side delay between Unlock attempts
    RateLimitedCooldown = 30, -- delay after the server says rate_limited
}

local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")

local function getHwid()
    -- Replace with your executor/HWID method. Must match ^[A-Za-z0-9:_.-]{6,128}$
    local ok, id = pcall(function() return gethwid and gethwid() end)
    if ok and type(id) == "string" and #id >= 6 then return id end
    return tostring(Players.LocalPlayer and Players.LocalPlayer.UserId or "unknown-hwid")
end

local function headers(key)
    local h = {
        ["x-api-key"] = Vine.Config.ApiKey,
        ["x-project-id"] = Vine.Config.ProjectId,
        ["x-hwid"] = getHwid(),
    }
    -- Send the key as a header so it stays out of Vercel/CDN URL logs.
    -- ?key= is still accepted server-side as a fallback for old scripts.
    if key then h["x-key"] = key end
    return h
end

local function decode(body)
    local data
    local ok = pcall(function()
        data = HttpService:JSONDecode(body)
    end)
    if not ok or type(data) ~= "table" then return nil end
    return data
end

-- Key-page link carrying this device's HWID (?hwid= is hidden on the
-- page — the minted key is silently device-locked).
function Vine.KeyPageLink()
    local base = Vine.Config.BaseUrl:gsub("/v1/*$", "")
    return base .. "/key.html?project=" .. HttpService:UrlEncode(Vine.Config.ProjectId)
        .. "&hwid=" .. HttpService:UrlEncode(getHwid())
end

-- Step 1: check the key with /v1/verify. Returns true + info, or false + reason.
-- Key travels in the x-key header (not the URL) to avoid access-log leaks.
function Vine.Verify(key)
    assert(type(key) == "string" and #key > 10, "Vine: key required")
    local url = Vine.Config.BaseUrl .. "/verify?project=" .. HttpService:UrlEncode(Vine.Config.ProjectId)
    local ok, res = pcall(function()
        return request({ Url = url, Method = "GET", Headers = headers(key) })
    end)
    if not ok or not res then return false, "request_failed" end
    if res.StatusCode == 429 then return false, "rate_limited" end
    local data = decode(res.Body)
    if not data then return false, "bad_response" end
    if data.valid == true then return true, data end
    return false, data.reason or data.error or "invalid_key"
end

-- Step 2: fetch the project's main script (valid keys only). The server
-- re-verifies the key before returning anything.
function Vine.GetScript(key)
    local url = Vine.Config.BaseUrl .. "/script?project=" .. HttpService:UrlEncode(Vine.Config.ProjectId)
    local ok, res = pcall(function()
        return request({ Url = url, Method = "GET", Headers = headers(key) })
    end)
    if not ok or not res then return false, "request_failed" end
    if res.StatusCode == 429 then return false, "rate_limited" end
    local data = decode(res.Body)
    if not data then return false, "bad_response" end
    if data.valid ~= true then return false, data.reason or data.error or "invalid_key" end
    if not data.hasScript or type(data.script) ~= "string" or #data.script == 0 then
        return false, "no_script_set"
    end
    return true, data.script
end

-- Verify + fetch + run. onLoaded(true) on success, onLoaded(false, reason)
-- on any failure (bad key, rate limit, missing script, load error).
function Vine.Load(key, onLoaded)
    local function done(ok, err)
        if onLoaded then
            local okCb, cbErr = pcall(onLoaded, ok, err)
            if not okCb then warn("Vine: callback error: " .. tostring(cbErr)) end
        end
        return ok, err
    end
    local ok, info = Vine.Verify(key)
    if not ok then return done(false, info) end
    local ok2, script = Vine.GetScript(key)
    if not ok2 then return done(false, script) end
    local fn, loadErr = loadstring(script)
    if not fn then return done(false, "script_load_error") end
    local okRun, runErr = pcall(fn)
    if not okRun then
        warn("Vine: hub error: " .. tostring(runErr))
        return done(false, "hub_error")
    end
    return done(true)
end

-- Drag helper (mouse + touch): press the handle to move the window.
local function makeDraggable(frame, handle)
    local dragging = false
    local dragStart, startPos
    handle.InputBegan:Connect(function(input)
        if input.UserInputType == Enum.UserInputType.MouseButton1
            or input.UserInputType == Enum.UserInputType.Touch then
            dragging = true
            dragStart = input.Position
            startPos = frame.Position
            input.Changed:Connect(function()
                if input.UserInputState == Enum.UserInputState.End then
                    dragging = false
                end
            end)
        end
    end)
    UserInputService.InputChanged:Connect(function(input)
        if dragging
            and (input.UserInputType == Enum.UserInputType.MouseMovement
                or input.UserInputType == Enum.UserInputType.Touch) then
            local delta = input.Position - dragStart
            frame.Position = UDim2.new(
                startPos.X.Scale, startPos.X.Offset + delta.X,
                startPos.Y.Scale, startPos.Y.Offset + delta.Y
            )
        end
    end)
end

local function friendlyError(err)
    if err == "invalid_key" then return "Invalid key"
    elseif err == "expired" then return "Key expired — get a new one"
    elseif err == "hwid_mismatch" then return "Key is locked to another device"
    elseif err == "rate_limited" then return "Too fast — slow down"
    elseif err == "no_script_set" then return "No hub script set — ask the owner"
    elseif err == "request_failed" or err == "bad_response" then return "Network error — retry"
    else return tostring(err) end
end

-- Key UI (black + vine green, matches website). Draggable top bar, close
-- button, Unlock + Get Key buttons, per-attempt cooldown.
function Vine.Prompt(onLoaded)
    local cooldownUntil = 0
    local gui = Instance.new("ScreenGui")
    gui.Name = "VineLoader"
    gui.ResetOnSpawn = false
    gui.IgnoreGuiInset = true

    local frame = Instance.new("Frame")
    frame.Size = UDim2.new(0, 340, 0, 330)
    frame.Position = UDim2.new(0.5, -170, 0.5, -165)
    frame.BackgroundColor3 = Color3.fromRGB(13, 15, 13)
    frame.BorderSizePixel = 0
    frame.Parent = gui
    local corner = Instance.new("UICorner") corner.CornerRadius = UDim.new(0, 14) corner.Parent = frame
    local stroke = Instance.new("UIStroke") stroke.Color = Color3.fromRGB(28, 33, 28) stroke.Thickness = 1 stroke.Parent = frame

    local strip = Instance.new("Frame")
    strip.Size = UDim2.new(1, 0, 0, 6)
    strip.BackgroundColor3 = Color3.fromRGB(22, 163, 74)
    strip.BorderSizePixel = 0
    strip.Parent = frame
    local stripCorner = Instance.new("UICorner") stripCorner.CornerRadius = UDim.new(0, 14) stripCorner.Parent = strip

    -- Drag handle (title row)
    local bar = Instance.new("Frame")
    bar.Size = UDim2.new(1, 0, 0, 52)
    bar.Position = UDim2.new(0, 0, 0, 6)
    bar.BackgroundTransparency = 1
    bar.Parent = frame
    local title = Instance.new("TextLabel")
    title.Size = UDim2.new(1, -60, 1, 0) title.Position = UDim2.new(0, 20, 0, 0)
    title.BackgroundTransparency = 1
    title.Text = "Vine" title.Font = Enum.Font.GothamBold title.TextSize = 26
    title.TextColor3 = Color3.fromRGB(34, 197, 94)
    title.TextXAlignment = Enum.TextXAlignment.Left
    title.Parent = bar
    local closeBtn = Instance.new("TextButton")
    closeBtn.Size = UDim2.new(0, 28, 0, 28) closeBtn.Position = UDim2.new(1, -40, 0, 12)
    closeBtn.Text = "x" closeBtn.Font = Enum.Font.GothamBold closeBtn.TextSize = 16
    closeBtn.BackgroundColor3 = Color3.fromRGB(24, 28, 24)
    closeBtn.TextColor3 = Color3.fromRGB(154, 154, 148)
    closeBtn.Parent = bar
    local closeCorner = Instance.new("UICorner") closeCorner.CornerRadius = UDim.new(0, 8) closeCorner.Parent = closeBtn
    closeBtn.MouseButton1Click:Connect(function() gui:Destroy() end)
    makeDraggable(frame, bar)

    local sub = Instance.new("TextLabel")
    sub.Size = UDim2.new(1, -40, 0, 20) sub.Position = UDim2.new(0, 20, 0, 60)
    sub.BackgroundTransparency = 1
    sub.Text = "Enter your key to load the hub" sub.TextSize = 13
    sub.TextColor3 = Color3.fromRGB(154, 154, 148)
    sub.TextXAlignment = Enum.TextXAlignment.Left
    sub.Parent = frame

    local box = Instance.new("TextBox")
    box.Size = UDim2.new(1, -40, 0, 42) box.Position = UDim2.new(0, 20, 0, 86)
    box.PlaceholderText = "Paste your key" box.Text = ""
    box.Font = Enum.Font.Code box.TextSize = 14
    box.BackgroundColor3 = Color3.fromRGB(10, 12, 10)
    box.TextColor3 = Color3.fromRGB(245, 245, 243)
    box.ClearTextOnFocus = false
    box.Parent = frame
    local boxCorner = Instance.new("UICorner") boxCorner.CornerRadius = UDim.new(0, 10) boxCorner.Parent = box

    local btn = Instance.new("TextButton")
    btn.Size = UDim2.new(1, -40, 0, 44) btn.Position = UDim2.new(0, 20, 0, 140)
    btn.Text = "Unlock" btn.Font = Enum.Font.GothamBold btn.TextSize = 16
    btn.BackgroundColor3 = Color3.fromRGB(22, 163, 74)
    btn.TextColor3 = Color3.fromRGB(4, 20, 10)
    btn.AutoButtonColor = true
    btn.Parent = frame
    local btnCorner = Instance.new("UICorner") btnCorner.CornerRadius = UDim.new(0, 10) btnCorner.Parent = btn

    local getBtn = Instance.new("TextButton")
    getBtn.Size = UDim2.new(1, -40, 0, 36) getBtn.Position = UDim2.new(0, 20, 0, 192)
    getBtn.Text = "Get Key" getBtn.Font = Enum.Font.GothamBold getBtn.TextSize = 14
    getBtn.BackgroundTransparency = 1
    getBtn.TextColor3 = Color3.fromRGB(34, 197, 94)
    getBtn.Parent = frame
    local getCorner = Instance.new("UICorner") getCorner.CornerRadius = UDim.new(0, 10) getCorner.Parent = getBtn
    local getStroke = Instance.new("UIStroke") getStroke.Color = Color3.fromRGB(22, 163, 74) getStroke.Thickness = 1 getStroke.Parent = getBtn

    local status = Instance.new("TextLabel")
    status.Size = UDim2.new(1, -40, 0, 34) status.Position = UDim2.new(0, 20, 0, 236)
    status.BackgroundTransparency = 1
    status.Text = "" status.TextSize = 13
    status.TextColor3 = Color3.fromRGB(154, 154, 148)
    status.TextWrapped = true
    status.Parent = frame

    local foot = Instance.new("TextLabel")
    foot.Size = UDim2.new(1, -40, 0, 20) foot.Position = UDim2.new(0, 20, 0, 300)
    foot.BackgroundTransparency = 1
    foot.Text = "Drag the top bar to move • x to close" foot.TextSize = 11
    foot.TextColor3 = Color3.fromRGB(110, 110, 105)
    foot.Parent = frame

    getBtn.MouseButton1Click:Connect(function()
        local link = Vine.KeyPageLink()
        if setclipboard then
            local okClip = pcall(setclipboard, link)
            status.Text = okClip and "Key link copied — open it in your browser." or "Copy failed."
        else
            status.Text = link
        end
    end)

    btn.MouseButton1Click:Connect(function()
        local now = os.clock()
        if now < cooldownUntil then
            status.Text = "Wait " .. tostring(math.ceil(cooldownUntil - now)) .. "s…"
            return
        end
        cooldownUntil = now + (Vine.Config.CooldownSeconds or 5)
        btn.Text = "Checking…"
        status.Text = ""
        task.spawn(function()
            local ok, err = Vine.Load(box.Text, onLoaded)
            if ok then
                gui:Destroy()
            else
                if err == "rate_limited" then
                    cooldownUntil = os.clock() + (Vine.Config.RateLimitedCooldown or 30)
                end
                btn.Text = "Unlock"
                status.Text = friendlyError(err)
            end
        end)
    end)

    gui.Parent = (gethui and gethui() or game.CoreGui)
    return gui
end

return Vine
