local G=game:GetService("\067\104\097\110\103\101\072\105\115\116\111\114\121\083\101\114\118\105\099\101")local N=game:GetService("\085\115\101\114\073\110\112\117\116\083\101\114\118\105\099\101")local x=game:GetService("\083\101\108\101\099\116\105\111\110")local n=game:GetService("\083\101\114\118\101\114\083\116\111\114\097\103\101")local h=n.SocketPlugin:FindFirstChild("\085\116\105\108\115")local O=require(h.Logger)local X=require(h.Janitor)local f=require(h.InstanceUtil)local j=require(h.RaycastUtil)local w=1000 local k="\087\105\108\108 \100\114\097\119 \108\105\110\101\115 \098\101\116\119\101\101\110 \116\104\101 \100\101\102\105\110\101\100 \112\111\105\110\116\115\046\n"k=("\037\115\n\037\115"):format(k,"\080\108\097\099\101 \075\101\121\098\105\110\100\058 \075\101\121\112\114\101\115\115 \116\111 \100\101\102\105\110\101 \097 \112\111\105\110\116")k=("\037\115\n\037\115"):format(k,"\080\097\114\116 \083\105\122\101\058 \084\104\101 \119\105\100\116\104 \111\102 \116\104\101 \112\097\114\116 \108\105\110\101 \099\114\101\097\116\101\100")k=("\037\115\n\037\115"):format(k,"\082\101\099\117\114\115\101\058 \073\102 \116\114\117\101\044 \100\114\097\119\115 \108\105\110\101\115 \098\101\116\119\101\101\110 \101\118\101\114\121 \112\111\105\110\116\046 \073\102 \102\097\108\115\101\044 \100\114\097\119\115 \112\111\105\110\116\115 \098\101\116\119\101\101\110 \101\097\099\104 \112\111\105\110\116 \105\110 \116\104\101 \111\114\100\101\114 \116\104\101\121 \119\101\114\101 \112\108\097\099\101\100")local function I(G)local N=Enum.KeyCode[G]and true or false if not N then return("\073\110\118\097\108\105\100 \075\101\121\067\111\100\101 \037\113"):format(G)end end local q={Group="\067\111\114\101";Name="\068\111\116 \084\111 \068\111\116",Icon="\240\159\140\160";Description=k;Fields={{Name="\067\111\110\102\105\114\109 \075\101\121\098\105\110\100";Type="\115\116\114\105\110\103";IsRequired=true,Validator=I};{Name="\080\108\097\099\101 \075\101\121\098\105\110\100",Type="\115\116\114\105\110\103";IsRequired=true,Validator=I};{Name="\080\097\114\116 \083\105\122\101",Type="\110\117\109\098\101\114",IsRequired=true};{Name="\080\097\114\116 \067\111\108\111\114",Type="\067\111\108\111\114\051";IsRequired=true};{Name="\082\101\099\117\114\115\101";Type="\098\111\111\108\101\097\110",IsRequired=true}},State={FieldValues={["\067\111\110\102\105\114\109 \075\101\121\098\105\110\100"]="\089",["\080\108\097\099\101 \075\101\121\098\105\110\100"]="\084",["\080\097\114\116 \083\105\122\101"]=.2;["\080\097\114\116 \067\111\108\111\114"]=Color3.fromRGB(255,255,255);Recurse=true},IsRunning=false}}local function M(G)local N=Instance.new("\070\111\108\100\101\114")N.Name="\068\114\097\119\076\105\110\101\115\078\111\100\101\115"f:IntroduceInstance(N,true)G.State.NodeDirectory=N end local function K(G)local N=j:RaycastMouse(w,nil,true)if not N then return end local x=N.Position local n=Instance.new("\080\097\114\116")n.Size=Vector3.new(1,1,1)n.Shape=Enum.PartType.Ball n.Name=("\037\100"):format(#G.State.NodeDirectory:GetChildren()+1)n.Position=x n.Parent=G.State.NodeDirectory local h=Instance.new("\072\105\103\104\108\105\103\104\116")h.Adornee=n h.FillTransparency=.3 h.Parent=n local O=Instance.new("\066\105\108\108\098\111\097\114\100\071\117\105")O.Size=UDim2.new(1,25,1,25)O.AlwaysOnTop=true O.Parent=n local X=Instance.new("\084\101\120\116\076\097\098\101\108")X.Size=UDim2.fromScale(1,1)X.BackgroundTransparency=1 X.TextColor3=Color3.fromRGB(255,255,255)X.TextScaled=true X.Text=n.Name X.Parent=O local f=Instance.new("\085\073\083\116\114\111\107\101")f.Thickness=3 f.Parent=X G.RunJanitor:Add(n)end local function L(G)local N=G.State.FieldValues["\080\097\114\116 \083\105\122\101"]local x=G.State.FieldValues["\080\097\114\116 \067\111\108\111\114"]local n=G.State.FieldValues.Recurse local h=G.State.NodeDirectory local function O(G,n)local h=Instance.new("\080\097\114\116")h.Name="\080\097\114\116"h.Anchored=true h.CanCollide=false h.Color=x local O=n-G h.CFrame=CFrame.new(G,n)h.Position=G+O/2 h.Size=Vector3.new(N,N,O.Magnitude)return h end local X=h:GetChildren()local f={}if n then local G=#X for N=1,G,1 do local x=X[N]for G=N+1,G,1 do local N=X[G]if x~=N then table.insert(f,O(x.Position,N.Position))end end end else for G=1,#X-1,1 do local N=X[G]local x=X[G+1]table.insert(f,O(N.Position,x.Position))end end local j=Instance.new("\070\111\108\100\101\114")j.Name="\076\105\110\101\115"j.Parent=game.Workspace for G,N in pairs(f)do N.Parent=j end for G,N in pairs(X)do N.Parent=nil end end local function D(G)f:ClearInstance(G.State.NodeDirectory,true)end q.Function=function(x,n)x:ToggleIsRunning()local h=x:IsRunning()local X=x.State.FieldValues["\067\111\110\102\105\114\109 \075\101\121\098\105\110\100"]local f=x.State.FieldValues["\080\108\097\099\101 \075\101\121\098\105\110\100"]if h then local n=G:TryBeginRecording("\068\114\097\119\076\105\110\101\115 \073\110\105\116")M(x)if n then G:FinishRecording(n,Enum.FinishRecordingOperation.Commit)else O:Warn("\082\101\099\111\114\100\105\110\103 \070\097\105\108\101\100")end x.RunJanitor:Add(N.InputBegan:Connect(function(N,n)if n then return end local h=N.KeyCode.Name==f if h then local N=G:TryBeginRecording("\068\114\097\119\076\105\110\101\115 \080\108\097\099\101")K(x)if N then G:FinishRecording(N,Enum.FinishRecordingOperation.Commit)else O:Warn("\082\101\099\111\114\100\105\110\103 \070\097\105\108\101\100")end return end local j=N.KeyCode.Name==X if j then local N=G:TryBeginRecording("\068\114\097\119\076\105\110\101\115 \068\114\097\119")L(x)if N then G:FinishRecording(N,Enum.FinishRecordingOperation.Commit)else O:Warn("\082\101\099\111\114\100\105\110\103 \070\097\105\108\101\100")end return end end))else D(x)end end q.BindToClose=function(G,N)if G:IsRunning()then G:ToggleIsRunning()D(G)end end return q
---
---DrawLines
---

--------------------------------------------------
-- Types
-- ...

--------------------------------------------------
-- Dependencies
local ChangeHistoryService = game:GetService("ChangeHistoryService") ---@type ChangeHistoryService
local UserInputService = game:GetService("UserInputService") ---@type UserInputService
local Selection = game:GetService("Selection") ---@type Selection
local ServerStorage = game:GetService("ServerStorage") ---@type ServerStorage
local Utils = ServerStorage.SocketPlugin:FindFirstChild("Utils")
local Logger = require(Utils.Logger) ---@type Logger
local Janitor = require(Utils.Janitor) ---@type Janitor
local InstanceUtil = require(Utils.InstanceUtil) ---@type InstanceUtil
local RaycastUtil = require(Utils.RaycastUtil) ---@type RaycastUtil

--------------------------------------------------
-- Constants
local RAYCAST_LENGTH = 1000

--------------------------------------------------
-- Members
local description = "Will draw lines between the defined points.\n"
description = ("%s\n%s"):format(description, "Place Keybind: Keypress to define a point")
description = ("%s\n%s"):format(description, "Part Size: The width of the part line created")
description = ("%s\n%s"):format(
    description,
    "Recurse: If true, draws lines between every point. If false, draws points between each point in the order they were placed"
)

---@param keyCodeName string
---@return boolean, string
local function keybindValidator(keyCodeName)
    local isValidKeyCodeName = Enum.KeyCode[keyCodeName] and true or false
    if not isValidKeyCodeName then
        return ("Invalid KeyCode %q"):format(keyCodeName)
    end
end

---@type MacroDefinition
local macroDefinition = {
    Group = "Core", ---@type string
    Name = "Dot To Dot",
    Icon = "🌠",
    Description = description, ---@type string
    Fields = {
        {
            Name = "Confirm Keybind",
            Type = "string",
            IsRequired = true,
            Validator = keybindValidator,
        },
        {
            Name = "Place Keybind",
            Type = "string",
            IsRequired = true,
            Validator = keybindValidator,
        },
        {
            Name = "Part Size",
            Type = "number",
            IsRequired = true,
        },
        {
            Name = "Part Color",
            Type = "Color3",
            IsRequired = true,
        },
        {
            Name = "Recurse",
            Type = "boolean",
            IsRequired = true,
        },
    }, ---@type MacroField[]
    State = {
        FieldValues = {
            ["Confirm Keybind"] = "Y",
            ["Place Keybind"] = "T",
            ["Part Size"] = 0.2,
            ["Part Color"] = Color3.fromRGB(255, 255, 255),
            ["Recurse"] = true,
        },
        IsRunning = false,
    }, ---@type MacroState
}

--================================================================================================================================================
--===
--================================================================================================================================================

---@param macro MacroDefinition
local function init(macro)
    -- Create node directory
    local nodeDirectory = Instance.new("Folder") ---@type Folder
    nodeDirectory.Name = "DrawLinesNodes"
    InstanceUtil:IntroduceInstance(nodeDirectory, true)
    macro.State.NodeDirectory = nodeDirectory
end

---@param macro MacroDefinition
local function placeNode(macro)
    -- Get position to place
    local raycastResult = RaycastUtil:RaycastMouse(RAYCAST_LENGTH, nil, true)
    if not raycastResult then
        return
    end
    local position = raycastResult.Position

    -- Create node
    local node = Instance.new("Part") ---@type Part
    node.Size = Vector3.new(1, 1, 1)
    node.Shape = Enum.PartType.Ball
    node.Name = ("%d"):format(#macro.State.NodeDirectory:GetChildren() + 1)
    node.Position = position
    node.Parent = macro.State.NodeDirectory

    local highlight = Instance.new("Highlight") ---@type Highlight
    highlight.Adornee = node
    highlight.FillTransparency = 0.3
    highlight.Parent = node

    local billboardGui = Instance.new("BillboardGui") ---@type BillboardGui
    billboardGui.Size = UDim2.new(1, 25, 1, 25)
    billboardGui.AlwaysOnTop = true
    billboardGui.Parent = node

    local textLabel = Instance.new("TextLabel") ---@type TextLabel
    textLabel.Size = UDim2.fromScale(1, 1)
    textLabel.BackgroundTransparency = 1
    textLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
    textLabel.TextScaled = true
    textLabel.Text = node.Name
    textLabel.Parent = billboardGui

    local uiStroke = Instance.new("UIStroke") ---@type UIStroke
    uiStroke.Thickness = 3
    uiStroke.Parent = textLabel

    macro.RunJanitor:Add(node)
end

---@param macro MacroDefinition
local function drawLines(macro)
    -- Read state
    local partSize = macro.State.FieldValues["Part Size"]
    local partColor = macro.State.FieldValues["Part Color"]
    local doRecurse = macro.State.FieldValues.Recurse
    local nodeDirectory = macro.State.NodeDirectory

    ---@param pos0 Vector3
    ---@param pos1 Vector3
    ---@return Part
    local function createLine(pos0, pos1)
        -- Create part
        local part = Instance.new("Part") ---@type Part
        part.Name = "Part"
        part.Anchored = true
        part.CanCollide = false
        part.Color = partColor

        -- Calculations
        local vector01 = pos1 - pos0

        part.CFrame = CFrame.new(pos0, pos1) -- Easy way to get the orientation we need
        part.Position = pos0 + vector01 / 2
        part.Size = Vector3.new(partSize, partSize, vector01.Magnitude)

        return part
    end

    -- Get nodes
    local nodes = nodeDirectory:GetChildren()
    local lines = {}

    -- Create lines
    if doRecurse then
        local totalNodes = #nodes
        for i = 1, totalNodes do
            local node0 = nodes[i]
            for j = i + 1, totalNodes do
                local node1 = nodes[j]
                if node0 ~= node1 then
                    table.insert(lines, createLine(node0.Position, node1.Position))
                end
            end
        end
    else
        for i = 1, #nodes - 1 do
            local node0 = nodes[i]
            local node1 = nodes[i + 1]
            table.insert(lines, createLine(node0.Position, node1.Position))
        end
    end

    -- Finalise
    local linesFolder = Instance.new("Folder") ---@type Folder
    linesFolder.Name = "Lines"
    linesFolder.Parent = game.Workspace
    for _, line in pairs(lines) do
        line.Parent = linesFolder
    end
    for _, node in pairs(nodes) do
        node.Parent = nil
    end
end

---@param macro MacroDefinition
local function stopRunning(macro)
    InstanceUtil:ClearInstance(macro.State.NodeDirectory, true)
end

--================================================================================================================================================
--===
--================================================================================================================================================

---@param macro MacroDefinition
---@param plugin Plugin
macroDefinition.Function = function(macro, plugin)
    macro:ToggleIsRunning()

    -- Read state
    local isRunning = macro:IsRunning()
    local confirmKeybind = macro.State.FieldValues["Confirm Keybind"]
    local placeKeybind = macro.State.FieldValues["Place Keybind"]

    if isRunning then
        local recordingInit = ChangeHistoryService:TryBeginRecording("DrawLines Init")
        init(macro)
        if recordingInit then
            ChangeHistoryService:FinishRecording(recordingInit, Enum.FinishRecordingOperation.Commit)
        else
            Logger:Warn("Recording Failed")
        end

        -- Listen to User Input
        macro.RunJanitor:Add(UserInputService.InputBegan:Connect(function(inputObject, gameProcessedEvent)
            -- RETURN: gameProcessedEvent
            if gameProcessedEvent then
                return
            end

            -- Place
            local isPlaceKeybind = inputObject.KeyCode.Name == placeKeybind
            if isPlaceKeybind then
                local recordingPlace = ChangeHistoryService:TryBeginRecording("DrawLines Place")
                placeNode(macro)
                if recordingPlace then
                    ChangeHistoryService:FinishRecording(recordingPlace, Enum.FinishRecordingOperation.Commit)
                else
                    Logger:Warn("Recording Failed")
                end
                return
            end

            -- Place
            local isConfirmKeybind = inputObject.KeyCode.Name == confirmKeybind
            if isConfirmKeybind then
                local recordingDraw = ChangeHistoryService:TryBeginRecording("DrawLines Draw")
                drawLines(macro)
                if recordingDraw then
                    ChangeHistoryService:FinishRecording(recordingDraw, Enum.FinishRecordingOperation.Commit)
                else
                    Logger:Warn("Recording Failed")
                end
                return
            end
        end))
    else
        stopRunning(macro)
    end
end

---@param macro MacroDefinition
---@param plugin Plugin
macroDefinition.BindToClose = function(macro, plugin)
    if macro:IsRunning() then
        macro:ToggleIsRunning()
        stopRunning(macro)
    end
end

return macroDefinition
