local IRF2 = InvenRaidFrames2
local SM = LibStub("LibSharedMedia-3.0")
local QuickHealth = LibStub("LibQuickHealth-2.0")

local _G = getfenv(0)
local min = _G.min
local max = _G.max
local type = _G.type
local pairs = _G.pairs
local unpack = _G.unpack
local select = _G.select
local tonumber = _G.tonumber
local tostring = _G.tostring
local strsplit = _G.strsplit
local tinsert = _G.table.insert
local GetTime = _G.GetTime
local UnitExists = _G.UnitExists
local UnitIsUnit = _G.UnitIsUnit
local UnitIsPlayer = _G.UnitIsPlayer
local realUnitHealth = _G.UnitHealth
local realUnitHealthMax = _G.UnitHealthMax
--local UnitHealth = QuickHealth.UnitHealth
--local UnitHealthMax = QuickHealth.UnitHealthMax
local UnitHealth = _G.UnitHealth
local UnitHealthMax = _G.UnitHealthMax
local UnitMana = _G.UnitMana
local UnitManaMax = _G.UnitManaMax
local UnitName = _G.UnitName
local UnitPowerType = _G.UnitPowerType
local UnitGUID = _G.UnitGUID
local UnitIsConnected = _G.UnitIsConnected
local UnitIsAFK = _G.UnitIsAFK
local UnitIsDead = _G.UnitIsDead
local UnitIsGhost = _G.UnitIsGhost
local UnitIsDeadOrGhost = _G.UnitIsDeadOrGhost
local UnitInVehicle = _G.UnitInVehicle
local GetUnitName = _G.GetUnitName

local unitBackdrop = {
	bgFile = "Interface\\Addons\\InvenRaidFrames2\\Texture\\Blink", tile = true, tileSize = 16,
}
local unitOutline = {
	edgeFile = "Interface\\Addons\\InvenRaidFrames2\\Texture\\Blink", edgeSize = 2,
	insets = { left = -2, right = -2, top = -2, bottom = -2 },
}

local unitPowerEvent = { ["UNIT_MANA"] = true, ["UNIT_RAGE"] = true, ["UNIT_FOCUS"] = true, ["UNIT_ENERGY"] = true, ["UNIT_RUNIC_POWER"] = true }
local displayPowerType = { ["MANA"] = true, ["RAGE"] = true, ["FOCUS"] = true, ["ENERGY"] = true, ["RUNIC_POWER"] = true }

local function SetSafeAttribute(self, name, value)
	if self:GetAttribute(name) ~= value then
		self:SetAttribute(name, value)
	end
end

local function SetManyAttributes(self, ...)
	-- 다수의 SetAttribute 메서드 적용 함수
	for i = 1, select('#', ...), 2 do
		SetSafeAttribute(self, select(i, ...))
	end
end

local function objectClear(self)
	self.unit, self.name, self.guid, self.class, self.ownerClass, self.unitType, self.unitID, self.hasAggro, self.vehicleUnit = nil
	self.debuffType, self.enemy, self.onenter, self.targetUnit, self.fullName, self.survivalSkill = nil
	if self.vehicleCheck then
		self.vehicleCheck:Hide()
	end
	if self.highlight then
		self.highlight:Hide()
	end
	if self.heal then
		self.heal:Hide()
		self.heal.icon:Hide()
	end
	if self.raidIcon then
		self.raidIcon:Hide()
		self.raidIcon2:Hide()
	end
	if self.debuffFrame then
		for i = 1, #self.debuffFrame do
			self.debuffFrame[i]:Hide()
		end
	end
	if self.resCastingBar then
		self.resCastingBar:Hide()
	end
	if self.castingBar then
		self.castingBar:Hide()
	end
end

local function objectHealth(self, health, maxHealth)
	-- 유니트의 체력 업데이트
	if self.unit then
		if self.connected ~= UnitIsConnected(self.unit) then
			self.connected = UnitIsConnected(self.unit)
			self:UNIT_NAME_UPDATE()
			if IRF2.db.outline == "체력 저하" then
				self.textFrame:SetBackdropBorderColor(self:GetBackdropColor())
			end
		elseif self.connected then
			if self.vehicleUnit then
				health, maxHealth = max(UnitHealth(self.vehicleUnit), 0), max(UnitHealthMax(self.vehicleUnit), 1)
			else
				health, maxHealth = max(health or UnitHealth(self.unit), 0), max(maxHealth or UnitHealthMax(self.unit), 1)
			end
			self.health:SetMinMaxValues(0, maxHealth)
			self.lostHealth = maxHealth - health
			if IRF2.db.layout.healthDisplayType == "손실 생명력" then
				self.health:SetValue(self.lostHealth)
			else
				self.health:SetValue(health)
			end
			self:UpdateText()
			if IRF2.db.outline == "체력 저하" then
				if not UnitIsDeadOrGhost(self.unit) and self.lostHealth > 0 and (health / maxHealth) < IRF2.db.outlineLowHealth.value then
					self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineLowHealth.color))
				else
					self.textFrame:SetBackdropBorderColor(self:GetBackdropColor())
				end
			end
			IRF2:UpdateUnitHeal(self)
		else
			self:UpdateText()
		end
	end
end

local function objectPower(self)
	-- 유니트의 마나 업데이트
	self.mana:SetMinMaxValues(0, UnitManaMax(self.vehicleUnit or self.unit))
	self.mana:SetValue(UnitMana(self.vehicleUnit or self.unit))
end

local function objectPowerType(self)
	-- 유니트의 마나바에 마나, 기력, 분노, 루닉파워 색상 적용
	self.powerType = select(2, UnitPowerType(self.vehicleUnit or self.unit))
	self.powerType = IRF2.db.powerColor[self.powerType or "*"] and self.powerType or "MANA"
	self.mana:SetStatusBarColor(unpack(IRF2.db.powerColor[self.powerType]))
	for event in pairs(unitPowerEvent) do
		self:UnregisterEvent(event)
	end
	self:RegisterEvent(("UNIT_%s"):format(displayPowerType[self.powerType] and self.powerType or "MANA"))
	objectPower(self)
end

local function GetTextWidth(text)
	if not text or text == "" then
		return 0
	else
		IRF2.tempText:SetText(text)
		return IRF2.tempText:GetWidth()
	end
end

local cutStep

local function CutText(text, width)
	cutStep = text:len()
	while true do
		if cutStep <= 3 or GetTextWidth(text:sub(1, cutStep)) < width then
			return text:sub(1, cutStep)
		end
		cutStep = cutStep - 3
	end
end

local isLostHealth, ctime

local function GetObjectText(self, desc)
	if type(desc) == "number" then
		isLostHealth = true
		if IRF2.db.layout.lostHPred then
			desc = IRF2.db.layout.lostHPshort and (("|cff%s%.1f|r"):format(IRF2.db.layout.lostHPColor, desc / 1000)) or (("|cff%s%d|r"):format(IRF2.db.layout.lostHPColor, desc))
		else
			desc = IRF2.db.layout.lostHPshort and (("%.1f"):format(desc / 1000)) or (("%d"):format(desc))
		end
	else
		isLostHealth = false
	end
	if IRF2.db.layout.showAggro and self.hasAggro then
		if desc then
			if IRF2.db.layout.twoLine then
				if self.survivalSkill then
					ctime = GetTime()
					if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
						return ("|cffff0000▶|r<%s:%d>\n(%s)"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, tostring(desc))
					else
						return ("|cffff0000▶|r<%s>\n(%s)"):format(self.survivalSkill, tostring(desc))
					end
				else
					return ("|cffff0000▶|r%s\n(%s)"):format(CutText(self.cutName, IRF2.db.layout.width - 10 - GetTextWidth("▶")), tostring(desc))
				end
			elseif IRF2.db.layout.replaceLostHP and isLostHealth then
				if self.survivalSkill then
					ctime = GetTime()
					if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
						return ("|cffff0000▶|r<%s:%d>%s"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, desc)
					else
						return ("|cffff0000▶|r<%s>%s"):format(self.survivalSkill, desc)
					end
				else
					return ("|cffff0000▶|r%s"):format(desc)
				end
			elseif self.survivalSkill then
				ctime = GetTime()
				if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
					return ("|cffff0000▶|r<%s:%d>(%s)"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, tostring(desc))
				else
					return ("|cffff0000▶|r<%s>(%s)"):format(self.survivalSkill, tostring(desc))
				end
			else
				return ("|cffff0000▶|r%s(%s)"):format(CutText(self.cutName, IRF2.db.layout.width - 10 - GetTextWidth("▶("..desc..")")), tostring(desc))
			end
		elseif self.survivalSkill then
			ctime = GetTime()
			if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
				return ("|cffff0000▶|r<%s:%d>"):format(self.survivalSkill, self.survivalSkill_endTime - ctime)
			else
				return ("|cffff0000▶|r<%s>"):format(self.survivalSkill)
			end
		else
			return ("|cffff0000▶|r%s"):format(CutText(self.cutName, IRF2.db.layout.width - 10 - GetTextWidth("▶")))
		end
	elseif desc then
		if IRF2.db.layout.twoLine then
			if self.survivalSkill then
				ctime = GetTime()
				if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
					return ("<%s:%d>\n(%s)"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, tostring(desc))
				else
					return ("<%s>\n(%s)"):format(self.survivalSkill, tostring(desc))
				end
			else
				return ("%s\n(%s)"):format(self.cutName, tostring(desc))
			end
		elseif IRF2.db.layout.replaceLostHP and isLostHealth then
			if self.survivalSkill then
				ctime = GetTime()
				if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
					return ("<%s:%d>%s"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, desc)
				else
					return ("<%s>%s"):format(self.survivalSkill, desc)
				end
			else
				return desc
			end
		elseif self.survivalSkill then
			ctime = GetTime()
			if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
				return ("<%s:%d>(%s)"):format(self.survivalSkill, self.survivalSkill_endTime - ctime, tostring(desc))
			else
				return ("<%s>(%s)"):format(self.survivalSkill, tostring(desc))
			end
		else
			return ("%s(%s)"):format(CutText(self.cutName, IRF2.db.layout.width - 10 - GetTextWidth("("..desc..")")), tostring(desc))
		end
	elseif self.survivalSkill then
		ctime = GetTime()
		if self.survivalSkill_endTime and (self.survivalSkill_endTime - ctime) >= 0 then
			return ("<%s:%d>"):format(self.survivalSkill, self.survivalSkill_endTime - ctime)
		else
			return ("<%s>"):format(self.survivalSkill)
		end
	else
		return self.cutName
	end
end

local function objectUpdateOutline(self)
	if UnitExists(self.unit or "") then
		if IRF2.db.outline == "디버프" then
			if self.debuffType then
				return self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.debuffColor[self.debuffType]))
			end
		elseif IRF2.db.outline == "대상" then
			if UnitIsUnit(self.vehicleUnit or self.unit or "", "target") then
				return self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineTarget))
			end
		elseif IRF2.db.outline == "어그로" then
			if self.hasAggro then
				return self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineAggroColor))
			end
		elseif IRF2.db.outline == "마우스 오버" then
			if self.onenter then
				return self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineMouseOver))
			end
		elseif IRF2.db.outline == "체력 저하" then
			if not UnitIsDeadOrGhost(self.unit) and (UnitHealth(self.vehicleUnit or self.unit) / UnitHealthMax(self.vehicleUnit or self.unit)) < IRF2.db.outlineLowHealth.value then
				return self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineLowHealth.color))
			end
		elseif IRF2.db.outline == "전술 목표" then
			local id = GetRaidTargetIndex(self.vehicleUnit or self.unit)
			if id and not IRF2.db.outlineRaidIcon.skip[id] then
				self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineRaidIcon.color))
			else
				self.textFrame:SetBackdropBorderColor(self:GetBackdropColor())
			end
			return
		end
	end
	self.textFrame:SetBackdropBorderColor(self:GetBackdropColor())
end

local function objectUpdateHealthBarColor(self)
	if self.unit and UnitExists(self.unit) then
		if not self.connected or UnitIsDeadOrGhost(self.unit) then
			self.health:SetStatusBarColor(unpack(IRF2.db.layout.offlineColor))
			self.health:SetMinMaxValues(0, 1)
			self.health:SetValue(1)
		elseif self.enemy and IRF2.db.showEnemy then
			self.health:SetStatusBarColor(unpack(IRF2.db.enemyColor))
		elseif self.debuffType and IRF2.db.debuffColor.changeHealthColor then
			self.health:SetStatusBarColor(unpack(IRF2.db.debuffColor[self.debuffType]))
		elseif IRF2.db.layout.classHealth then
			if self.class then
				self.health:SetStatusBarColor(unpack(IRF2.db.classColor[self.class]))
			end
		else
			self.health:SetStatusBarColor(unpack(IRF2.db.layout.healthColor))
		end
	end
end

local function objectText(self)
	-- 접속 유무에 따른 텍스트 및 체력바 색상 적용
	if UnitExists(self.unit or "") then
		if self.connected then
			if UnitIsDead(self.unit) then
				self.textValue = GetObjectText(self, "죽음")
				if IRF2.db.layout.nameRvDeath and self.class then
					self.text:SetTextColor(unpack(IRF2.db.classColor[self.class]))
				else
					self.text:SetTextColor(0.58, 0.58, 0.58)
				end
			elseif UnitIsGhost(self.unit) then
				self.textValue = GetObjectText(self, "유령")
				if IRF2.db.layout.nameRvDeath and self.class then
					self.text:SetTextColor(unpack(IRF2.db.classColor[self.class]))
				else
					self.text:SetTextColor(0.58, 0.58, 0.58)
				end
			else
				if UnitIsAFK(self.unit) then
					self.textValue = GetObjectText(self, "자리")
				elseif (IRF2.db.layout.lostHP == "항상" or (IRF2.db.layout.lostHP == "사정거리 안" and self.inRange) or (IRF2.db.layout.lostHP == "사정거리 밖" and not self.inRange)) and (self.lostHealth or 0) > 0 then
					self.textValue = GetObjectText(self, self.lostHealth)
				else
					self.textValue = GetObjectText(self)
				end
				if self.class then
					if ((IRF2.db.layout.nameRv and not self.inRange) or IRF2.db.layout.className) then
						self.text:SetTextColor(unpack(IRF2.db.classColor[self.ownerClass]))
					else
						self.text:SetTextColor(unpack(IRF2.db.layout.nameColor))
					end
				end
			end
			self.text:SetText(self.textValue)
		else
			self.textValue = GetObjectText(self, "오프")
			self.text:SetText(self.textValue)
			if IRF2.db.layout.nameRvDeath and self.class then
				self.text:SetTextColor(unpack(IRF2.db.classColor[self.class]))
			else
				self.text:SetTextColor(0.58, 0.58, 0.58)
			end
		end
		self:UpdateHealthBarColor()
	end
end

local function objectName(self, isEvent)
	-- 유니트의 이름 업데이트
	if UnitExists(self.unit or "") then
		self.name = UnitName(self.unit)
		self.guid = UnitGUID(self.unit)
		self.cutName = CutText(self.name or "무엇인가", IRF2.db.layout.width - 10)
		self.class = UnitIsPlayer(self.unit) and select(2, UnitClass(self.unit)) or "PET"
		self.ownerClass = self.class
		if self.vehicleUnit then
			self.class = "VEHICLE"
		end
		self:UpdateText()
	end
end

local function objectVehicleCheck(self, timer)
	self.timer = (self.timer or 0) + timer
	if self.timer > 0.18 then
		self.timer = 0
		if UnitHasVehicleUI(self:GetParent().unit or "") and self:GetParent().vehicleUnit then
			if UnitExists(self:GetParent().vehicleUnit) and UnitHealthMax(self:GetParent().vehicleUnit) > 0 then
				self:Hide()
				self:GetParent().hasAggro = IRF2:GetAggro(self:GetParent().vehicleUnit)
				self:GetParent():PLAYER_ENTERING_WORLD()
			end
		else
			self:GetParent().vehicleUnit = nil
			self:Hide()
			self:GetParent().hasAggro = IRF2:GetAggro(self:GetParent().unit)
			self:GetParent():PLAYER_ENTERING_WORLD()
		end
	end
end

local function objectVehicle(self, isEvent)
	-- 탈것 탑승 상태인지 검사
	if UnitExists(self.unit or "") then
		if UnitHasVehicleUI(self.unit) then
			self.vehicleUnit = IRF2:GetVehicleUnit(self.unit)
			if UnitHealthMax(self.vehicleUnit) > 0 then
				self.vehicleCheck:Hide()
				if isEvent then
					self.hasAggro = IRF2:GetAggro(self.unit)
					self:PLAYER_ENTERING_WORLD()
				end
			else
				self.vehicleCheck:Show()
			end
		else
			self.vehicleUnit = nil
			self.vehicleCheck:Hide()
			if isEvent then
				self.hasAggro = IRF2:GetAggro(self.unit)
				self:PLAYER_ENTERING_WORLD()
			end
		end
	else
		self.vehicleUnit = nil
		self.vehicleCheck:Hide()
	end
end

local groupNumber, childNumber, fullName

local function OnAttributeChanged(self, name, value)
	-- 유니트의 유니트 아이디 변경시 재설정
	if name == "unit" and value then
		if not self.initUnit then
			-- 프레임 레이아웃 미적용시 적용
			self.initUnit = true
			IRF2:SetObjectLayout(self)
			IRF2:SetObjectEvent(self)
			-- 프레임 저장 변수에 추가
			groupNumber, childNumber = self:GetName():match("Group(%d+)UnitButton(%d+)$")
			if groupNumber and childNumber then
				IRF2.group[tonumber(groupNumber)].children = IRF2.group[tonumber(groupNumber)].children or {}
				IRF2.group[tonumber(groupNumber)].children[childNumber] = self
			else
				childNumber = self:GetName():match("GroupPetUnitButton(%d+)$")
				if childNumber then
					IRF2.group.pet.children = IRF2.group.pet.children or {}
					IRF2.group.pet.children[childNumber] = self
				end
			end
			if IRF2.group.pet and self:GetParent() == IRF2.group.pet then
				self:SetScript("OnDragStart", IRF2.petAnchor:GetScript("OnDragStart"))
				self:SetScript("OnDragStop", IRF2.petAnchor:GetScript("OnDragStop"))
			end
		end
		IRF2.units[value] = self
		self.name = UnitName(value)
		if self.unit ~= value then
			self.connected = true
			self.unit = value
			self.unitID = value:match("(%d+)")
			if self.unitID then
				self.unitType = value:gsub(self.unitID, "")
				self.unitID = tonumber(self.unitID)
			else
				self.unitType = nil
			end
			self.targetUnit = value == "player" and "target" or (value.."target")
			self.fullName = IRF2:GetUnitName(value)
			if self.fullName and self.name ~= "무엇인가" then
				IRF2.names[self.fullName] = IRF2.names[self.fullName] or {}
				for i = 1, #IRF2.names[self.fullName] do
					if IRF2.names[self.fullName][i] == self then
						return
					end
				end
				tinsert(IRF2.names[self.fullName], self)
			end
		end
	end
	if name == "unit" then
		IRF2:StopMoving()
	end
end

local function objectUpdate(self)
	-- 유니트 전체 업데이트
	self.inRange, self.vehicleUnit = nil
	if self.unit == nil and self:GetAttribute("unit") then
		OnAttributeChanged(self, "unit", self:GetAttribute("unit"))
	end
	if self.unit and UnitExists(self.unit) then
		objectVehicle(self)
		objectName(self)
		objectHealth(self)
		objectPowerType(self)
		IRF2:AuraUpdate(self.unit)
		if self.RAID_TARGET_UPDATE then
			self:RAID_TARGET_UPDATE()
		end
		if self.UpdateResBar then
			self:UpdateResBar()
		end
		if self.UpdateCastingBar then
			self:UpdateCastingBar()
		end
	end
end

local removedMenu = {}

local function removeMenuType(menu, menuType)
	for index, name in ipairs(UnitPopupMenus[menu]) do
		if name == menuType then
			tremove(UnitPopupMenus[menu], index)
			break
		end
	end
end

local function removeMenu(menu)
	if removedMenu[menu] == nil and UnitPopupMenus[menu] then
		removeMenuType(menu, "SET_FOCUS")
		removeMenuType(menu, "CLEAR_FOCUS")
		removeMenuType(menu, "TARGET")
		removedMenu[menu] = true
	end
end

local function objectMenu(self)
	-- 유니트의 메뉴 토글
	if self.unit == "player" then
		removeMenu("SELF")
		ToggleDropDownMenu(1, nil, PlayerFrameDropDown, "cursor")
	elseif UnitIsUnit(self.unit, "pet") then
		removeMenu("PET")
		ToggleDropDownMenu(1, nil, PetFrameDropDown, "cursor")
	elseif self.unitType == "party" and self.unitID then
		removeMenu("PARTY")
		ToggleDropDownMenu(1, nil, _G[("PartyMemberFrame%dDropDown"):format(self.unitID)], "cursor")
	elseif self.unitType == "raid" and self.unitID and self.name then
		removeMenu("RAID")
		removeMenu("RAID_PLAYER")
		self.id = self.unitID
		RaidGroupButton_ShowMenu(self)
	end
end

local function OnEvent(self, event, unit, ...)
	-- 유니트의 이벤트를 각각 적절한 핸들러로 연결
	if self[event] and self.unit and UnitExists(self.unit) then
		if not unit then
			if self:IsShown() then
				self[event](self)
			end
		elseif self.unit == unit or UnitIsUnit(self.unit, unit) or self.vehicleUnit == unit or UnitIsUnit(self.vehicleUnit or "", unit) then
			if event == "UNIT_HEALTH" or event == "UNIT_MAXHEALTH" then
				self[event](self, nil, nil)
			else
				self[event](self, self.unit, ...)
			end
		end
	end
end

local function OnEnter(self)
	IRF2.highlightFrame.parent = self
	IRF2.highlightFrame:ClearAllPoints()
	IRF2.highlightFrame:SetAllPoints(self)
	IRF2.highlightFrame:Show()
	self.onenter = true
	if IRF2.db.outline == "마우스 오버" then
		self.textFrame:SetBackdropBorderColor(unpack(IRF2.db.outlineMouseOver))
	end
end

local function OnLeave(self)
	IRF2.highlightFrame:Hide()
	GameTooltip:Hide()
	self.onenter = nil
	if IRF2.db.outline == "마우스 오버" then
		self.textFrame:SetBackdropBorderColor(self:GetBackdropColor())
	end
end

local function initObject(object, unit)
	-- 유니트의 기본 동작 설정
	object:RegisterForClicks("AnyUp")
	object:SetAttribute("type1", "target")
	object:SetAttribute("initial-height", IRF2.db.layout.height)
	object:SetAttribute("initial-width", IRF2.db.layout.width)
	object:SetAttribute("toggleForVehicle", object:GetParent() ~= IRF2.group.pet)	-- 탈것
	object:SetScript("OnEvent", OnEvent)
	object:SetScript("OnAttributeChanged", OnAttributeChanged)
	object:SetScript("OnShow", objectUpdate)
	object:SetScript("OnHide", objectClear)
	object:SetScript("OnEnter", OnEnter)
	object:SetScript("OnLeave", OnLeave)
	object:RegisterForDrag("LeftButton")
	object:SetScript("OnDragStart", IRF2.anchor:GetScript("OnDragStart"))
	object:SetScript("OnDragStop", IRF2.anchor:GetScript("OnDragStop"))
	object:SetFrameLevel(2)
	object.SetManyAttributes = SetManyAttributes
	object.SetSafeAttribute = SetSafeAttribute
	-- 유니트의 메뉴 등록
	object.menu = objectMenu
	object:SetAttribute("type2", "menu")
	-- 오브젝트 저장 변수에 유니트 등록
	tinsert(IRF2.objects, object)
	-- 오부젝트 기본 모듈 실행
	for _, func in pairs(IRF2.objectHandler) do
		func(object)
	end
end

local child, childnum

local function preCreateUnitButton(self, num)
	if type(self) == "number" and IRF2.group[self] then
		self = IRF2.group[self]
	elseif type(self) ~= "table" then
		return
	end
	if not self.children then
		self.children = {}
		childnum = 1
		while true do
			child = self:GetAttribute("child"..childnum)
			if child then
				self.children[childnum] = child
				childnum = childnum + 1
			else
				break
			end
		end
	end
	for i = #self.children + 1, num do
		child = CreateFrame("Button", ("%sUnitButton%d"):format(self:GetName(), i), self, "SecureUnitButtonTemplate")
		SetupUnitButtonConfiguration(self, child)
		self:SetAttribute(("child%d"):format(i), child)
		self.children[i] = child
	end
end

local function GetNumChild(self)
	if self:IsShown() then
		for i = 1, #self.children do
			if not self.children[i]:IsShown() then
				return i - 1
			end
		end
		return #self.children
	else
		return 0
	end
end

local function GetNextGroup(self, value)
	return IRF2.group[self:GetID() + value]
end

function IRF2:SetNewAttribute(object)
	object.SetManyAttributes = SetManyAttributes
	object.SetSafeAttribute = SetSafeAttribute
end

function IRF2:CreateGroup(group)
	-- 유니트 그룹 생성
	if self.group[group] then
		return self.group[group]
	else
		local header = CreateFrame("Frame", ("InvenRaidFrames2Group%d"):format(group), UIParent, "SecureGroupHeaderTemplate")
		header:Hide()
		header.initialConfigFunction = initObject
		header.SetManyAttributes = SetManyAttributes
		header.SetSafeAttribute = SetSafeAttribute
		header.GetNumChild = GetNumChild
		header.GetNextGroup = GetNextGroup
		header.PreCreateChildren = preCreateUnitButton
		header:SetFrameStrata(self.db.strata)
		header:SetFrameLevel(2)
		header:SetID(group)
		header:SetScale(self.db.layout.scale)
		header:SetManyAttributes("template", "SecureUnitButtonTemplate", "maxColumns", false, "unitsPerColumn", false, "columnAnchorPoint", false, "columnSpacing", self.db.layout.offset)
		if group == 0 then
			preCreateUnitButton(header, 5)
			header:SetManyAttributes("showSolo", true, "showPlayer", true, "showParty", true)
		elseif self.db.groupOrder.byClass or self.db.groupOrder.byName then
			if group == 1 then
				preCreateUnitButton(header, 40)
				header:SetManyAttributes("showRaid", true, "groupFilter", self:GetGroupOrder(), "groupingOrder", self:GetGroupOrder(), "groupBy", "CLASS")
			else
				header:SetManyAttributes("showRaid", true, "groupFilter", "9", "groupingOrder", "9", "groupBy", "GROUP")
			end
		else
			preCreateUnitButton(header, 5)
			header:SetManyAttributes("showRaid", true, "groupFilter", ("%d"):format(group), "groupingOrder", ("%d"):format(group), "groupBy", "GROUP")
		end
		self.group[group] = header
		return header
	end
end

function IRF2:UpdateAllPlayerUnits()
	for unit, object in pairs(self.units) do
		if UnitIsPlayer(unit) then
			object:PLAYER_ENTERING_WORLD()
		end
	end
end

function IRF2:UpdateAllUnits()
	for _, object in pairs(self.units) do
		object:PLAYER_ENTERING_WORLD()
	end
end

function IRF2:QuickHealth()
	-- 빠른 체력바 반응을 위해 LibQuickHealth-2.0 사용
	QuickHealth.RegisterCallback(self, "UnitHealthUpdated", function(_, unit, health, maxHealth)
		if IRF2.units[unit] and not IRF2.units[unit].vehicleUnit then
			IRF2.units[unit]:UNIT_HEALTH(health, maxHealth)
		end
	end)
end

function IRF2:SetObjectEvent(object)
	-- 유니트의 이벤트 핸들러 등록
	object.PLAYER_ENTERING_WORLD = objectUpdate
	object.UNIT_HEALTH = objectHealth
	object.UNIT_MANA = objectPower
	object.UNIT_RAGE = objectPower
	object.UNIT_ENERGY = objectPower
	object.UNIT_RUNIC_POWER = objectPower
	object.UNIT_FOCUS = objectPower
	object.UNIT_NAME_UPDATE = objectName
	object.UNIT_DISPLAYPOWER = objectPowerType
	object.UpdateText = objectText
	object.PLAYER_FLAGS_CHANGED = objectText
	object.UpdateHealthBarColor = objectUpdateHealthBarColor
	object.UpdateOutline = objectUpdateOutline
	object.UNIT_EXITED_VEHICLE = objectVehicle
	object.UNIT_ENTERED_VEHICLE = objectVehicle
	object.UNIT_PET = objectVehicleCheck
	-- 유니트의 이벤트 등록
	object:RegisterEvent("PLAYER_ENTERING_WORLD")
	object:RegisterEvent("UNIT_NAME_UPDATE")
	object:RegisterEvent("UNIT_DISPLAYPOWER")
	object:RegisterEvent("UNIT_HEALTH")
	object:RegisterEvent("UNIT_MAXHEALTH")
	object:RegisterEvent("PLAYER_FLAGS_CHANGED")
	object:RegisterEvent("UNIT_ENTERED_VEHICLE")
	object:RegisterEvent("UNIT_EXITED_VEHICLE")
	-- 추가 모듈 실행
	for _, module in pairs(self.objectModules) do
		module(object)
	end
end

function IRF2:SetFont(fontString)
	fontString:SetFont(SM:Fetch("font", self.db.font.file), self.db.font.size, self.db.font.attribute)
	fontString:SetShadowColor(0, 0, 0)
	if self.db.font.shadow then
		fontString:SetShadowOffset(1, -1)
	else
		fontString:SetShadowOffset(0, 0)
	end
end

function IRF2:SetObjectLayout(object)
	-- 배경 생성
	object:SetBackdrop(unitBackdrop)
	object:SetBackdropColor(unpack(self.db.layout.backdropColor))
	-- 체력바 생성
	object.health = CreateFrame("StatusBar", object:GetName().."HealthBar", object)
	object.health:SetMinMaxValues(0, 100)
	object.health:SetStatusBarTexture(SM:Fetch("statusbar", self.db.layout.texture))
	object.health:SetFrameLevel(3)
	object.health:SetOrientation(self.db.layout.healthOrientation == "세로" and "VERTICAL" or "HORIZONTAL")
	-- 마나바 생성
	object.mana = CreateFrame("StatusBar", object:GetName().."ManaBar", object)
	object.mana:SetMinMaxValues(0, 100)
	object.mana:SetStatusBarTexture(SM:Fetch("statusbar", self.db.layout.texture))
	object.mana:SetFrameLevel(3)
	-- 체력바와 마나바 비율 조정
	if self.db.layout.manaBar == 0 then
		object.health:SetAllPoints(object)
	elseif self.db.layout.manaBar == 1 then
		object.mana:SetAllPoints(object)
	else
		object.health:SetPoint("TOPLEFT", object, "TOPLEFT")
		object.health:SetPoint("BOTTOMRIGHT", object, "BOTTOMRIGHT", 0, self.db.layout.height * self.db.layout.manaBar)
		object.mana:SetPoint("TOPLEFT", object, "TOPLEFT", 0, -(self.db.layout.height * (1 - self.db.layout.manaBar) + 1))
		object.mana:SetPoint("BOTTOMRIGHT", object, "BOTTOMRIGHT")
	end
	-- 이름 및 테두리 프레임 생성
	object.textFrame = CreateFrame("Frame", nil, object)
	object.textFrame:SetAllPoints(object)
	object.textFrame:SetFrameLevel(self.db.frameLevel.name)
	object.textFrame:SetBackdrop(unitOutline)
	object.textFrame:SetBackdropBorderColor(unpack(self.db.layout.backdropColor))
	object.text = object.textFrame:CreateFontString(object:GetName().."Name", "OVERLAY")
	object.text:SetPoint("CENTER", object.textFrame, "CENTER", 0, 3)
	self:SetFont(object.text)
	-- 탈것(vehicle) 업데이트 체크 프레임 생성
	object.vehicleCheck = CreateFrame("Frame", nil, object)
	object.vehicleCheck:Hide()
	object.vehicleCheck:SetScript("OnUpdate", objectVehicleCheck)
end