local a=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 m=N.SocketPlugin:FindFirstChild("\085\116\105\108\115")local H=require(m.Logger)local p="\087\105\108\108 \115\101\108\101\099\116 \097\108\108 \105\110\115\116\097\110\099\101\115 \116\104\097\116 \097\114\101 \100\101\115\099\101\110\100\097\110\116\115 \111\102 \116\104\101 \115\101\108\101\099\116\101\100 \105\110\115\116\097\110\099\101\044 \116\104\097\116 \109\101\101\116 \111\117\114 \112\097\114\097\109\101\116\101\114\115 \100\101\102\105\110\101\100 \105\110 \111\117\114 \102\105\101\108\100\115\046\n"p=("\037\115\n\037\115"):format(p,"\078\097\109\101\058 \084\104\101 \046\078\097\109\101 \111\102 \097\110 \101\108\105\103\105\098\108\101 \105\110\115\116\097\110\099\101")p=("\037\115\n\037\115"):format(p,"\067\108\097\115\115\078\097\109\101\058 \073\110\115\116\097\110\099\101 \105\115 \101\108\105\103\105\098\108\101 \105\102 \058\073\115\065\040\099\108\097\115\115\078\097\109\101\041 \061\061 \116\114\117\101")p=("\037\115\n\037\115"):format(p,"\067\104\105\108\100 \078\097\109\101\058 \073\110\115\116\097\110\099\101 \105\115 \101\108\105\103\105\098\108\101 \105\102 \058\100\105\100\070\105\110\100\070\105\114\115\116\067\104\105\108\100\040\099\104\105\108\100\078\097\109\101\044 \114\101\099\117\114\115\101\041 \061\061 \116\114\117\101")p=("\037\115\n\037\115"):format(p,"\082\101\099\117\114\115\101 \067\104\105\108\100\114\101\110\058 \068\101\102\105\110\101\115 \096\114\101\099\117\114\115\101\096 \098\111\111\108\101\097\110 \118\097\108\117\101 \102\111\114 \096\067\104\105\108\100 \078\097\109\101\096")p=("\037\115\n\037\115"):format(p,"\069\120\097\099\116 \077\097\116\099\104\058 \073\102 \116\114\117\101\044 \105\110\115\116\097\110\099\101 \105\115 \101\108\105\103\105\098\108\101 \105\102 \101\046\103\046\044 \105\116\115 \046\078\097\109\101 \061\061 \096\078\097\109\101\096\046 \073\102 \102\097\108\115\101\044 \105\110\115\116\097\110\099\101 \105\115 \101\108\105\103\105\098\108\101 \105\102 \111\110\108\121 \112\097\114\116 \111\102 \116\104\101 \101\046\103\046\044 \046\078\097\109\101 \109\097\116\099\104\101\115 \096\078\097\109\101\096")p=("\037\115\n\037\115"):format(p,"\067\097\115\101 \083\101\110\115\105\116\105\118\101\058 \073\102 \102\097\108\115\101\044 \097\108\108 \101\108\105\103\105\098\105\108\105\116\121 \108\111\103\105\099 \105\103\110\111\114\101\115 \108\111\119\101\114 \099\097\115\101\047\117\112\112\101\114 \099\097\115\101")p=("\037\115\n\037\115"):format(p,"\077\097\120 \065\109\111\117\110\116\058 \087\105\108\108 \110\111\116 \115\101\108\101\099\116 \097\110\121 \105\110\115\116\097\110\099\101\115 \109\111\114\101 \116\104\097\110 \116\104\105\115")local g={Group="\067\111\114\101",Name="\083\101\108\101\099\116 \073\110\115\116\097\110\099\101\115",Icon="\226\157\135\239\184\143";Description=p,EnableAutomaticUndo=true;Fields={{Name="\078\097\109\101";Type="\115\116\114\105\110\103"};{Name="\067\108\097\115\115\078\097\109\101",Type="\115\116\114\105\110\103"};{Name="\067\104\105\108\100 \078\097\109\101";Type="\115\116\114\105\110\103"},{Name="\082\101\099\117\114\115\101 \067\104\105\108\100\114\101\110",Type="\098\111\111\108\101\097\110"};{Name="\065\116\116\114\105\098\117\116\101 \078\097\109\101";Type="\115\116\114\105\110\103"};{Name="\069\120\097\099\116 \077\097\116\099\104";Type="\098\111\111\108\101\097\110"},{Name="\067\097\115\101 \083\101\110\115\105\116\105\118\101";Type="\098\111\111\108\101\097\110"},{Name="\077\097\120 \065\109\111\117\110\116";Type="\110\117\109\098\101\114"}};State={FieldValues={["\082\101\099\117\114\115\101 \067\104\105\108\100\114\101\110"]=false,["\069\120\097\099\116 \077\097\116\099\104"]=true,["\067\097\115\101 \083\101\110\115\105\116\105\118\101"]=true}}}local function k(a,N,m,H,p)local g=m and a:GetDescendants()or a:GetChildren()for a,m in pairs(g)do local g=p and m.Name or m.Name:upper()local k=H and g==N or not H and(string.find(g,N)and true)or false if k then return true end end return false end local function n(a,N,m,H)local p=a:GetAttributes()for a,p in pairs(p)do a=H and a or a:upper()local g=m and a==N or not m and(string.find(a,N)and true)if g then return true end end return false end local function W(a,N,m,H,p,g,W,S,F)local A={}N=N and(S and N or N:upper())H=H and(S and H or H:upper())p=p and(S and p or p:upper())for a,B in pairs(a)do for a,B in pairs(B:GetDescendants())do local q=S and B.Name or B.Name:upper()local h=not N or W and q==N or not W and(string.find(q,N)and true)or false local K=not H or k(B,H,g,W,S)local I=not m or B:IsA(m)local x=not p or n(B,p,W,S)if h and(K and(I and x))then table.insert(A,B)end if#A==F then return A end end end return A end g.Function=function(N,m)local p=a:Get()if#p==0 then H:MacroWarn(N,"\080\108\101\097\115\101 \115\101\108\101\099\116 \097\116\108\101\097\115\116 \049 \105\110\115\116\097\110\099\101 \116\111 \115\101\097\114\099\104 \105\110")return end local g=N:GetFieldValue("\078\097\109\101")local k=N:GetFieldValue("\067\108\097\115\115\078\097\109\101")local n=N:GetFieldValue("\067\104\105\108\100 \078\097\109\101")local S=N:GetFieldValue("\065\116\116\114\105\098\117\116\101 \078\097\109\101")local F=N:GetFieldValue("\082\101\099\117\114\115\101 \067\104\105\108\100\114\101\110")and true or false local A=N:GetFieldValue("\069\120\097\099\116 \077\097\116\099\104")and true or false local B=N:GetFieldValue("\067\097\115\101 \083\101\110\115\105\116\105\118\101")and true or false local q=N:GetFieldValue("\077\097\120 \065\109\111\117\110\116")local h=W(p,g,k,n,S,F,A,B,q)a:Set(h)a:Set(h)H:MacroInfo(N,("\083\101\108\101\099\116\101\100 \037\100 \101\108\105\103\105\098\108\101 \105\110\115\116\097\110\099\101\115"):format(#h))end return g
---
---SelectInstances
---

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

--------------------------------------------------
-- Dependencies
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

--------------------------------------------------
-- Constants
-- ...

--------------------------------------------------
-- Members
local description =
    "Will select all instances that are descendants of the selected instance, that meet our parameters defined in our fields.\n"
description = ("%s\n%s"):format(description, "Name: The .Name of an eligible instance")
description = ("%s\n%s"):format(description, "ClassName: Instance is eligible if :IsA(className) == true")
description = ("%s\n%s"):format(description, "Child Name: Instance is eligible if :didFindFirstChild(childName, recurse) == true")
description = ("%s\n%s"):format(description, "Recurse Children: Defines `recurse` boolean value for `Child Name`")
description = ("%s\n%s"):format(
    description,
    "Exact Match: If true, instance is eligible if e.g., its .Name == `Name`. If false, instance is eligible if only part of the e.g., .Name matches `Name`"
)
description = ("%s\n%s"):format(description, "Case Sensitive: If false, all eligibility logic ignores lower case/upper case")
description = ("%s\n%s"):format(description, "Max Amount: Will not select any instances more than this")

---@type MacroDefinition
local macroDefinition = {
    Group = "Core", ---@type string
    Name = "Select Instances",
    Icon = "❇️",
    Description = description, ---@type string
    EnableAutomaticUndo = true, ---@type boolean
    Fields = {
        {
            Name = "Name",
            Type = "string",
        },
        {
            Name = "ClassName",
            Type = "string",
        },
        {
            Name = "Child Name",
            Type = "string",
        },
        {
            Name = "Recurse Children",
            Type = "boolean",
        },
        {
            Name = "Attribute Name",
            Type = "string",
        },
        {
            Name = "Exact Match",
            Type = "boolean",
        },
        {
            Name = "Case Sensitive",
            Type = "boolean",
        },
        {
            Name = "Max Amount",
            Type = "number",
        },
    },
    State = {
        FieldValues = {
            ["Recurse Children"] = false,
            ["Exact Match"] = true,
            ["Case Sensitive"] = true,
        },
    },
}

---@param parent Instance
---@param name string
---@param recurseChildren boolean
---@param exactMatch boolean
---@param caseSensitive boolean
---@return boolean
local function didFindFirstChild(parent, name, recurseChildren, exactMatch, caseSensitive)
    local options = recurseChildren and parent:GetDescendants() or parent:GetChildren()
    for _, option in pairs(options) do
        local optionName = caseSensitive and option.Name or option.Name:upper()
        local doesMatch = (exactMatch and optionName == name) or (not exactMatch and string.find(optionName, name) and true) or false
        if doesMatch then
            return true
        end
    end

    return false
end

---@param instance Instance
---@param name string
---@param exactMatch boolean
---@param caseSensitive boolean
---@return boolean
local function hasAttribute(instance, name, exactMatch, caseSensitive)
    local attributes = instance:GetAttributes() ---@type table<string, any>
    for attributeName, _ in pairs(attributes) do
        attributeName = caseSensitive and attributeName or attributeName:upper()
        local doesMatch = (exactMatch and attributeName == name) or (not exactMatch and string.find(attributeName, name) and true)
        if doesMatch then
            return true
        end
    end

    return false
end

---@param instances Instance[]
---@param name string|nil
---@param className string|nil
---@param childName string|nil
---@param attributeName string|nil
---@param recurseChildren boolean
---@param exactMatch boolean
---@param caseSensitive boolean
---@param maxAmount number|nil
---@return Instances[]
local function search(instances, name, className, childName, attributeName, recurseChildren, exactMatch, caseSensitive, maxAmount)
    local results = {}

    -- Transform data
    name = name and (caseSensitive and name or name:upper())
    childName = childName and (caseSensitive and childName or childName:upper())
    attributeName = attributeName and (caseSensitive and attributeName or attributeName:upper())

    -- Loop instance descendants
    for _, instance in pairs(instances) do
        for _, descendant in pairs(instance:GetDescendants()) do
            -- Transform data
            local descendantName = caseSensitive and descendant.Name or descendant.Name:upper()

            -- Check if an eligible instance
            local hasMatchingName = not name
                or (exactMatch and descendantName == name)
                or (not exactMatch and string.find(descendantName, name) and true)
                or false
            local hasMatchingChildOfName = not childName
                or didFindFirstChild(descendant, childName, recurseChildren, exactMatch, caseSensitive)
            local hasMatchingClassName = not className or descendant:IsA(className)
            local hasMatchingAttributeName = not attributeName or hasAttribute(descendant, attributeName, exactMatch, caseSensitive)

            if hasMatchingName and hasMatchingChildOfName and hasMatchingClassName and hasMatchingAttributeName then
                table.insert(results, descendant)
            end

            -- Return if we met the cap
            if #results == maxAmount then
                return results
            end
        end
    end

    return results
end

---@param macro MacroDefinition
---@param plugin Plugin
macroDefinition.Function = function(macro, plugin)
    -- Get Selection
    local selectedInstances = Selection:Get()
    if #selectedInstances == 0 then
        Logger:MacroWarn(macro, "Please select atleast 1 instance to search in")
        return
    end

    -- Get State
    local name = macro:GetFieldValue("Name")
    local className = macro:GetFieldValue("ClassName")
    local childName = macro:GetFieldValue("Child Name")
    local attributeName = macro:GetFieldValue("Attribute Name")
    local recurseChildren = macro:GetFieldValue("Recurse Children") and true or false
    local exactMatch = macro:GetFieldValue("Exact Match") and true or false
    local caseSensitive = macro:GetFieldValue("Case Sensitive") and true or false
    local maxAmount = macro:GetFieldValue("Max Amount")

    -- Search
    local results =
        search(selectedInstances, name, className, childName, attributeName, recurseChildren, exactMatch, caseSensitive, maxAmount)

    -- Select
    Selection:Set(results)
    Selection:Set(results)

    -- Log
    Logger:MacroInfo(macro, ("Selected %d eligible instances"):format(#results))
end

return macroDefinition
