--- Runs for the very first time whenever a hitbox is created
--- Do not run this more than once, you may introduce memory leaks if you do so

function Hitbox:_Init()
	if not self.HitboxObject then return end

	local tagConnection: RBXScriptConnection

	local function onTagRemoved(instance: Instance)
		if instance == self.HitboxObject then
			tagConnection:Disconnect()
			self:Destroy()
		end
	end

	self:Recalibrate()
	table.insert(ActiveHitboxes, self)
	CollectionService:AddTag(self.HitboxObject, self.Tag)

	tagConnection = CollectionService:GetInstanceRemovedSignal(self.Tag):Connect(onTagRemoved)
end

local function Init()
	--- Reserve table sizing for solver tables
	local solversCache: {[number]: any} = table.create(#Solvers:GetChildren())

	DEFAULT_SIMULATION_TYPE:Connect(function(step: number)
		--- Iterate through all the hitboxes
		for i = #ActiveHitboxes, 1, -1 do
			--- Skip this hitbox if the hitbox will be garbage collected this frame
			if ActiveHitboxes[i].HitboxPendingRemoval then
				local hitbox: any = table.remove(ActiveHitboxes, i)
				table.clear(hitbox)
				setmetatable(hitbox, nil)
				continue
			end

			for _: number, point: Point in ipairs(ActiveHitboxes[i].HitboxRaycastPoints) do
				--- Reset this point if the hitbox is inactive
				if not ActiveHitboxes[i].HitboxActive then
					point.LastPosition = nil
					continue
				end

				--- Calculate rays
				local castMode: any = solversCache[point.CastMode]
				local origin: Vector3, direction: Vector3 = castMode:Solve(point)
				local raycastResult: RaycastResult = workspace:Raycast(origin, direction, ActiveHitboxes[i].RaycastParams)

				--- Draw debug rays
				if ActiveHitboxes[i].Visualizer then
					local adornmentData: AdornmentData? = VisualizerCache:GetAdornment()

					if adornmentData then
						local debugStartPosition: CFrame = castMode:Visualize(point)
						adornmentData.Adornment.Length = direction.Magnitude
						adornmentData.Adornment.CFrame = debugStartPosition
					end
				end

				--- Update the current point's position
				point.LastPosition = castMode:UpdateToNextPosition(point)

				--- If a ray detected a hit
				if raycastResult then
					local part: BasePart = raycastResult.Instance
					local model: Instance?
					local humanoid: Instance?
					local target: Instance?

					if ActiveHitboxes[i].DetectionMode == 1 then
						model = part:FindFirstAncestorOfClass("Model")
						if model then
							humanoid = model:FindFirstChildOfClass("Humanoid")
						end
						target = humanoid
					else
						target = part
					end

					--- Found a target. Fire the OnHit event
					if target then
						if ActiveHitboxes[i].DetectionMode <= 2 then
							if ActiveHitboxes[i].HitboxHitList[target] then
								continue
							else
								ActiveHitboxes[i].HitboxHitList[target] = true
							end
						end

						ActiveHitboxes[i].OnHit:Fire(part, humanoid, raycastResult, point.Group)
					end
				end

				--- Hitbox Time scheduler
				if ActiveHitboxes[i].HitboxStopTime > 0 then
					if ActiveHitboxes[i].HitboxStopTime <= os.clock() then
						ActiveHitboxes[i]:HitStop()
					end
				end

				--- OnUpdate event that fires every frame for every point
				ActiveHitboxes[i].OnUpdate:Fire(point.LastPosition)

				--- Update SignalType
				if ActiveHitboxes[i].OnUpdate._signalType ~= ActiveHitboxes[i].SignalType then
					ActiveHitboxes[i].OnUpdate._signalType = ActiveHitboxes[i].SignalType
					ActiveHitboxes[i].OnHit._signalType = ActiveHitboxes[i].SignalType
				end
			end
		end

		local adornmentsInUse: number = #VisualizerCache._AdornmentInUse

		--- Iterates through all the debug rays to see if they need to be cached or cleaned up
		if adornmentsInUse > 0 then
			for i = adornmentsInUse, 1, -1 do
				if (os.clock() - VisualizerCache._AdornmentInUse[i].LastUse) >= DEFAULT_DEBUGGER_RAY_DURATION then
					local adornment: AdornmentData? = table.remove(VisualizerCache._AdornmentInUse, i)

					if adornment then
						VisualizerCache:ReturnAdornment(adornment)
					end
				end
			end
		end
	end)

	--- Require all solvers
	for castMode: string, enum: number in pairs(Hitbox.CastModes) do
		local moduleScript: Instance? = Solvers:FindFirstChild(castMode)

		if moduleScript then
			local load = require(moduleScript)
			solversCache[enum] = load
		end
	end
end

Init()

return Hitbox
