--Variables
local SeatInteractionCount = {}

local VehicleSeating = {}

local function Raycast(startPos, direction, range, ignore, inceptNumber)
	if inceptNumber == nil then inceptNumber = 0 end
	inceptNumber = inceptNumber + 1
	local ray = Ray.new(startPos, direction * range)
	local part, position = Workspace:FindPartOnRayWithIgnoreList(ray, ignore)
	if part then
		if part.CanCollide == false and inceptNumber <= 5 then
			--raycast again if we hit a cancollide false brick, put a limit on to prevent an infinite loop
			local rangeLeft = range - (startPos - position).magnitude
			part, position = Raycast(position, direction, rangeLeft, ignore, inceptNumber) --Raycast remaining distance.
		end
	end
	return part, position
end

local function ExitSeat(player, character, seat, weld)
	local hrp = character:FindFirstChild("HumanoidRootPart")
	if hrp then
		weld:Destroy()

		if seat:FindFirstChild("DoorHinge") then
			seat.DoorLatchWeld.Enabled = false
			seat.DoorHinge.TargetAngle = DOOR_OPEN_ANGLE
		end
		--Record the interaction
		SeatInteractionCount[seat] = SeatInteractionCount[seat] and SeatInteractionCount[seat] + 1 or 1

		wait()
		if seat:FindFirstChild("ExitPosition") then --Check if we can move the character to the designated pos.
			--Find vehicle model
			local model
			local newParent = seat
			repeat
				model = newParent
				newParent = model.Parent
			until newParent.ClassName ~= "Model"

			local targetPos = seat.ExitPosition.WorldPosition
			local delta = targetPos - seat.Position
			local dist = delta.magnitude
			local dir = delta.unit
			local part, _ = Raycast(seat.Position, dir, dist, {character, model})

			if not part then --Prevent people being CFramed into walls and stuff
				hrp.CFrame = CFrame.new(targetPos)
			else
				hrp.CFrame = CFrame.new(seat.Position)
				--The CFrame element orients the character up-right, the MoveTo stops the character from clipping into objects
				character:MoveTo(seat.Position+Vector3.new(0,8,0))
			end
		else
			hrp.CFrame = CFrame.new(seat.Position)
			character:MoveTo(seat.Position+Vector3.new(0,8,0))
		end

		RemotesFolder.ExitSeat:FireClient(player, true) --Fire this to trigger the client-side anti-trip function

		wait(DOOR_OPEN_TIME)

		SeatInteractionCount[seat] = SeatInteractionCount[seat] > 1 and SeatInteractionCount[seat] - 1 or nil
		if seat:FindFirstChild("DoorHinge") then
			--If nobody else has interactied in this time, close the door.
			if SeatInteractionCount[seat] == nil then
				seat.DoorHinge.TargetAngle = 0
				-- Weld door shut when closed
				while math.abs(seat.DoorHinge.CurrentAngle) > 0.01 do
					wait()
				end
				seat.DoorLatchWeld.Enabled = true
			end
		end
	end
end

local function FlipSeat(Player, Seat)
	if Seat then
		if Seat.Parent then
			if not Seat.Parent.Parent:FindFirstChild("Scripts") then
				warn("Flip Error: Scripts file not found. Please parent seats to the chassis model")
				return
			end
			if not Seat.Parent.Parent.Scripts:FindFirstChild("Chassis") then
				warn("Flip Error: Chassis module not found.")
				return
			end
			local Chassis = require(Seat.Parent.Parent.Scripts.Chassis)
			Chassis.Redress()
		end
	end
end

function VehicleSeating.SetRemotesFolder(remotes)
	RemotesFolder = remotes

	RemotesFolder:FindFirstChild("GetSeatTag").OnServerInvoke = function(player)
		return SeatTag
	end

	--Detect exit seat requests
	RemotesFolder:FindFirstChild("ExitSeat").OnServerEvent:Connect(function(player)
		if player.Character then
			local character = player.Character
			if player.Character:FindFirstChild("HumanoidRootPart") then
				for _, weld in pairs(character.HumanoidRootPart:GetJoints()) do
					if weld.Name == "SeatWeld" then
						ExitSeat(player, character, weld.Part0, weld)
						break
					end
				end
			end
		end
	end)

	RemotesFolder:FindFirstChild("FlipSeat").OnServerEvent:Connect(FlipSeat)

	--When a player holds down the interaction button, open the vehicle door.
	--This only serves a purpose if there are doors to open/close
	RemotesFolder:FindFirstChild("GetInStart").OnServerEvent:Connect(function(player, seat)
		if seat:FindFirstChild("DoorHinge") then
			if seat.DoorHinge.ClassName ~= "HingeConstraint" then warn("Warning, door hinge is not actually a hinge!") end

			--Record that a player is trying to get in the seat
			SeatInteractionCount[seat] = SeatInteractionCount[seat] and SeatInteractionCount[seat] + 1 or 1

			--Activate the hinge
			seat.DoorLatchWeld.Enabled = false
			seat.DoorHinge.TargetAngle = DOOR_OPEN_ANGLE
			seat.DoorHinge.AngularSpeed = DOOR_OPEN_SPEED

			wait(DOOR_OPEN_TIME)

			--Check if anyone has interacted with the door within this time. If not, close it.
			SeatInteractionCount[seat] = SeatInteractionCount[seat] > 1 and SeatInteractionCount[seat] - 1 or nil
			if SeatInteractionCount[seat] == nil then
				seat.DoorHinge.TargetAngle = 0
				-- Weld door shut when closed
				while math.abs(seat.DoorHinge.CurrentAngle) > 0.01 do
					wait()
				end
				seat.DoorLatchWeld.Enabled = true
			end
		end
	end)

	--Actually put the character in the seat when requested.
	RemotesFolder.GetInSeat.OnServerEvent:Connect(function(Player, Seat)
		if Seat then
			if not Seat:FindFirstChild("SeatWeld") then
				if Player.Character ~= nil then
					local HRP = Player.Character:FindFirstChild("HumanoidRootPart")
					local humanoid = Player.Character:FindFirstChild("Humanoid")
					if HRP then
						local Dist = (HRP.Position - Seat.Position).magnitude
						if Dist <= MAX_SEATING_DISTANCE then
							Seat:Sit(humanoid)
						end
					end
				end
			end
		end
	end)
end

function VehicleSeating.AddSeat(seat, enterCallback, exitCallback)
	CollectionService:AddTag(seat, SeatTag)

	local occupantPlayer = nil
	local occupantChangedConn = seat:GetPropertyChangedSignal("Occupant"):Connect(function()
		if seat.Occupant then
			local char = seat.Occupant.Parent
			occupantPlayer = Players:GetPlayerFromCharacter(char)
			if occupantPlayer and enterCallback then
				enterCallback(occupantPlayer, seat)
			end
		elseif occupantPlayer and exitCallback then
			exitCallback(occupantPlayer, seat)
			occupantPlayer = nil
		end
	end)

	--Clean up after the seat is destroyed
	seat.AncestryChanged:connect(function()
		if not seat:IsDescendantOf(game) then
			occupantChangedConn:Disconnect()
		end
	end)
end

return VehicleSeating