-- Catch whenever the user finishes dragging
UserInputService.InputEnded:connect(function (InputInfo, GameProcessedEvent)

	-- Make sure this was button 1 being released
	if InputInfo.UserInputType ~= Enum.UserInputType.MouseButton1 then
		return;
	end;

	-- Clean up dragging detection listeners and data
	if DragStart then

		-- Clear dragging detection data
		DragStart = nil;
		DragStartTarget = nil;

		-- Disconnect dragging initiation listeners
		ClearConnection 'WatchForDrag';

	end;

	-- Reset from drag mode if dragging
	if Dragging then

		-- Reset normal axes option state
		SetAxes(MoveTool.Axes);

		-- Finalize the dragging operation
		FinishDragging();

	end;

end);

function SetUpDragging(BasePart, BasePoint)
	-- Sets up and initiates dragging based on the given base part

	-- Prevent selection while dragging
	Core.Targeting.CancelSelecting();

	-- Prepare parts, and start dragging
	InitialState = PreparePartsForDragging();
	StartDragging(BasePart, InitialState, BasePoint);

end;

MoveTool.SetUpDragging = SetUpDragging;

function PreparePartsForDragging()
	-- Prepares parts for dragging and returns the initial state of the parts

	local InitialState = {};

	-- Get index of parts
	local PartIndex = Support.FlipTable(Selection.Items);

	-- Stop parts from moving, and capture the initial state of the parts
	for _, Part in pairs(Selection.Items) do
		InitialState[Part] = { Anchored = Part.Anchored, CanCollide = Part.CanCollide, CFrame = Part.CFrame };
		Part.Anchored = true;
		Part.CanCollide = false;
		InitialState[Part].Joints = Core.PreserveJoints(Part, PartIndex);
		Part:BreakJoints();
		Part.Velocity = Vector3.new();
		Part.RotVelocity = Vector3.new();
	end;

	return InitialState;
end;

function StartDragging(BasePart, InitialState, BasePoint)
	-- Begins dragging the selection

	-- Ensure dragging is not already ongoing
	if Dragging then
		return;
	end;

	-- Indicate that we're dragging
	Dragging = true;

	-- Track changes
	TrackChange();

	-- Disable bounding box calculation
	BoundingBox.ClearBoundingBox();

	-- Cache area permissions information
	local AreaPermissions;
	if Core.Mode == 'Tool' then
		AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Items), Core.Player);
	end;

	-- Ensure a base part is provided
	if not BasePart or not InitialState[BasePart] then
		return;
	end;

	-- Determine the base point for dragging
	local BasePartOffset = -BasePart.CFrame:pointToObjectSpace(Core.Mouse.Hit.p);

	-- Improve base point alignment for the given increment
	BasePartOffset = Vector3.new(
		math.clamp(GetIncrementMultiple(BasePartOffset.X, MoveTool.Increment), -BasePart.Size.X / 2, BasePart.Size.X / 2),
		math.clamp(GetIncrementMultiple(BasePartOffset.Y, MoveTool.Increment), -BasePart.Size.Y / 2, BasePart.Size.Y / 2),
		math.clamp(GetIncrementMultiple(BasePartOffset.Z, MoveTool.Increment), -BasePart.Size.Z / 2, BasePart.Size.Z / 2)
	);

	-- Use the given base point instead if any
	if BasePoint then
		BasePartOffset = -BasePart.CFrame:pointToObjectSpace(BasePoint);
	end;

	-- Prepare snapping in case it is enabled, and make sure to override its default target selection
	SnapTracking.TargetBlacklist = Selection.Items;
	Connections.DragSnapping = PointSnapped:connect(function (SnappedPoint)

		-- Align the selection's base point to the snapped point
		local Rotation = SurfaceAlignment or (InitialState[BasePart].CFrame - InitialState[BasePart].CFrame.p);
		BasePart.CFrame = CFrame.new(SnappedPoint) * Rotation * CFrame.new(BasePartOffset);
		TranslatePartsRelativeToPart(BasePart, InitialState);

		-- Make sure we're not entering any unauthorized private areas
		if Core.Mode == 'Tool' and Security.ArePartsViolatingAreas(Selection.Items, Core.Player, false, AreaPermissions) then
			BasePart.CFrame = InitialState[BasePart].CFrame;
			TranslatePartsRelativeToPart(BasePart, InitialState);
		end;

	end);

	-- Update cache of corner offsets for later crossthrough calculations
	CornerOffsets = GetCornerOffsets(InitialState[BasePart].CFrame, InitialState);

	-- Provide a callback to trigger alignment
	TriggerAlignment = function ()

		-- Trigger drag recalculation
		DragToMouse(BasePart, BasePartOffset, InitialState, AreaPermissions);

		-- Trigger snapping recalculation
		if SnapTracking.Enabled then
			PointSnapped:fire(SnappedPoint);
		end;

	end;

	-- Start up the dragging action
	Connections.Drag = Core.Mouse.Move:connect(function ()
		DragToMouse(BasePart, BasePartOffset, InitialState, AreaPermissions);
	end);

end;