--[[
-- the player touched events which utilise active zones at the moment may change to the new CanTouch method for parts in the future
-- hence im disabling this as it may be depreciated quite soon
function ZoneController.getActiveZones()
	local zonesArray = {}
	for zone, _ in pairs(activeZones) do
		table.insert(zonesArray, zone)
	end
	return zonesArray
end
--]]


function ZoneController.getCharacterRegion(player)
	local char = player.Character
	local head = char and char:FindFirstChild("Head")
	local hrp = char and char:FindFirstChild("HumanoidRootPart")
	if not(hrp and head) then return nil end
	local headY = head.Size.Y
	local hrpSize = hrp.Size
	local charSize = (hrpSize * Vector3.new(2, 2, 1)) + Vector3.new(0, headY, 0)
	local regionCFrame = hrp.CFrame * CFrame.new(0, headY/2 - hrpSize.Y/2, 0)
	local charRegion = RotatedRegion3.new(regionCFrame, charSize)
	return charRegion, regionCFrame, charSize
end

function ZoneController.getTouchingZones(player, onlyActiveZones, recommendedDetection)
	local exitDetection = playerExitDetections[player]
	playerExitDetections[player] = nil
	local finalDetection = exitDetection or recommendedDetection
	local charRegion
	if finalDetection == enum.Detection.WholeBody then
		charRegion = ZoneController.getCharacterRegion(player)
	else
		local char = player.Character
		local hrp = char and char:FindFirstChild("HumanoidRootPart")
		local regionCFrame = hrp and hrp.CFrame
		charRegion = regionCFrame and RotatedRegion3.new(regionCFrame, Vector3.new(0.1, 0.1, 0.1))
	end
	if not charRegion then return {} end
	--[[
	local part = Instance.new("Part")
	part.Size = charSize
	part.CFrame = regionCFrame
	part.Anchored = true
	part.CanCollide = false
	part.Color = Color3.fromRGB(255, 0, 0)
	part.Transparency = 0.4
	part.Parent = workspace
	game:GetService("Debris"):AddItem(part, 2)
	--]]
	local partsTable = (onlyActiveZones and activeParts) or allParts
	local partToZoneDict = (onlyActiveZones and activePartToZone) or allPartToZone
	local parts = charRegion:FindPartsInRegion3WithWhiteList(partsTable, #partsTable)
	if #parts > 0 then
		local hrp = player.Character.HumanoidRootPart
		local hrpCFrame = hrp.CFrame
		local hrpSizeX = hrp.Size.X
		local pointsToVerify
		if finalDetection == enum.Detection.WholeBody then
			pointsToVerify = {
				(hrpCFrame * CFrame.new(-hrpSizeX, 0, 0)).Position,
				(hrpCFrame * CFrame.new(hrpSizeX, 0, 0)).Position,
			}
		else
			pointsToVerify = {
				hrp.Position,
			}
		end
		if not ZoneController.verifyTouchingParts(pointsToVerify, parts) then
			return {}
		end
	end
	local zonesDict = {}
	local touchingPartsDictionary = {}
	for _, part in pairs(parts) do
		local correspondingZone = partToZoneDict[part]
		if correspondingZone then
			zonesDict[correspondingZone] = true
			touchingPartsDictionary[part] = correspondingZone
		end
	end
	local touchingZonesArray = {}
	local newExitDetection
	for zone, _ in pairs(zonesDict) do
		if newExitDetection == nil or zone._currentExitDetection < newExitDetection then
			newExitDetection = zone._currentExitDetection
		end
		table.insert(touchingZonesArray, zone)
	end
	if newExitDetection then
		playerExitDetections[player] = newExitDetection
	end
	return touchingZonesArray, touchingPartsDictionary
end

function ZoneController.getHeightOfParts(tableOfParts)
	local maxY
	local minY
	for _, groupPart in pairs(tableOfParts) do
		local partHeight = groupPart.Size.Y + 10
		local partYHalf = partHeight/2
		local partTopY = groupPart.Position.Y + partYHalf
		local partBottomY = groupPart.Position.Y - partYHalf
		if maxY == nil or partTopY > maxY then
			maxY = partTopY
		end
		if minY == nil or partBottomY < minY then
			minY = partBottomY
		end
	end
	local height = maxY - minY
	return height, minY, maxY
end

function ZoneController.vectorIsBetweenYBounds(vectorToCheck, tableOfParts)
	local height, minY, maxY = ZoneController.getHeightOfParts(tableOfParts)
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = tableOfParts
	raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
	for i = 1, 2 do
		local startVector = Vector3.new(vectorToCheck.X, (i == 1 and maxY) or minY, vectorToCheck.Z)
		local directionVector = Vector3.new(0, (i == 1 and -height) or height, 0)
		local raycastResult = workspace:Raycast(startVector, directionVector, raycastParams)
		local vectorY = vectorToCheck.Y
		local intersectionY = (raycastResult and raycastResult.Position.Y)
		if not intersectionY or (i == 1 and vectorY > intersectionY) or (i == 2 and vectorY < intersectionY) then
			return false
		end
	end
	return true
end

function ZoneController.verifyTouchingParts(vectorsToCheck, tableOfParts)
	-- Region3 checks are unreliable for some baseparts such as MeshParts and UnionOperations, therefore
	-- we perform additional raycast checks to confirm whether the touching parts are actually within the zone
	local classesToCheck = {
		MeshPart = true,
		UnionOperation = true,
	}
	local additionalCheckRequired = true
	for _, part in pairs(tableOfParts) do
		if not classesToCheck[part.ClassName] then
			additionalCheckRequired = false
		end
	end
	if not additionalCheckRequired then
		return true
	end
	for _, vector in pairs(vectorsToCheck) do
		if ZoneController.vectorIsBetweenYBounds(vector, tableOfParts) then
			return true
		end
	end
	return false
end


return ZoneController
