-- May return NaN or inf or -inf
local function absoluteAngleBetween3dVectors(vec1, vec2)
	return math.acos(vec1:Dot(vec2) / (vec1.magnitude * vec2.magnitude))
end

local humanoidCache = {}
local function findPlayerHumanoid(player)
	local character = player and player.Character
	if character then
		local resultHumanoid = humanoidCache[player]
		if resultHumanoid and resultHumanoid.Parent == character then
			return resultHumanoid
		else
			humanoidCache[player] = nil -- Bust Old Cache
			for _, child in pairs(character:GetChildren()) do
				if child:IsA('Humanoid') then
					humanoidCache[player] = child
					return child
				end
			end
		end
	end
end

local function CreateFollowCamera()
	local module = RootCameraCreator()
	
	local tweenAcceleration = math.rad(250)
	local tweenSpeed = math.rad(0)
	local tweenMaxSpeed = math.rad(250)
	
	local lastUpdate = tick()
	function module:Update()
		local now = tick()
		local userPanningTheCamera = (self.UserPanningTheCamera == true)

		if lastUpdate == nil or now - lastUpdate > 1 then
			module:ResetCameraLook()
			self.LastCameraTransform = nil
		end
		
		if lastUpdate then
			-- Cap out the delta to 0.5 so we don't get some crazy things when we re-resume from
			local delta = math.min(0.5, now - lastUpdate)
			local angle = self.TurningLeft and -120 or 0
			angle = angle + (self.TurningRight and 120 or 0)
			if angle ~= 0 then userPanningTheCamera = true end
			self:RotateCamera(self:GetCameraLook(), Vector2.new(math.rad(angle * delta), 0))
		end
		
		-- Reset tween speed if user is panning
		if userPanningTheCamera then
			tweenSpeed = 0
		end
		

		local camera = 	workspace.CurrentCamera
		local player = PlayersService.LocalPlayer
		local subjectPosition = self:GetSubjectPosition()
		
		if subjectPosition and player and camera then
			local zoom = self:GetCameraZoom()
			if zoom <= 0 then
				zoom = 0.1
			end
			
			if self:GetShiftLock() and not self:IsInFirstPerson() then
				local offset = ((self:GetCameraLook() * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
				-- Check for NaNs
				if IsFiniteVector3(offset) then
					subjectPosition = subjectPosition + offset
				end
				zoom = math.max(zoom, 5)
			else
				if self.LastCameraTransform and not userPanningTheCamera then
					local humanoid = findPlayerHumanoid(player)
					local cameraSubject = camera and camera.CameraSubject
					local isClimbing = humanoid and humanoid:GetState() == Enum.HumanoidStateType.Climbing
					local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
					local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
					if (isClimbing or isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
						local forwardVector = humanoid.Torso.CFrame.lookVector
						if isOnASkateboard then
							forwardVector = cameraSubject.CFrame.lookVector
						end
						local timeDelta = (now - lastUpdate)
						
						tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
						
						local percent = clamp(0, 1, tweenSpeed * timeDelta)
						if not isClimbing and self:IsInFirstPerson() then
							percent = 1
						end
						local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
						-- Check for NaN
						if IsFinite(y) then
							self:RotateCamera(self:GetCameraLook(), Vector3.new(y * percent, 0, 0))
						end
					elseif not self:IsInFirstPerson() then
						local lastVec = -(self.LastCameraTransform.p - subjectPosition)
						local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
						-- Check for NaNs
						if IsFinite(y)then
							self:RotateCamera(self:GetCameraLook(), Vector3.new(y, 0, 0))
						end
					end
				end
			end
			
			camera.Focus = CFrame.new(subjectPosition)
			camera.CoordinateFrame = CFrame.new(camera.Focus.p - (zoom * self:GetCameraLook()), camera.Focus.p)
			self.LastCameraTransform = camera.CoordinateFrame
		end
		
		lastUpdate = now
	end
	
	return module
end

return CreateFollowCamera