local W=game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")local w=game:GetService("\080\108\097\121\101\114\115")local Z=W:WaitForChild("\099\111\109\109\111\110")local d=W:WaitForChild("\108\105\098")local a=require(d:WaitForChild("\083\105\103\110\097\108"))local p=require(Z:WaitForChild("\069\113\117\105\112\109\101\110\116\066\101\104\097\118\105\111\114\115"))local o=require(Z:WaitForChild("\065\115\115\101\116\067\097\116\097\103\111\114\105\101\115"))local c=require(Z:WaitForChild("\083\101\108\101\099\116\111\114\115"))local h=p.EquipmentBehavior local E={}local function g(W,w)local Z,d={},{}local a,p={},{}for W,w in pairs(W)do a[w]=true end for W,w in pairs(w)do p[w]=true if not a[w]then table.insert(Z,w)end end for W,w in pairs(W)do if not p[w]then table.insert(d,w)end end return Z,d end function E.equipAsset(Z,W,w)local d=Z.equipmentBehaviors[W]if not d then return end local a=d[w]if a then return end local p=h.new(Z.pzCore,w)p:equipped(W)d[w]=p Z.equippedAsset:fire(W,w,p)return p end function E.unequipAsset(Z,W,w)local d=Z.equipmentBehaviors[W]if not d then return end local a=d[w]if a then Z.unequippingAsset:fire(W,w,a)if a then a:unequipped()a:destroy()end d[w]=nil end end function E.clearEquipped(w,W)for Z,d in pairs(w.equipmentBehaviors[W]or{})do w:unequipAsset(W,Z)end w.equipmentBehaviors[W]={}end function E.getBehaviors(w,W)return w.equipmentBehaviors[W]end function E.playerCharacterSpawned(Z,W,w)Z:clearEquipped(W)local d=c.getEquipped(Z.store:getState(),W)or{}for w,d in pairs(d)do for w,d in pairs(d)do Z:equipAsset(W,d)end end end function E.playerAdded(w,W)w.equipmentBehaviors[W]={}w.characterEvents[W].adding=W.CharacterAdded:connect(function(Z)Z:WaitForChild("\072\117\109\097\110\111\105\100")Z:WaitForChild("\084\111\114\115\111")Z:WaitForChild("\072\101\097\100")Z:WaitForChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")w:playerCharacterSpawned(W,Z)end)w.characterEvents[W].removing=W.CharacterRemoving:connect(function()w:clearEquipped(W)end)if W.Character then w:playerCharacterSpawned(W,W.Character)end end function E.new(W,Z)local d=setmetatable({},{__index=E})d.pzCore=W d.store=Z d.equipmentBehaviors={}d.equippedAsset=a.new()d.unequippingAsset=a.new()d.characterEvents={}w.PlayerAdded:connect(function(W)d.characterEvents[W]={adding=nil,removing=nil}d:playerAdded(W)end)w.PlayerRemoving:connect(function(W)for W,w in pairs(d.characterEvents[W])do w:Disconnect()end d.equipmentBehaviors[W]=nil d.characterEvents[W]=nil end)for W,w in pairs(w:GetPlayers())do d.characterEvents[w]={adding=nil,removing=nil}d:playerAdded(w)end d.store.changed:connect(function(W,Z)for w,a in pairs(w:GetPlayers())do for w,p in pairs(o.byId)do local o=(c.getEquipped(Z,a)or{})[w]or{}local h=(c.getEquipped(W,a)or{})[w]or{}local E,H=g(o,h)for W,w in pairs(H)do d:unequipAsset(a,w)end for W,w in pairs(E)do d:equipAsset(a,w)end end end end)return d end return E
-- renders yours and other egs equipment and handles their local usage effects

-- creates equipment object which handles behavior for the equipped asset.

-- things that require equipment object to be created
--  asset being equipped
--  player spawning

-- things that require equipment object to be destroyed
--  asset being unequipped
--  player being destroyed

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")

local common = ReplicatedStorage:WaitForChild("common")
local lib = ReplicatedStorage:WaitForChild("lib")

local Signal = require(lib:WaitForChild("Signal"))

local EquipmentBehaviors = require(common:WaitForChild("EquipmentBehaviors"))
local AssetCatagories = require(common:WaitForChild("AssetCatagories"))
local Selectors = require(common:WaitForChild("Selectors"))

local EquipmentBehavior = EquipmentBehaviors.EquipmentBehavior

local EquipmentReconciler = {}

local function getDiffs(t1,t2)
    local added, removed = {}, {}
    local t1Set, t2Set = {}, {}

    -- create t1 hash set
    for _,v in pairs(t1) do
        t1Set[v] = true
    end
    -- create t2 hash set and check against t1 set to find new elements
    for _,v in pairs(t2) do
        t2Set[v] = true
        -- do this here to cut out an extra for loop
        if not t1Set[v] then
            table.insert(added,v)
        end
    end
    -- check t1 against t2 set to find removed
    for _,v in pairs(t1) do
        if not t2Set[v] then
            table.insert(removed,v)
        end
    end

    return added,removed
end

function EquipmentReconciler:equipAsset(player, assetId)
    local playerEquipmentBehaviors = self.equipmentBehaviors[player]
    if not playerEquipmentBehaviors then return end
    local equippedBehavior = playerEquipmentBehaviors[assetId]
    if equippedBehavior then return end
    local equipmentBehavior = EquipmentBehavior.new(self.pzCore, assetId)
    equipmentBehavior:equipped(player)

    playerEquipmentBehaviors[assetId] = equipmentBehavior

    self.equippedAsset:fire(player, assetId, equipmentBehavior)

    return equipmentBehavior
end

function EquipmentReconciler:unequipAsset(player,assetId)
    local playerEquipmentBehaviors = self.equipmentBehaviors[player]
    if not playerEquipmentBehaviors then return end
    local equippedBehavior = playerEquipmentBehaviors[assetId]

    if equippedBehavior then
        self.unequippingAsset:fire(player, assetId, equippedBehavior)
        if equippedBehavior then
            equippedBehavior:unequipped()
            equippedBehavior:destroy()
        end
        playerEquipmentBehaviors[assetId] = nil
    end
end

function EquipmentReconciler:clearEquipped(player)
    for assetId,_ in pairs(self.equipmentBehaviors[player] or {}) do
        self:unequipAsset(player,assetId)
    end
    self.equipmentBehaviors[player] = {}
end

function EquipmentReconciler:getBehaviors(player)
    return self.equipmentBehaviors[player]
end


function EquipmentReconciler:playerCharacterSpawned(player, char)
    self:clearEquipped(player) -- clear just in case equipment wasnt removed for some reason
    -- recreate and bind equipment for this player

    local equipment = Selectors.getEquipped(self.store:getState(), player) or {}

    for _, cataEquipped in pairs(equipment) do
        for _, assetId in pairs(cataEquipped) do
            self:equipAsset(player, assetId)
        end
    end
end

function EquipmentReconciler:playerAdded(player)
    self.equipmentBehaviors[player] = {}

    self.characterEvents[player].adding = player.CharacterAdded:connect(function(char)
        char:WaitForChild("Humanoid")
        char:WaitForChild("Torso")
        char:WaitForChild("Head")
        char:WaitForChild("HumanoidRootPart")
        self:playerCharacterSpawned(player, char)
    end)
    self.characterEvents[player].removing = player.CharacterRemoving:connect(function()
        self:clearEquipped(player)
    end)

    if player.Character then
        self:playerCharacterSpawned(player, player.Character)
    end
end

function EquipmentReconciler.new(pzCore,store)
    local self = setmetatable({},{__index = EquipmentReconciler})
    self.pzCore = pzCore
    self.store = store
    self.equipmentBehaviors = {} -- table of behavior tables indexed by players

    self.equippedAsset = Signal.new()
    self.unequippingAsset = Signal.new()
    self.characterEvents = {}

    Players.PlayerAdded:connect(function(player)
        self.characterEvents[player] = {adding = nil, removing = nil}
        self:playerAdded(player)
    end)
    Players.PlayerRemoving:connect(function(player)
        for _,event in pairs(self.characterEvents[player]) do
            event:Disconnect()
        end
        self.equipmentBehaviors[player] = nil
        self.characterEvents[player] = nil
    end)

    -- equip everyone's initial equipment
    for _, player in pairs(Players:GetPlayers()) do
        self.characterEvents[player] = {adding = nil, removing = nil}
        self:playerAdded(player)
    end

    self.store.changed:connect(function(newstate,oldstate)
        -- detect if hats have changed for each player
        for _, player in pairs(Players:GetPlayers()) do
            -- check each catagory for changes
            for cataId, _ in pairs(AssetCatagories.byId) do
                local oldEquipped = (Selectors.getEquipped(oldstate, player) or {})[cataId] or {}
                local newEquipped = (Selectors.getEquipped(newstate, player) or {})[cataId] or {}
                local added,removed = getDiffs(oldEquipped, newEquipped)

                for _,assetId in pairs(removed) do
                    self:unequipAsset(player,assetId)
                end
                for _,assetId in pairs(added) do
                    self:equipAsset(player,assetId)
                end
            end
        end
    end)

    return self
end

return EquipmentReconciler