--[[ How to use BouncePlatforms

LocalScript:
	BouncePlatforms.new(part, {
		debounce = 0.2,
		bounceVector = Vector3.new(0, 1, 0),
		bounceForce = 2000,
	})

	BouncePlatforms:enable()

]]

local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")

local BouncePlatform = require(script.BouncePlatform)
local BouncePlatformConstants = require(script.BouncePlatformConstants)

local BOUNCY_TAG = BouncePlatformConstants.BOUNCY_TAG
local DEBUG = false
local HIPHEIGHT_BUFFER = 2
local RAYCAST_DETECTION_OFFSETS = {
	Vector2.new(-1, 0.5),
	Vector2.new(0, 0.5),
	Vector2.new(1, 0.5),
	Vector2.new(0, 0),
	Vector2.new(-1, -0.5),
	Vector2.new(0, -0.5),
	Vector2.new(1, -0.5),
}

local player = Players.LocalPlayer

local function _assert(part)
	assert(part ~= nil, "Part expected got nil")
	assert(typeof(part) == "Instance", "expected Instance got " .. typeof(part))
	assert(part.ClassName == "MeshPart" or part.ClassName == "Part", "expected MeshPart or Part got " .. part.ClassName)
end

local function getModelMass(model)
	local totalMass = 0

	for _, basepart in pairs(model:GetDescendants()) do
		if basepart:IsA("BasePart") then
			totalMass += basepart:GetMass()
		end
	end

	return totalMass
end

local BouncePlatforms = {
	ActiveBouncePlatforms = {},
}

function BouncePlatforms.new(part, config)
	_assert(part)

	local bouncePlatform = BouncePlatform.new(part, config)

	BouncePlatforms.ActiveBouncePlatforms[bouncePlatform.platform] = bouncePlatform

	if DEBUG then
		bouncePlatform:_debug(bouncePlatform)
	end

	return bouncePlatform
end

function BouncePlatforms:_detect(raycastParams, length)
	local raycastResults = {}

	local lookVector = player.Character.PrimaryPart.CFrame.LookVector
	local rightVector = player.Character.PrimaryPart.CFrame.RightVector

	for _, cell in pairs(RAYCAST_DETECTION_OFFSETS) do
		local offsetX = rightVector * cell.X
		local offsetZ = lookVector * cell.Y
		local origin = (player.Character.PrimaryPart.Position + offsetZ + offsetX)
		local raycastResult = workspace:Raycast(origin, Vector3.new(0, -length, 0), raycastParams)

		if raycastResult and raycastResult.Instance then
			table.insert(raycastResults, raycastResult)
		end
	end

	return raycastResults
end

function BouncePlatforms:_checkForBouncePlatforms()
	if not player.Character then
		return
	end

	if not player.Character.PrimaryPart then
		return
	end

	local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
	if not humanoid then
		return
	end
	
	local length = humanoid.HipHeight + HIPHEIGHT_BUFFER
	local raycastParams = RaycastParams.new()
	raycastParams.FilterDescendantsInstances = CollectionService:GetTagged(BOUNCY_TAG)
	raycastParams.FilterType = Enum.RaycastFilterType.Whitelist

	local raycastResults = BouncePlatforms:_detect(raycastParams, length)

	for _, raycastResult in pairs(raycastResults) do
		local platformInstance = raycastResult.Instance

		local bouncePlatform = BouncePlatforms:getBouncePlatformFromInstance(platformInstance)

		if not bouncePlatform or (bouncePlatform and not bouncePlatform.enabled) then
			return
		end

		local currentTime = os.clock()

		if currentTime - bouncePlatform.lastBouncedAt >= bouncePlatform.debounce then
			bouncePlatform.lastBouncedAt = currentTime

			player.Character.PrimaryPart.Velocity = Vector3.new(0, 0, 0)

			local momentum = bouncePlatform.bounceVector * bouncePlatform.bounceForce * player.Character.PrimaryPart.AssemblyMass

			player.Character.PrimaryPart:ApplyImpulse(momentum)

			bouncePlatform:_onBounced()

			return
		end
	end
end

function BouncePlatforms:enable()
	if BouncePlatforms.HeartbeatConnection then
		BouncePlatforms:disable()
	end

	BouncePlatforms.HeartbeatConnection = RunService.Heartbeat:Connect(function()
		BouncePlatforms:_checkForBouncePlatforms()
	end)
end

function BouncePlatforms:disable()
	if BouncePlatforms.HeartbeatConnection then
		BouncePlatforms.HeartbeatConnection:Disconnect()
		BouncePlatforms.HeartbeatConnection = nil
	end
end

function BouncePlatforms:getBouncePlatformFromInstance(platform)
	return BouncePlatforms.ActiveBouncePlatforms[platform]
end

function BouncePlatforms:getActiveBouncePlatforms()
	return BouncePlatforms.ActiveBouncePlatforms
end

return BouncePlatforms