local gamepadAngle = 0
local baseSteer = 0
local ffbSmooth = 0
local offsetSmooth = 0
local collisionCooldown = 0

local function vibratingMechanism(minDelay, maxDelay)
	local active = 0
	local wait = 0
	return function (baseValue, value)
		if value < 0.1 then
			active = 0
			wait = 0
			return baseValue
		end
		if active > 0 then
			active = active - 1
			return 0.3
		end
		if wait > 0 then
			wait = wait - 1
		else
			active = 1
			wait = math.lerp(maxDelay or 10, minDelay or 0, value)
		end
		return baseValue
	end
end

local vibSlide = vibratingMechanism(0, 4)
local vibCurb = vibratingMechanism(3, 6)
local vibDirt = vibratingMechanism(4, 10)

---@param state ac.StateJoypadData
---@param driftMode boolean
---@param step integer
---@param dt number
return function(state, driftMode, step, dt)
  local ds = ac.getDualSense(state.gamepadIndex)
  if ds == nil then return end

  -- ac.debug('gyroscope', ds.gyroscope)
  -- ac.debug('accelerometer', ds.accelerometer)

  -- Adding accelerometer to steering  
  local accDir = ds.gyroscope:clone():normalize()
  local accAngle = -math.atan2(accDir.x, #vec2(accDir.y, accDir.z)) * 0.2    
  local fastTurning = math.pow(math.saturateN(math.abs(ds.accelerometer.z * 1000 / 3.1415 * 0.03) - 0.1), 3)
  local fastTurningLag = math.lerp(0.95, 0.5, fastTurning)
  gamepadAngle = gamepadAngle - ds.accelerometer.z * 1400 * dt * 0.0037
  gamepadAngle = math.applyLag(gamepadAngle, accAngle, fastTurningLag, dt)

  local gammed = driftMode
    and math.tan(gamepadAngle * 3.5) / 3.5
    or math.tan(gamepadAngle * 4) / 4
  local edgeLag = 0.9 * math.saturateN(math.abs(baseSteer) * 4)
  baseSteer = math.applyLag(baseSteer, gammed, edgeLag, dt)
	local finalSteer = baseSteer * ScriptSettings.SENSITIVITY * 2

  ffbSmooth = math.applyLag(ffbSmooth, state.ffb, 0.9, dt)
  local ffbSteer = driftMode
    and finalSteer + ffbSmooth * -0.12 * math.lerp(1, 0, math.min(1, math.abs(finalSteer) * 3) ^ 3)
    or finalSteer + ffbSmooth * -0.06

  local offsetMult = math.lerpInvSat(state.speedKmh, 5, 10)
  local carDirection = math.normalize(car.velocity)
  local drivingFwd = math.dot(carDirection, car.look)
  local offsetSteer = offsetMult > 0 and -math.dot(carDirection, car.side) or 0
  if drivingFwd < 0 then
    offsetSteer = math.sign(offsetSteer) * math.lerpInvSat(drivingFwd, -1, -0.85)
  else
    offsetSteer = math.smoothstep(math.abs(offsetSteer)) * math.sign(offsetSteer)
  end
  offsetSmooth = math.applyLag(offsetSmooth, offsetMult * offsetSteer, 0.7, dt)
  state.steer = driftMode
    and math.clampN((state.steer + ffbSteer + offsetSmooth * 0.2) * 900 / car.steerLock, -1, 1)
    or math.clampN((state.steer + ffbSteer + offsetSmooth * 0) * 500 / car.steerLock, -1, 1)

  -- Analyzing surface for estimating vibrations
  local speedMult = math.lerpInvSat(state.speedKmh, 20, 60)
  local dirt, ndSlip = 0, 0
  for i = 0, 1 do
    local mult = car.wheels[i].loadK
    dirt = dirt + car.wheels[i].surfaceDirt * mult
    ndSlip = math.max(ndSlip, car.wheels[i].ndSlip * mult)
  end

  -- Minor vibrations on the right
	state.vibrationRight = 0
    local wheelLF = math.abs(math.dot(math.normalize(car.wheels[0].velocity), car.wheels[0].look))
    local wheelRF = math.abs(math.dot(math.normalize(car.wheels[1].velocity), car.wheels[1].look))
    state.vibrationRight = vibSlide(state.vibrationRight, math.abs(state.ffb) 
      * math.lerpInvSat(car.speedKmh, 2, 5)
      * math.pow(math.sqrt(1 - math.pow(math.min(wheelLF, wheelRF), 2)) * 3, 1.5))

  -- Major vibrations on the left
	state.vibrationLeft = 0
  state.vibrationLeft = vibCurb(state.vibrationLeft, speedMult 
  * math.max(state.surfaceVibrationGainLeft, state.surfaceVibrationGainRight) * 2)
  state.vibrationLeft = vibDirt(state.vibrationLeft, dirt * speedMult * 0.2)

  -- Ton of vibrations on active collision
	if car.collisionDepth > 0 and car.speedKmh > 10 then
    if collisionCooldown <= 0 then
      state.vibrationLeft = 1
      state.vibrationRight = 1
    end
    collisionCooldown = 0.1
  elseif collisionCooldown > 0 then
    collisionCooldown = collisionCooldown - dt
	end

  -- Resistive triggers
	if ScriptSettings.TRIGGERS_FEEDBACK then
    local brakeForce = car.absInAction and 0 or car.brake
    local force = math.lerpInvSat(car.rpm, car.rpmLimiter - 100, car.rpmLimiter - 50)
    ac.setDualSenseTriggerContinuousResitanceEffect(0, 0, brakeForce * 0.25)
		ac.setDualSenseTriggerContinuousResitanceEffect(1, 0, force * 0.1)
	else
		ac.setDualSenseTriggerNoEffect(0)
		ac.setDualSenseTriggerNoEffect(1)
	end
end

