--!strict

local LocalPlayer: Player = game:GetService("Players").LocalPlayer
local RunService = game:GetService("RunService")

local fmt = string.format

local OPT_AXIS_ATTRIBUTE = "Axis"
local OPT_AXIS = "Z"
local OPT_DEGREES_ATTRIBUTE = "Degrees"
local OPT_DEGREES = 1
local OPT_MIN_DISTANCE_ATTRIBUTE = "MinDistance"
local OPT_MIN_DISTANCE = 48

local ERR_NOT_MODEL = "Rotate expects a Model, but got %q (%s)"

local Obstacle = {}

Obstacle.__index = Obstacle

function Obstacle.new(model: Model)
	assert(model:IsA("Model"), fmt(ERR_NOT_MODEL, model:GetFullName(), model.ClassName))
	
	local self = setmetatable({}, Obstacle)
	self._events = {}
	
	self._model = model
	
	self._distance = model:GetAttribute(OPT_MIN_DISTANCE_ATTRIBUTE) or
		math.max(model:GetModelSize().Magnitude * 2, OPT_MIN_DISTANCE)
	
	self._axis = model:GetAttribute(OPT_AXIS_ATTRIBUTE) or OPT_AXIS
	self._degrees = model:GetAttribute(OPT_DEGREES_ATTRIBUTE) or OPT_DEGREES
	
	-- TODO: Defer render to global handler
	table.insert(self._events, RunService.RenderStepped:Connect(function()
		self:RenderUpdate()
	end))
	
	return self
end

function Obstacle:IsLocalPlayerNearby(): boolean
	if self._distance == 0 then
		return true
	end
	
	local distance = LocalPlayer:DistanceFromCharacter(self._model:GetPrimaryPartCFrame().Position)
	return distance < self._distance
end

function Obstacle:GetRotationCFrame(): CFrame
	local degrees = math.rad(self._degrees)
	
	if self._axis == "X" then
		return CFrame.Angles(degrees, 0, 0)
	elseif self._axis == "Y" then
		return CFrame.Angles(0, degrees, 0)
	end
	
	return CFrame.Angles(0, 0, degrees)
end

function Obstacle:RenderUpdate()
	if not self:IsLocalPlayerNearby() then
		return
	end
	
	local cframe = self._model:GetPrimaryPartCFrame()
	local angles = self:GetRotationCFrame()
	
	self._model:SetPrimaryPartCFrame(cframe * angles)
end

function Obstacle:Destroy()
	for _, event: RBXScriptConnection in ipairs(self._events) do
		event:Disconnect()
	end
end

return Obstacle
