--===========================================================================================================================>
--!optimize 2
--!native
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zone]: Regions')

-- Define Module table
local RegionsModule: RegionsModule = {}; RegionsModule.__index = RegionsModule

--===========================================================================================================================>
--[ GLOBAL VARIABLES: ]

local MAX_PART_SIZE = 2024

local RANDOM = Random.new()

local OVERLAP = OverlapParams.new()
OVERLAP.FilterType = Enum.RaycastFilterType.Include

--===========================================================================================================================>
--[ DEFINE TYPES: ]

-- Insert the Object Types:
export type RegionsModule = typeof(RegionsModule)

type PartDetails = {
	Part: BasePart;
	Type: 'Sphere'|'Complex'|'Block';
}

-- Create Local Type:
type BoundDetails = {
	Values: {};

	ParseCheck:
		(self: BoundDetails, Value1: number, Value2: number) -> boolean;
	Parse:
		(self: BoundDetails, ValuesToParse: {number}) -> ();
};

-- Create Local Type:
type Bounds = {
	Max: BoundDetails; Min: BoundDetails
}

type Polygon = {
	Next: {
		Next: PolygonT2?;
		Position: Vector3;
		Previous: Polygon;
	};
	Position: Vector3;
	Previous: Polygon
}

type PolygonT2 = {
	Position: Vector3?,
	Previous: {
		Next: PolygonT2,
		Position: Vector3?,
		Previous: Polygon?
	}?
}

type PartsTable = {{CFrame: CFrame, Size: Vector3}} | {BasePart};

--===========================================================================================================================>
--[ FUNCTIONS: ]

-- @Public
-- Function to create Parts to a certain size and scale and parented to the Model Container:
function RegionsModule.CreateCube(self: RegionsModule, Container: Model, CubeCFrame: CFrame, CubeSize: Vector3)
	--=======================================================================================================>
	if CubeSize.X > MAX_PART_SIZE or CubeSize.Y > MAX_PART_SIZE or CubeSize.Z > MAX_PART_SIZE then
		local quarterSize = CubeSize * 0.25
		local halfSize = CubeSize * 0.5
		self:CreateCube(Container, CubeCFrame * CFrame.new(-quarterSize.X, -quarterSize.Y, -quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(-quarterSize.X, -quarterSize.Y, quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(-quarterSize.X, quarterSize.Y, -quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(-quarterSize.X, quarterSize.Y, quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(quarterSize.X, -quarterSize.Y, -quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(quarterSize.X, -quarterSize.Y, quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(quarterSize.X, quarterSize.Y, -quarterSize.Z), halfSize)
		self:CreateCube(Container, CubeCFrame * CFrame.new(quarterSize.X, quarterSize.Y, quarterSize.Z), halfSize)
	else
		local Part = Instance.new("Part")
		Part.CFrame = CubeCFrame
		Part.Size = CubeSize
		Part.Anchored = true
		Part.CanCollide = false
		Part.CanTouch   = false
		Part.Transparency = 1
		Part.Reflectance  = 0
		Part.Material = Enum.Material.SmoothPlastic
		Part.Massless = true
		Part.Parent = Container
	end
	--=======================================================================================================>
end

-- @Public
-- Optimized function to get the corners of a BasePart
function RegionsModule.GetCorners(self: RegionsModule, CFrame1: CFrame, Size2: Vector3, Half: boolean?): {Vector3}
	--=======================================================================================================>
	-- Create an empty array for corner vectors
	local Corners = {}

	-- If the part is a simple box (Part) or a MeshPart with no mesh ID
	if Half == true then
		-- Only calculate the 4 necessary corners

		-- Top-Left Front
		table.insert(Corners, CFrame1 * Vector3.new(-Size2.X, Size2.Y, Size2.Z))
		-- Bottom-Right Front
		table.insert(Corners, CFrame1 * Vector3.new(Size2.X, -Size2.Y, Size2.Z))
		-- Top-Left Back
		table.insert(Corners, CFrame1 * Vector3.new(-Size2.X, Size2.Y, -Size2.Z))
		-- Bottom-Right Back
		table.insert(Corners, CFrame1 * Vector3.new(Size2.X, -Size2.Y, -Size2.Z))
	else
		-- Calculate all 8 corners for more complex parts
		table.insert(Corners, (CFrame1 * Vector3.new(Size2.X, Size2.Y, Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(-Size2.X, Size2.Y, Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(Size2.X, -Size2.Y, Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(Size2.X, Size2.Y, -Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(-Size2.X, -Size2.Y, Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(Size2.X, -Size2.Y, -Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(-Size2.X, Size2.Y, -Size2.Z)))
		table.insert(Corners, (CFrame1 * Vector3.new(-Size2.X, -Size2.Y, -Size2.Z)))
	end

	-- Return the corners array
	return Corners
	--=======================================================================================================>
end

-- @Public
-- Optimized function to get the corners of a BasePart
function RegionsModule.GetCornersFromPart(self: RegionsModule, Part: BasePart): {Vector3}
	--=======================================================================================================>
	-- Get the CFrame of the part
	local CFrame1 = Part.CFrame
	-- Get the size of the part, divided by half
	local Size = Part.Size * 0.5

	-- If the part is a simple box (Part) or a MeshPart with no mesh ID
	if (Part:IsA('Part') and Part.Shape == Enum.PartType.Block) or (Part:IsA('MeshPart') and Part.MeshId == "") then
		return self:GetCorners(CFrame1, Size, true)
	else
		return self:GetCorners(CFrame1, Size, false)
	end

	--=======================================================================================================>
end

-- @Public
-- Optimized function to get the corners of a BasePart
function RegionsModule.GetCornersFromPartDetails(self: RegionsModule, PartHalfSize: Vector3, PartCFrame: CFrame, PartType: 'Block'|'Sphere'|'Complex'): {Vector3}
	--=======================================================================================================>
	-- If the part is a simple box (Part) or a MeshPart with no mesh ID
	if PartType == 'Block' then
		return self:GetCorners(PartCFrame, PartHalfSize, true)
	else
		return self:GetCorners(PartCFrame, PartHalfSize, false)
	end
	--=======================================================================================================>
end

--===========================================================================================================================>

-- @Public
-- Function to return the Bounding Box Min and Max from CFrame and Size:
function RegionsModule.GetBoundingBox(self: RegionsModule, CFrame1: CFrame, Size1: Vector3): (Vector3, Vector3)
	--=======================================================================================================>
	local Min = CFrame1.Position - (Size1 * 0.5)
	local Max = CFrame1.Position + (Size1 * 0.5)
	return Min, Max
	--=======================================================================================================>
end

--===========================================================================================================================>

-- @Public
-- Function to check if a Vector3 is inside a BasePart
function RegionsModule.IsPointInsidePart(self: RegionsModule, Point: Vector3, Part: BasePart): boolean
	--=======================================================================================================>
	local CFrame1 = Part.CFrame
	local Size   = Part.Size * 0.5

	local Min = CFrame1.Position - Size
	local Max = CFrame1.Position + Size

	return Point.X >= Min.X and Point.X <= Max.X and
		Point.Y >= Min.Y and Point.Y <= Max.Y and
		Point.Z >= Min.Z and Point.Z <= Max.Z
	--=======================================================================================================>
end

-- @Public
-- Check if the BasePart1 is fully inside BasePart2
function RegionsModule.IsPartFullyInsidePart(self: RegionsModule, Part1: BasePart, Part2: BasePart): boolean
	--=======================================================================================================>

	local Corners: {Vector3} = self:GetCornersFromPart(Part1)

	for Index: number, Corner: Vector3 in ipairs(Corners) do
		if not self:IsPointInsidePart(Corner, Part2) then return false end
	end

	return true

	--=======================================================================================================>
end

--===========================================================================================================================>

function RegionsModule.GetHalfSize(self: RegionsModule, Size: Vector3): Vector3
	return Size / 2
end

function RegionsModule.GetRadius(self: RegionsModule, Size: Vector3): number
	return Size.X / 2
end

function RegionsModule.GetPartDetails(self: RegionsModule, Part: BasePart): PartDetails
	return {
		Part = Part;
		Type = self:GetPartType(Part)
	}
end

-- Initialization function to start/setup the Object's initial data:
function RegionsModule.GetPartType(self: RegionsModule, Part: BasePart): 'Block'|'Sphere'|'Complex'
	--=======================================================================================================>

	-- Returns whether the ZonePart is a Block:
	local function IsBlock(Part: BasePart): boolean
		-- Cache the PartShape:
		local PartShape: Enum.PartType | false = Part:IsA('Part') and Part.Shape
		-- Return the boolean whether the PartShape is a 'Block' or the Part is a MeshPart with no Id:
		return PartShape == Enum.PartType.Block or Part:IsA('MeshPart') and Part.MeshId == ""
	end

	-- Returns whether the ZonePart is a Sphere:
	local function IsSphere(Part: BasePart): boolean
		-- Cache the PartShape:
		local PartShape: Enum.PartType | false = Part:IsA('Part') and Part.Shape
		-- Return the boolean whether the PartShape is a 'Ball'
		return PartShape == Enum.PartType.Ball
	end

	--=======================================================================================================>

	-- Return the ZonePartProperties Dictionary:
	return if IsBlock(Part) then 'Block' elseif IsSphere(Part) then 'Sphere' else 'Complex'

	--=======================================================================================================>
end

-- @Public
-- Get the Center Position Of the Array of Parts:
function RegionsModule.GetCenterPointOfParts(self: RegionsModule, Parts: {BasePart}): Vector3
	--=======================================================================================================>
	-- Initialize the vector for total positions and part counter
	local TotalPosition: Vector3 = Vector3.zero
	local PartCount: number = 0
	-- Sum up positions of all parts in Parts table
	for Index: number, Part: BasePart in ipairs(Parts) do TotalPosition += Part.Position; PartCount += 1 end
	-- Return the average position; if PartCount is 0, this returns (0,0,0)
	return TotalPosition / PartCount
	--=======================================================================================================>
end

-- @Public
-- Get the Center Part Of the Array of Parts:
function RegionsModule.GetCenterPartOfParts(self: RegionsModule, Parts: {BasePart}): BasePart
	--=======================================================================================================>

	-- Calculate the center position of parts
	local CenterPoint: Vector3 = self:GetCenterPointOfParts(Parts)
	-- Define ClosestPart as BasePart
	local ClosestDistance: number, ClosestPart: BasePart = math.huge, nil

	-- Iterate through each part to find the closest one to the center point
	for Index: number, Part: BasePart in ipairs(Parts) do
		-- Grab the Distance:
		local Distance: number = (Part.Position - CenterPoint).Magnitude
		-- Compare Distance to ClosestDistance variable, overwrite if less:
		if Distance < ClosestDistance then ClosestDistance, ClosestPart = Distance, Part end
	end

	-- Return the part closest to the center point
	return ClosestPart

	--=======================================================================================================>
end

-- @Public
-- Get a random point in the box passed:
function RegionsModule.GetRandomPointInBox(self: RegionsModule, BoxCFrame: CFrame, BoxSize: Vector3): Vector3
	--=======================================================================================================>
	-- Generate a local space point within the bounds of the box:
	local X: number = (RANDOM:NextNumber() - 0.5) * BoxSize.X
	local Y: number = (RANDOM:NextNumber() - 0.5) * BoxSize.Y
	local Z: number = (RANDOM:NextNumber() - 0.5) * BoxSize.Z

	-- Return the world space point
	return BoxCFrame:PointToWorldSpace(Vector3.new(X, Y, Z))
	--=======================================================================================================>
end

-- Get a random point inside a sphere given its center and radius.
function RegionsModule.GetRandomPointInSphere(self: RegionsModule, Center: Vector3, Radius: number): Vector3
	--=======================================================================================================>
	-- Generate random direction with unit vector
	local X: number = RANDOM:NextNumber(-1, 1)
	local Y: number = RANDOM:NextNumber(-1, 1)
	local Z: number = RANDOM:NextNumber(-1, 1)

	local Direction: Vector3 = Vector3.new(X, Y, Z)

	-- Normalize to make sure it’s on unit sphere, and multiply by scaled radius
	-- Retry if magnitude is zero (rare)
	if Direction.Magnitude == 0 then
		return self:GetRandomPointInSphere(Center, Radius)
	end

	-- Uniform radius scaling using cubic root
	local ScaledRadius: number = Radius * RANDOM:NextNumber()^(1/3)

	-- Return final point inside sphere
	return Center + Direction.Unit * ScaledRadius
	--=======================================================================================================>
end

-- @Public
-- Get a random point that lies *within* the physical volume of the given BasePart.
-- This is useful for complex shapes where bounding box checks alone ain't precise enough.
function RegionsModule.GetRandomPointInBasePart(self: RegionsModule, Part: BasePart, WorldModel: WorldModel | Workspace): Vector3
	--=======================================================================================>

	-- Set a maximum number of attempts to avoid infinite loops if no valid point is found
	local MaxAttempts: number = 10000

	-- Create a tiny invisible probe part used for precise overlap testing
	local Probe_Part: Part = Instance.new('Part')
	Probe_Part.Anchored = true
	Probe_Part.CanCollide = false
	Probe_Part.CanTouch = false
	Probe_Part.Massless = true
	Probe_Part.Transparency = 1
	Probe_Part.Size = Vector3.new(0.5, 0.5, 0.5) -- Small size for tight spatial accuracy
	Probe_Part.Parent = WorldModel -- Parent it into the world so overlap checks work

	-- Loop up to the max number of attempts
	for Attempt: number = 1, MaxAttempts do

		-- Get a random point within the bounding box of the part
		local Point: Vector3 = self:GetRandomPointInBox(Part.CFrame, Part.Size)

		-- Move the tiny test part to that random point
		Probe_Part.CFrame = CFrame.new(Point)

		-- Add the probe to the overlap filter so it doesn't get detected by itself
		OVERLAP:AddToFilter(Probe_Part)

		-- Perform a precise spatial overlap check using Roblox's GetPartsInPart
		if WorldModel:GetPartsInPart(Part, OVERLAP)[1] then
			-- If the overlap returns any hit, we've got a valid point!
			-- Clean up before returning
			OVERLAP.FilterDescendantsInstances = {}
			Probe_Part:Destroy()
			return Point
		end
	end

	-- Clean up test part and filter if no point was found after all attempts
	Probe_Part:Destroy()
	OVERLAP.FilterDescendantsInstances = {}

	-- Fallback: return the center of the part if no point was found
	return Part.Position

	--=======================================================================================>
end

--===========================================================================================================================>

-- @Public
-- Calculates an oriented bounding box for the specified parts,
-- applying a faster AABB calculation if parts are axis-aligned.
function RegionsModule.GetBoundingCFrameAndSize(self: RegionsModule, Parts: PartsTable | BasePart, VoxelAlign: boolean?): (CFrame, Vector3)
	--=======================================================================================================>

	-- Input validation: ensure Parts is not empty and contains only BasePart instances
	if typeof(Parts) ~= "table" then Parts = {Parts} end

	--=======================================================================================================>

	-- Helper function to round values to the nearest voxel grid size (default: 4) for voxel alignment
	local function RoundToVoxel(Value: number): number
		local ROUND_TO: number = 4  -- Define the alignment step to match the voxel grid
		return ROUND_TO * math.floor((Value + ROUND_TO / 2) / ROUND_TO)
	end

	-- Helper function to update min and max bounds by comparing a point to current bounds
	local function UpdateBounds(Point: Vector3, MinBound: Vector3, MaxBound: Vector3): (Vector3, Vector3)
		return MinBound:Min(Point), MaxBound:Max(Point)
	end

	-- Inline function to determine if all parts are axis-aligned (no rotation)
	local function IsAxisAligned(PartsSent: PartsTable): boolean
		-- Loop through all parts, return false if any part is rotated (not aligned with world axes)
		for Index: number, Part: BasePart | {CFrame: CFrame, Size: Vector3} in ipairs(PartsSent) do
			if not Part.CFrame.Rotation:FuzzyEq(CFrame.new()) then return false end
		end
		return true  -- All parts are aligned if none are rotated
	end

	--=======================================================================================================>

	-- Axis-Aligned Bounding Box (AABB) Optimization: Checks if all parts are axis-aligned
	local AxisAlignedParts: boolean = IsAxisAligned(Parts :: PartsTable)

	-- Define variables for the final bounding CFrame, center, and size of the calculated box
	local BoundingCFrame: CFrame, Center: Vector3, Size: Vector3

	-- Initialize min and max bounds to extreme values for later updates
	local MinBound: Vector3 = Vector3.new(math.huge, math.huge, math.huge)
	local MaxBound: Vector3 = Vector3.new(-math.huge, -math.huge, -math.huge)

	--=======================================================================================================>

	-- Axis-Aligned Bounding Box (AABB) Calculation if all parts are axis-aligned
	if AxisAlignedParts then
		--===============================================================================>
		-- Loop through all parts and update bounds based on their half-size and position
		for Index: number, Part: BasePart | {CFrame: CFrame, Size: Vector3} in ipairs(Parts :: PartsTable) do
			-- Calculate the half-size and position of each part
			local HalfSize: Vector3, Position: Vector3 = Part.Size * 0.5, Part.CFrame.Position
			-- Update the min and max bounds with each corner of the part
			MinBound, MaxBound = UpdateBounds(Position - HalfSize, MinBound, MaxBound)
			MinBound, MaxBound = UpdateBounds(Position + HalfSize, MinBound, MaxBound)
		end
		--===============================================================================>
	else
		--===============================================================================>
		-- Oriented Bounding Box Calculation if parts are not axis-aligned

		-- Variables to accumulate average look and right vectors across all parts
		local AvgLookVector: Vector3, AvgRightVector: Vector3 = Vector3.zero, Vector3.zero

		-- Calculate the sum of LookVector and RightVector for all parts to later find the average rotation
		for Index: number, Part: BasePart | {CFrame: CFrame, Size: Vector3} in ipairs(Parts :: PartsTable) do
			AvgLookVector += Part.CFrame.LookVector
			AvgRightVector += Part.CFrame.RightVector
		end

		-- Normalize vectors to unit length to create a consistent average rotation
		AvgLookVector, AvgRightVector = AvgLookVector.Unit, AvgRightVector.Unit

		-- Calculate the UpVector and rotation matrix using the average look and right vectors
		local AvgUpVector: Vector3 = AvgLookVector:Cross(AvgRightVector).Unit
		local AvgRotation: CFrame = CFrame.fromMatrix(Vector3.zero, AvgRightVector, AvgUpVector)

		-- Calculate bounds for rotated box by converting each part's CFrame to the average rotation
		for Index: number, Part: BasePart | {CFrame: CFrame, Size: Vector3} in ipairs(Parts :: PartsTable) do
			-- Transform part CFrame into local space of average rotation
			local LocalCFrame: CFrame = AvgRotation:Inverse() * Part.CFrame
			local HalfSize: Vector3 = Part.Size * 0.5

			-- Corners of the part in its local space, considering its half-size
			local Corners: {Vector3} = {
				LocalCFrame * Vector3.new(-HalfSize.X, -HalfSize.Y, -HalfSize.Z),
				LocalCFrame * Vector3.new(-HalfSize.X, -HalfSize.Y,  HalfSize.Z),
				LocalCFrame * Vector3.new(-HalfSize.X,  HalfSize.Y, -HalfSize.Z),
				LocalCFrame * Vector3.new(-HalfSize.X,  HalfSize.Y,  HalfSize.Z),
				LocalCFrame * Vector3.new( HalfSize.X, -HalfSize.Y, -HalfSize.Z),
				LocalCFrame * Vector3.new( HalfSize.X, -HalfSize.Y,  HalfSize.Z),
				LocalCFrame * Vector3.new( HalfSize.X,  HalfSize.Y, -HalfSize.Z),
				LocalCFrame * Vector3.new( HalfSize.X,  HalfSize.Y,  HalfSize.Z),
			}

			-- Update min and max bounds for each corner in the rotated box space
			for Index: number, Corner: Vector3 in ipairs(Corners) do
				MinBound, MaxBound = UpdateBounds(Corner, MinBound, MaxBound)
			end
		end

		-- Set the calculated average rotation as the initial bounding CFrame
		BoundingCFrame = AvgRotation

		--===============================================================================>
	end

	-- If voxel alignment is specified, round min and max bounds for voxel consistency
	if VoxelAlign then
		MinBound = Vector3.new(RoundToVoxel(MinBound.X), RoundToVoxel(MinBound.Y), RoundToVoxel(MinBound.Z))
		MaxBound = Vector3.new(RoundToVoxel(MaxBound.X), RoundToVoxel(MaxBound.Y), RoundToVoxel(MaxBound.Z))
	end

	--=======================================================================================================>

	-- Calculate the center and size of the bounding box from min and max bounds
	Center, Size = ((MinBound + MaxBound) / 2), (MaxBound - MinBound)

	-- Set bounding CFrame based on axis-alignment
	BoundingCFrame = if AxisAlignedParts then CFrame.new(Center) else BoundingCFrame * CFrame.new(Center)

	--=======================================================================================================>

	-- Return the calculated oriented bounding box CFrame and size
	return BoundingCFrame, Size

	--=======================================================================================================>
end

--===========================================================================================================================>

-- @Public
-- Checks if a specified point (IsPointInSphere) is within a given sphere
function RegionsModule.IsPointInSphere(self: RegionsModule, PointToCheck: Vector3, SphereCFrame: CFrame, Radius: number): boolean
	--=======================================================================================================>
	-- Calculate the distance between the point and the center of the sphere
	-- Check if the distance is less than or equal to the radius
	return (PointToCheck - SphereCFrame.Position).Magnitude <= Radius
	--=======================================================================================================>
end

-- @Public
-- Checks if a specified point (PointToCheck) is within a given part, taking into account the part’s rotation.
-- This function operates in local space to correctly interpret the part’s orientation.
function RegionsModule.IsPointInBox(self: RegionsModule, PointToCheck: Vector3, BoxCFrame: CFrame, BoxHalfSize: Vector3): boolean
	--=======================================================================================================>
	-- Transform the specified point (PointToCheck) to the part’s local space
	-- This ensures that the check accounts for the rotation and position of the part
	local LocalPoint: Vector3 = BoxCFrame:PointToObjectSpace(PointToCheck)
	-- Check if the transformed point’s local coordinates (X, Y, Z) are within the bounds of the part’s half-size.
	-- math.abs() is used on each coordinate to account for both positive and negative bounds,
	-- allowing the check to work for points on either side of the part’s center.
	-- If all three coordinates are within bounds, the function returns true, indicating the point lies within the part.
	return (math.abs(LocalPoint.X) <= BoxHalfSize.X)
		and (math.abs(LocalPoint.Y) <= BoxHalfSize.Y)
		and (math.abs(LocalPoint.Z) <= BoxHalfSize.Z)
	--=======================================================================================================>
end

--===========================================================================================================================>

-- Return a Frozen Module Table:
return table.freeze(RegionsModule) :: RegionsModule

--===========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Default Prefix:
local Zoner = `\n[ ZONER LOG ]`

-- Default Title Prefix:
local Title = `\n[Title]:`
-- Default Error Prefix:
local Error = `\n[Error]:`
-- Default Description Prefix:
local Desc = `\n[Desc]: `

--===========================================================================================================================>

-- Return Logs table:
return table.freeze({
	--================================================================================================================>

	Zoner = table.freeze({
		--======================================================================================================>
		DetectionCoverage =
			`{Zoner} {Title} Zone: New {Error} Invalid DetectionCoverage Enum {Desc} DetectionCoverage Config must be a number or string from the DetectionCoverage Enum`;
		DetectionMethod =
			`{Zoner} {Title} Zone: New {Error} Invalid DetectionMethod Enum {Desc} DetectionMethod Config must be a number or string from the DetectionMethod Enum`;
		DetectionMode =
			`{Zoner} {Title} Zone: New {Error} Invalid DetectionMode Enum {Desc} DetectionMode Config must be a number or string from the DetectionMode Enum`;
		Execution =
			`{Zoner} {Title} Zone: New {Error} Invalid Execution Enum {Desc} Execution Config must be a number or string from the Execution Enum`;
		Rate =
			`{Zoner} {Title} Zone: New {Error} Invalid Rate Enum {Desc} Rate Config must be a number or string from the Rate Enum`;
		ManualStepping =
			`{Zoner} {Title} Zone: New {Error} Invalid ManualStepping boolean {Desc} ManualStepping Config must be a boolean`;
		NoZonePartAddedListening =
			`{Zoner} {Title} Zone: New {Error} Invalid NoZonePartAddedListening boolean {Desc} NoZonePartAddedListening Config must be a boolean`;
		NoZonePartPropertyListening =
			`{Zoner} {Title} Zone: New {Error} Invalid NoZonePartPropertyListening boolean {Desc} NoZonePartPropertyListening Config must be a boolean`;
		Simulation =
			`{Zoner} {Title} Zone: New {Error} Invalid Simulation Enum {Desc} Simulation Config must be a number or string from the Simulation Enum`;
		Bounds =
			`{Zoner} {Title} Zone: New {Error} Invalid Bounds Enum {Desc} Bounds Config must be a number or string from the Bounds Enum`;

		ContainerType =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Type {Desc} Container Paramater must be a Table or Instance`;
		ContainerBoxTable =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Zone Box Table {Desc} Container Box Table is missing a CFrame or Size property`;
		ContainerBoxTable2 =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Zone Box Table {Desc} Container Box Table is not an Array of Zone Box Tables`;
		ContainerPartTable1 =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Part Table {Desc} Container Part Table does not contain only BaseParts`;
		ContainerPartTable2 =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Part Table {Desc} Container Part Table does not contain only Instances`;

		ContainerInstanceType  =
			`{Zoner} {Title} Zone: New {Error} Invalid Container Instance Type {Desc} Container Instance Paramater must be a 'BasePart', 'Folder', 'Model', or 'Configuration'`;
		--======================================================================================================>
	});

	Zone = table.freeze({
		--======================================================================================================>
		SetDetection =
			`{Zoner} {Title} Zone: SetDetection {Error} Invalid Detection Enum {Desc} Detection Paramater be a number or string from the Detection Enum`;
		SetEnterDetection =
			`{Zoner} {Title} Zone: SetEnterDetection {Error} Invalid Detection Enum {Desc} Detection Enum must be a number from the Detection Enum`;
		SetExitDetection =
			`{Zoner} {Title} Zone: SetExitDetection {Error} Invalid Detection Enum {Desc} Detection Enum must be a number from the Detection Enum`;

		SetSimulation1 =
			`{Zoner} {Title} Zone: SetSimulation {Error} Invalid Simulation Enum {Desc} Simulation Paramater must be a number or string from the Simulation Enum`;
		SetSimulation2 =
			`{Zoner} {Title} Zone: SetSimulation {Error} Invalid Simulation Enum {Desc} "PreRender" cannot be used on the Server`;

		SetAccuracy =
			`{Zoner} {Title} Zone: SetAccuracy {Error} Invalid Accuracy Enum {Desc} Accuracy Paramater must be a number or string from the Accuracy Enum`;

		SetRate =
			`{Zoner} {Title} Zone: SetRate {Error} Invalid Rate Enum {Desc} Rate Paramater must be a number or string from the Accuracy Enum`;

		SetState =
			`{Zoner} {Title} Zone: SetState {Error} Invalid State Value {Desc} State Value must be a boolean`;

		LogConfigPrefix =
			`{Zoner} {Title} Zone: Log Config`;

		--======================================================================================================>
	});

	Tracker = table.freeze({
		--================================>

		DetectionCoverage =
			`{Zoner} {Title} Tracker: New {Error} Invalid DetectionCoverage Enum {Desc} DetectionCoverage Paramater must be a number or string from the DetectionCoverage Enum`;
		DetectionMode =
			`{Zoner} {Title} Tracker: New {Error} Invalid DetectionMode Enum {Desc} DetectionMode Paramater must be a number or string from the DetectionMode Enum`;

		ItemType =
			`{Zoner} {Title} Tracker: New {Error} Invalid Item Type {Desc} CItem Paramater must be an Instance`;

		InstanceType  =
			`{Zoner} {Title} Tracker: New {Error} Invalid Item Instance Type {Desc} Item Instance Paramater must be a 'BasePart' or 'Model'`;

		SetDetection1 =
			`{Zoner} {Title} Tracker: SetDetection {Error} Invalid DetectionCoverage Enum {Desc} DetectionCoverage Paramater must be a number or string from the DetectionCoverage Enum`;
		SetDetection2 =
			`{Zoner} {Title} Tracker: SetDetection {Error} Invalid DetectionMode Enum {Desc} DetectionMode Paramater must be a number or string from the DetectionMode Enum`;

		--================================>
	});

	--================================================================================================================>
})

--===========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

local DetectionCoverage = require('./DetectionCoverage');
local DetectionMethod   = require('./DetectionMethod');
local DetectionMode     = require('./DetectionMode');
local Simulation        = require('./Simulation');
local Execution         = require('./Execution');
local Bounds            = require('./Bounds');
local Rate              = require('./Rate');

--==========================================================================================================================>

export type DetectionCoverageEnum = DetectionCoverage.DetectionCoverageEnum
export type DetectionMethodEnum   = DetectionMethod.DetectionMethodEnum
export type DetectionModeEnum     = DetectionMode.DetectionModeEnum
export type SimulationEnum        = Simulation.SimulationEnum
export type ExecutionEnum         = Execution.ExecutionEnum
export type RateEnum              = Rate.RateEnum

--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Enums')

--[=[
	@class Enums

	Tables of related enums for use in Zoner. Each enum is a frozen table with both name and value lookups,
	as well as optional associated properties.

	**Enums**
	- `DetectionMethod`, `DetectionMode`, `DetectionCoverage`
	- `Rate`, `Execution`, `Simulation`, `Bounds`
]=]

--[=[
	@interface EnumItem
	@within Enums
	@field Name string --Display name of the item (e.g. "Fast")
	@field Value number --Numeric id returned when indexing by name
]=]

--===========================================================================================================================>

--[=[
	@interface DetectionMethod
	@within Enums

	@field Automatic EnumItem --Auto-pick based on container shape & coverage/mode;
	@field Efficient EnumItem --Fastest. Uses simple CFrame and Size math checks to determine location in the Zone Volume.
	@field Simple    EnumItem --Balanced: may use `GetPartBoundsInBox/Radius` for modest cost and good fidelity.
	@field Complex   EnumItem --Highest accuracy: per-part or mesh-aware paths like `GetPartsInPart`; most expensive. If ZoneContainer has no Instances, they will be created.
]=]
--[=[
	@prop DetectionMethod DetectionMethod
	@within Enums
	@readonly
]=]

--[=[
	@interface DetectionMode
	@within Enums

	@field Full  EnumItem --Target must be fully inside or outside the Zone bounds to trigger entrance or exit.
	@field Touch EnumItem --Any intersection with the zone boundary counts (partial overlap). (Doesn't actually use .Touched)
	@field Point EnumItem --One or more representative points must be inside (fastest). Often pairs with Efficient math tests.
]=]
--[=[
	@prop DetectionMode DetectionMode
	@within Enums
	@readonly
]=]

--[=[
	@interface DetectionCoverage
	@within Enums

	@field Center   EnumItem --Check only the target's center point (very fast). Often math-only.
	@field AllParts EnumItem --Every relevant part/point must satisfy `DetectionMode` (strict).
	@field AnyPart  EnumItem --At least one relevant part/point must satisfy `DetectionMode` (lenient).
]=]
--[=[
	@prop DetectionCoverage DetectionCoverage
	@within Enums
	@readonly
]=]

--[=[
	@interface Rate
	@within Enums

	@field Slow      EnumItem --Step period ≈ **1.0 s** between evaluations.
	@field Moderate  EnumItem --Step period ≈ **0.5 s**.
	@field Fast      EnumItem --Step period ≈ **0.1 s**.
	@field Immediate EnumItem --Step period **0 s** (continuous / every frame slice).
]=]
--[=[
	@prop Rate Rate
	@within Enums
	@readonly
]=]

--[=[
	@interface Execution
	@within Enums

	@field Serial   EnumItem --Single-threaded group holder. Supports manual stepping via `Zone:Step(...)` when `ManualStepping = true`.
	@field Parallel EnumItem --Actor-backed parallel evaluation. Manual stepping not exposed; runs on its own schedule. Fastest with lots of Zones.
]=]
--[=[
	@prop Execution Execution
	@within Enums
	@readonly
	@since 0.9.9
]=]

--[=[
	@interface Simulation
	@within Enums

	@field PostSimulation EnumItem --Run after physics; safe for effects based on final transforms.
	@field PreSimulation  EnumItem --Run before physics; good for preparing forces/constraints.
	@field PreRender      EnumItem --Run before rendering (client). On server, requests are coerced to PostSimulation.
]=]
--[=[
	@prop Simulation Simulation
	@within Enums
	@readonly
	@since 0.9.9
]=]

--[=[
	@interface Bounds
	@within Enums

	@field Automatic EnumItem --Auto-select bounds model from the container (boxes/spheres vs per-part).
	@field BoxExact  EnumItem --Automatically creates a Rotated Box (No Instance unless specified) that fits the sum of the ZoneContainer
	@field BoxVoxel  EnumItem --Automatically creates a Rotated Box (No Instance unless specified) that fits the sum of the ZoneContainer, then rounds to Voxel Grid
	@field PerPart   EnumItem --Evaluate each contained BasePart or {CFrame, Size} individually as the sum of the whole Zone.
]=]
--[=[
	@prop Bounds Bounds
	@within Enums
	@readonly
]=]

--===========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	DetectionCoverage = DetectionCoverage;
	DetectionMethod   = DetectionMethod;
	DetectionMode     = DetectionMode;
	Simulation        = Simulation;
	Execution         = Execution;
	Bounds            = Bounds;
	Rate              = Rate;
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type Bounds = 'AUTOMATIC'|'PER_PART'|'BOX_EXACT'|'BOX_VOXEL'

-- Export the Type:
export type BoundsEnum = {Name: Bounds, Value: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Automatically selects the most appropriate bounds for detection,
	-- based on the zone’s shape and target complexity.
	Automatic = table.freeze({Name = 'AUTOMATIC' :: 'AUTOMATIC', Value = 1} :: BoundsEnum);

	-- Detects each part of the zone individually. Useful for zones with
	-- complex or irregular shapes where precise detection per part is necessary.
	PerPart = table.freeze({Name = 'PER_PART' :: 'PER_PART', Value = 2} :: BoundsEnum);

	-- Combines all parts of the zone into a single, exact bounding box
	-- without rotation. Provides efficient detection for simpler shapes.
	BoxExact = table.freeze({Name = 'BOX_EXACT' :: 'BOX_EXACT', Value = 3} :: BoundsEnum);

	-- Combines all parts of the zone into a bounding box aligned to
	-- the voxel grid. Ideal where some approximation is acceptable.
	BoxVoxel = table.freeze({Name = 'BOX_VOXEL' :: 'BOX_VOXEL', Value = 4} :: BoundsEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.Automatic.Value] = ENUMS.Automatic :: BoundsEnum;
		[ENUMS.PerPart.Value]   = ENUMS.PerPart   :: BoundsEnum;
		[ENUMS.BoxExact.Value]  = ENUMS.BoxExact  :: BoundsEnum;
		[ENUMS.BoxVoxel.Value]  = ENUMS.BoxVoxel  :: BoundsEnum;
	});
	Write = table.freeze({
		[ENUMS.Automatic :: BoundsEnum] = ENUMS.Automatic.Value;
		[ENUMS.PerPart   :: BoundsEnum] = ENUMS.PerPart.Value;
		[ENUMS.BoxExact  :: BoundsEnum] = ENUMS.BoxExact.Value;
		[ENUMS.BoxVoxel  :: BoundsEnum] = ENUMS.BoxVoxel.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type DetectionCoverage = 'CENTER'|'ALL_PARTS'|'ANY_PART'|'AUTOMATIC'

-- Export the Type:
export type DetectionCoverageEnum = {Name: DetectionCoverage, Value: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Detects only the center of the target. Ideal for quick checks.
	Center    = table.freeze({Name = 'CENTER'    :: 'CENTER',    Value = 1} :: DetectionCoverageEnum);

	-- Requires that all parts of the target are within the zone.
	AllParts  = table.freeze({Name = 'ALL_PARTS' :: 'ALL_PARTS', Value = 2} :: DetectionCoverageEnum);

	-- Detects if any part of the target is inside the zone.
	AnyPart   = table.freeze({Name = 'ANY_PART'  :: 'ANY_PART',  Value = 3} :: DetectionCoverageEnum);

	-- Automatically calculates a detection coverage mode.
	Automatic = table.freeze({Name = 'AUTOMATIC' :: 'AUTOMATIC', Value = 4} :: DetectionCoverageEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.Center.Value]    = ENUMS.Center    :: DetectionCoverageEnum;
		[ENUMS.AllParts.Value]  = ENUMS.AllParts  :: DetectionCoverageEnum;
		[ENUMS.AnyPart.Value]   = ENUMS.AnyPart   :: DetectionCoverageEnum;
		[ENUMS.Automatic.Value] = ENUMS.Automatic :: DetectionCoverageEnum;
	});
	Write = table.freeze({
		[ENUMS.Center    :: DetectionCoverageEnum] = ENUMS.Center.Value;
		[ENUMS.AllParts  :: DetectionCoverageEnum] = ENUMS.AllParts.Value;
		[ENUMS.AnyPart   :: DetectionCoverageEnum] = ENUMS.AnyPart.Value;
		[ENUMS.Automatic :: DetectionCoverageEnum] = ENUMS.Automatic.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type DetectionMethod = 'AUTOMATIC'|'EFFICIENT'|'SIMPLE'|'COMPLEX'

-- Export the Type:
export type DetectionMethodEnum = {Name: DetectionMethod, Value: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Automatically selects the best detection method based on shape,
	-- bounds, and target complexity.
	Automatic = table.freeze({Name = 'AUTOMATIC' :: 'AUTOMATIC', Value = 1} :: DetectionMethodEnum);

	-- Prioritizes speed and efficiency for simpler cases.
	Efficient = table.freeze({Name = 'EFFICIENT' :: 'EFFICIENT', Value = 2} :: DetectionMethodEnum);

	-- Balances between efficiency and accuracy.
	Simple    = table.freeze({Name = 'SIMPLE'    :: 'SIMPLE',    Value = 3} :: DetectionMethodEnum);

	-- Prioritizes accuracy over efficiency for complex shapes.
	Complex   = table.freeze({Name = 'COMPLEX'   :: 'COMPLEX',   Value = 4} :: DetectionMethodEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.Automatic.Value] = ENUMS.Automatic :: DetectionMethodEnum;
		[ENUMS.Efficient.Value] = ENUMS.Efficient :: DetectionMethodEnum;
		[ENUMS.Simple.Value]    = ENUMS.Simple    :: DetectionMethodEnum;
		[ENUMS.Complex.Value]   = ENUMS.Complex   :: DetectionMethodEnum;
	});
	Write = table.freeze({
		[ENUMS.Automatic :: DetectionMethodEnum] = ENUMS.Automatic.Value;
		[ENUMS.Efficient :: DetectionMethodEnum] = ENUMS.Efficient.Value;
		[ENUMS.Simple    :: DetectionMethodEnum] = ENUMS.Simple.Value;
		[ENUMS.Complex   :: DetectionMethodEnum] = ENUMS.Complex.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type DetectionMode = 'FULL'|'TOUCH'|'POINT'|'AUTOMATIC'

-- Export the Type:
export type DetectionModeEnum = {Name: DetectionMode, Value: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Requires the target to be fully contained within the zone.
	Full      = table.freeze({Name = 'FULL'      :: 'FULL',      Value = 1} :: DetectionModeEnum);

	-- Allows detection if the target is touching or partially overlapping.
	Touch     = table.freeze({Name = 'TOUCH'     :: 'TOUCH',     Value = 2} :: DetectionModeEnum);

	-- Detects based on specific points within the target.
	Point     = table.freeze({Name = 'POINT'     :: 'POINT',     Value = 3} :: DetectionModeEnum);

	-- Automatically calculates a detection mode.
	Automatic = table.freeze({Name = 'AUTOMATIC' :: 'AUTOMATIC', Value = 4} :: DetectionModeEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.Full.Value]      = ENUMS.Full      :: DetectionModeEnum;
		[ENUMS.Touch.Value]     = ENUMS.Touch     :: DetectionModeEnum;
		[ENUMS.Point.Value]     = ENUMS.Point     :: DetectionModeEnum;
		[ENUMS.Automatic.Value] = ENUMS.Automatic :: DetectionModeEnum;
	});
	Write = table.freeze({
		[ENUMS.Full      :: DetectionModeEnum] = ENUMS.Full.Value;
		[ENUMS.Touch     :: DetectionModeEnum] = ENUMS.Touch.Value;
		[ENUMS.Point     :: DetectionModeEnum] = ENUMS.Point.Value;
		[ENUMS.Automatic :: DetectionModeEnum] = ENUMS.Automatic.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type Rate = 'SLOW'|'MODERATE'|'FAST'|'IMMEDIATE'

-- Export the Type:
export type RateEnum = {Name: Rate, Value: number, DelayBetweenChecks: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Slow: Runs zone checks every 1.0 second.
	Slow      = table.freeze({Name = 'SLOW'      :: 'SLOW',      Value = 1, DelayBetweenChecks = 1.0} :: RateEnum);

	-- Moderate: Runs zone checks every 0.5 seconds.
	Moderate  = table.freeze({Name = 'MODERATE'  :: 'MODERATE',  Value = 2, DelayBetweenChecks = 0.5} :: RateEnum);

	-- Fast: Runs zone checks every 0.1 second.
	Fast      = table.freeze({Name = 'FAST'      :: 'FAST',      Value = 3, DelayBetweenChecks = 0.1} :: RateEnum);

	-- Immediate: Runs zone checks continuously with zero delay.
	Immediate = table.freeze({Name = 'IMMEDIATE' :: 'IMMEDIATE', Value = 4, DelayBetweenChecks = 0.0} :: RateEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.Slow.Value]      = ENUMS.Slow      :: RateEnum;
		[ENUMS.Moderate.Value]  = ENUMS.Moderate  :: RateEnum;
		[ENUMS.Fast.Value]      = ENUMS.Fast      :: RateEnum;
		[ENUMS.Immediate.Value] = ENUMS.Immediate :: RateEnum;
	});
	Write = table.freeze({
		[ENUMS.Slow      :: RateEnum] = ENUMS.Slow.Value;
		[ENUMS.Moderate  :: RateEnum] = ENUMS.Moderate.Value;
		[ENUMS.Fast      :: RateEnum] = ENUMS.Fast.Value;
		[ENUMS.Immediate :: RateEnum] = ENUMS.Immediate.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Data')

--===========================================================================================================================>

-- Export the Type:
export type Simulation = 'PostSimulation'|'PreSimulation'|'PreRender'

-- Export the Type:
export type SimulationEnum = {Name: Simulation, Value: number}

-- Table of Enums:
local ENUMS = {
	--===================================>

	-- Executes after the physics simulation step.
	PostSimulation = table.freeze({Name = 'PostSimulation' :: 'PostSimulation', Value = 1} :: SimulationEnum);

	-- Executes before the physics simulation step.
	PreSimulation  = table.freeze({Name = 'PreSimulation'  :: 'PreSimulation',  Value = 2} :: SimulationEnum);

	-- Executes before the rendering step.
	PreRender      = table.freeze({Name = 'PreRender'      :: 'PreRender',      Value = 3} :: SimulationEnum);

	--===================================>
};

-- Table of Network Hashes:
local HASH = {
	--===================================>
	Read  = table.freeze({
		[ENUMS.PostSimulation.Value] = ENUMS.PostSimulation :: SimulationEnum;
		[ENUMS.PreSimulation.Value]  = ENUMS.PreSimulation  :: SimulationEnum;
		[ENUMS.PreRender.Value]      = ENUMS.PreRender      :: SimulationEnum;
	});
	Write = table.freeze({
		[ENUMS.PostSimulation :: SimulationEnum] = ENUMS.PostSimulation.Value;
		[ENUMS.PreSimulation  :: SimulationEnum] = ENUMS.PreSimulation.Value;
		[ENUMS.PreRender      :: SimulationEnum] = ENUMS.PreRender.Value;
	});
	--===================================>
}

--==========================================================================================================================>

-- Return the Frozen Table:
return table.freeze({
	ENUMS = table.freeze(ENUMS);
	HASH  = table.freeze(HASH);
})

--==========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Types')

--===========================================================================================================================>
--==[ DEPENDENCIES ]==--

--=[ Local Requires ]=--

local DetectionCoverage = require('../Data/Enums/DetectionCoverage')
local DetectionMethod   = require('../Data/Enums/DetectionMethod')
local DetectionMode     = require('../Data/Enums/DetectionMode')
local Simulation        = require('../Data/Enums/Simulation')
local Execution         = require('../Data/Enums/Execution')
local Bounds            = require('../Data/Enums/Bounds')
local Rate              = require('../Data/Enums/Rate')

--===========================================================================================================================>

-- ZoneConfigTypes
export type ZoneConfigTable = {
	DetectionCoverage: DetectionCoverage.DetectionCoverageEnum;
	DetectionMethod:   DetectionMethod.DetectionMethodEnum;
	DetectionMode:     DetectionMode.DetectionModeEnum;
	Simulation:        Simulation.SimulationEnum;
	Execution:         Execution.ExecutionEnum;
	Bounds:            Bounds.BoundsEnum;
	Rate:              Rate.RateEnum;

	ManualStepping:              boolean;
	NoZonePartAddedListening:    boolean;
	NoZonePartPropertyListening: boolean;

	EnterDetectionMode: DetectionMode.DetectionModeEnum;
	ExitDetectionMode:  DetectionMode.DetectionModeEnum;

	EnterDetectionCoverage: DetectionCoverage.DetectionCoverageEnum;
	ExitDetectionCoverage:  DetectionCoverage.DetectionCoverageEnum;

}

export type ZoneConfig = {
	DetectionCoverage: DetectionCoverage.DetectionCoverageEnum?;
	DetectionMethod:   DetectionMethod.DetectionMethodEnum?;
	DetectionMode:     DetectionMode.DetectionModeEnum?;
	Simulation:        Simulation.SimulationEnum?;
	Execution:         Execution.ExecutionEnum?;
	Bounds:            Bounds.BoundsEnum?;
	Rate:              Rate.RateEnum?;

	EnterDetectionMode: DetectionMode.DetectionModeEnum?;
	ExitDetectionMode:  DetectionMode.DetectionModeEnum?;

	EnterDetectionCoverage: DetectionCoverage.DetectionCoverageEnum?;
	ExitDetectionCoverage:  DetectionCoverage.DetectionCoverageEnum?;

	NoZonePartAddedListening:    boolean?;
	NoZonePartPropertyListening: boolean?;
	ManualStepping:              boolean?;
}

--===========================================================================================================================>

-- Return an empty table:
return table.freeze({})

--===========================================================================================================================>


--===========================================================================================================================>
--!strict
--===========================================================================================================================>

-- Set Memory Category:
debug.setmemorycategory('[Zoner]: Types')

--===========================================================================================================================>

--[=[
	@type ZoneBox {CFrame: CFrame, Size: Vector3}
	@tag private_types
	@within Zoner

	A type of Zone "Part". Instanceless representation of a single Zone Box
]=]
export type ZoneBox = {CFrame: CFrame, Size: Vector3}

--[=[
	@type ZoneBoxs {ZoneBox}
	@tag private_types
	@within Zoner

	An array of ZoneBoxes. Useful to create multiple, instanceless Zone boxes as one container
]=]
export type ZoneBoxes = {ZoneBox}

export type ZoneContainer = (Model | Folder | BasePart | {BasePart} | {[string]: BasePart} | ZoneBoxes )

--[=[
	@type ZoneContainerType ('Part' | 'Holder' | 'Parts' | 'Boxes')
	@tag private_types
	@within Zone

	Discriminates container representation ("Boxes" or "Instance" flavors)
]=]
export type ZoneContainerType = 'Part' | 'Holder' | 'Parts' | 'Boxes'

--[=[
	@type ZoneParts {BasePart}
	@tag private_types
	@within Zone

	An array of BaseParts
]=]
export type ZoneParts = {BasePart}
-- Create and Export Type:
export type ZonePieces = ZoneBoxes | ZoneParts
-- Create and Export Type:
export type ZonePiece = BasePart | ZoneBox

--=======================================================================================================>

-- Return an empty table:
return table.freeze({})

--=======================================================================================================>