-- Sword Bot Script

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

local localPlayer = Players.LocalPlayer
local character = localPlayer.Character or localPlayer.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid")
local rootPart = character:WaitForChild("HumanoidRootPart")

-- Configuration
local APPROACH_DISTANCE = 5      
local ATTACK_DISTANCE = 7        
local PATH_UPDATE_INTERVAL = 0.4 
local STUCK_JUMP_COOLDOWN = 1.5  -- Stuck jump cooldown
local VOID_THRESHOLD = -100       -- Y position for void recovery
local SAFE_POS_UPDATE_INTERVAL = 1.0 -- Safe position save interval

-- Excluded targets
local EXCLUDED_TARGETS = {
	["Tellexi"] = true,
}

-- State
local waypoints = {}
local currentWaypointIndex = 2
local timeSinceLastPath = 0
local lastStuckJump = 0
local isComputingPath = false

-- Void recovery
local lastSafeCFrame = nil
local timeSinceLastSafeSave = 0
local isRecoveringFromVoid = false

-- Utility functions

local function isInvincible(char)
	return char:FindFirstChildOfClass("ForceField") ~= nil
end

local function equipFirstHotbarItem()
	local backpack = localPlayer:FindFirstChild("Backpack")
	if not backpack then return end

	local currentTool = character:FindFirstChildOfClass("Tool")
	local items = backpack:GetChildren()
	local firstItem = nil
	
	for _, item in ipairs(items) do
		if item:IsA("Tool") then
			firstItem = item
			break
		end
	end

	if firstItem and not currentTool then
		humanoid:EquipTool(firstItem)
	end
end

local function getNearest(): Player?
	local nearest = nil
	local shortestDist = math.huge

	for _, player in ipairs(Players:GetPlayers()) do
		if player == localPlayer then continue end
		if EXCLUDED_TARGETS[player.Name] then continue end  -- Skip excluded

		local char = player.Character
		if not char or not char:FindFirstChild("HumanoidRootPart") then continue end
		local hum = char:FindFirstChild("Humanoid")
		
		if not hum or hum.Health <= 0 or isInvincible(char) then 
			continue 
		end

		local dist = (rootPart.Position - char.HumanoidRootPart.Position).Magnitude
		if dist < shortestDist then
			shortestDist = dist
			nearest = player
		end
	end
	return nearest
end

-- Main loop

RunService.Heartbeat:Connect(function(dt)
	-- Character check
	if not character or not character.Parent then
		character = localPlayer.Character
		if character then
			humanoid = character:WaitForChild("Humanoid")
			rootPart = character:WaitForChild("HumanoidRootPart")
		end
		return
	end

	if humanoid.Health <= 0 then return end

	-- Void recovery - save safe CFrame periodically
	if rootPart.Position.Y > VOID_THRESHOLD and humanoid.FloorMaterial ~= Enum.Material.Air then
		timeSinceLastSafeSave += dt
		if timeSinceLastSafeSave >= SAFE_POS_UPDATE_INTERVAL then
			timeSinceLastSafeSave = 0
			lastSafeCFrame = rootPart.CFrame
		end
	end

	-- Detect void fall and teleport back
	if rootPart.Position.Y < VOID_THRESHOLD and not isRecoveringFromVoid then
		isRecoveringFromVoid = true
		if lastSafeCFrame then
			rootPart.CFrame = lastSafeCFrame
		end
		-- Clear stale waypoints
		waypoints = {}
		currentWaypointIndex = 2
		task.delay(0.2, function()
			isRecoveringFromVoid = false
		end)
		return
	end

	if isRecoveringFromVoid then return end

	equipFirstHotbarItem()

	local target = getNearest()
	if not target or not target.Character then 
		humanoid:Move(Vector3.new(0,0,0))
		return 
	end

	local targetHRP = target.Character:FindFirstChild("HumanoidRootPart")
	local distance = (targetHRP.Position - rootPart.Position).Magnitude

	-- Stuck detection - jump if moving but stuck
	if distance > APPROACH_DISTANCE then
		local horizontalVelocity = rootPart.AssemblyLinearVelocity * Vector3.new(1, 0, 1)
		local currentSPS = horizontalVelocity.Magnitude

		if currentSPS < 1 
			and humanoid.FloorMaterial ~= Enum.Material.Air -- Must be grounded
			and (tick() - lastStuckJump) > STUCK_JUMP_COOLDOWN then
			
			humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			lastStuckJump = tick()
		end
	end

	-- Attack
	if distance <= ATTACK_DISTANCE then
		local targetPos = Vector3.new(targetHRP.Position.X, rootPart.Position.Y, targetHRP.Position.Z)
		rootPart.CFrame = CFrame.lookAt(rootPart.Position, targetPos)
		
		local tool = character:FindFirstChildOfClass("Tool")
		if tool then tool:Activate() end
	end

	-- Pathfinding
	if distance > APPROACH_DISTANCE then
		timeSinceLastPath += dt
		
		if timeSinceLastPath >= PATH_UPDATE_INTERVAL and not isComputingPath then
			timeSinceLastPath = 0
			isComputingPath = true
			
			task.spawn(function()
				local path = PathfindingService:CreatePath({AgentCanJump = true, WaypointSpacing = 4})
				local success, _ = pcall(function() path:ComputeAsync(rootPart.Position, targetHRP.Position) end)
				if success and path.Status == Enum.PathStatus.Success then
					waypoints = path:GetWaypoints()
					currentWaypointIndex = 2
				end
				isComputingPath = false
			end)
		end

		if #waypoints > 0 and currentWaypointIndex <= #waypoints then
			local targetWaypoint = waypoints[currentWaypointIndex]
			humanoid:MoveTo(targetWaypoint.Position)

			-- Jump at waypoint
			if targetWaypoint.Action == Enum.PathWaypointAction.Jump and humanoid.FloorMaterial ~= Enum.Material.Air then
				humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end

			if (rootPart.Position - targetWaypoint.Position).Magnitude < 4 then
				currentWaypointIndex += 1
			end
		else
			humanoid:MoveTo(targetHRP.Position)
		end
	else
		humanoid:MoveTo(rootPart.Position) 
	end
end)
