local sharedNitro = nil

local nitroReplay = ac.ReplayStream({
  ac.StructItem.key('btm_nitro_replay_v1'),
  nitroActive   = ac.StructItem.int32(),
  nitroTimeLeft = ac.StructItem.double(),
  nitroDuration = ac.StructItem.double(),
  nitroUsesLeft = ac.StructItem.int32(),
}, function() end)

local function initSharedNitroIfNeeded()
  if sharedNitro ~= nil then return end
  sharedNitro = ac.connect({
    ac.StructItem.key('btm_nitro_audio_v1'),
    nitroPressCounter = ac.StructItem.int32(),
    nitroActive       = ac.StructItem.int32(),
    nitroTimeLeft     = ac.StructItem.double(),
    nitroDuration     = ac.StructItem.double(),
    nitroUsesLeft     = ac.StructItem.int32(),
  }, true, ac.SharedNamespace.CarScript)
end

local function isPlayerCar(c)
  return c ~= nil and c.isUserControlled == true and c.isAIControlled ~= true and c.isRemote ~= true
end

local function shouldRunForCar(c, isReplayActive)
  if isReplayActive then return c ~= nil end
  return isPlayerCar(c)
end

local CAR_INDEX = 0

local flameCfg = {
  color = rgbm(1.0, 0.55, 0.15, 1.0),
  size = 0.85,
  temperatureMultiplier = 4.0,
  flameIntensity = 2.2,
  carIndex = CAR_INDEX
}

local sparksCfg = {
  color = rgbm(1.0, 0.35, 0.05, 1.0),
  life = 0.10,
  size = 0.12,
  directionSpread = 0.6,
  positionSpread = -0.05
}

local fire  = ac.Particles.Flame(flameCfg)
local spark = ac.Particles.Sparks(sparksCfg)

local EXHAUST_L = vec3(-0.377, 0.215, -1.45)
local EXHAUST_R = vec3( 0.377, 0.215, -1.45)

local emitAccum = 0.0
local lastKnownDuration = 0.0

local function clamp(x, a, b)
  if x < a then return a end
  if x > b then return b end
  return x
end

function script.update(dt)
  local sim = ac.getSim()
  local isReplay = sim and sim.isReplayActive == true

  local c = car
  if not shouldRunForCar(c, isReplay) then
    emitAccum = 0.0
    return
  end

  if not isReplay then
    initSharedNitroIfNeeded()
  end

  local active   = (nitroReplay.nitroActive or 0) ~= 0
  local timeLeft = nitroReplay.nitroTimeLeft or 0.0
  local duration = nitroReplay.nitroDuration or 0.0
  local usesLeft = nitroReplay.nitroUsesLeft or 0

  if duration > 0.0 then
    lastKnownDuration = duration
  elseif lastKnownDuration > 0.0 then
    duration = lastKnownDuration
  end

  if (usesLeft <= 0) and (not active) then
    emitAccum = 0
    return
  end

  if (not active) or (timeLeft <= 0.0) or (duration <= 0.0) then
    emitAccum = 0
    return
  end

  local tNorm = clamp(timeLeft / duration, 0.0, 1.0)
  local rampIn  = clamp((duration - timeLeft) / 0.12, 0.0, 1.0)
  local rampOut = clamp(timeLeft / 0.18, 0.0, 1.0)
  local ramp = rampIn * rampOut

  local zBoost = (c.speedKmh or 0.0) / 300.0

  emitAccum = emitAccum + dt
  local step = 1.0 / 120.0
  if emitAccum < step then return end

  while emitAccum >= step do
    emitAccum = emitAccum - step

    local dir = vec3(0, 0, -2)

    local startPunch = 0.35 + 0.65 * tNorm
    local intensity = (1.25 * ramp) * startPunch

    local pL = vec3(EXHAUST_L.x, EXHAUST_L.y, EXHAUST_L.z + zBoost)
    local pR = vec3(EXHAUST_R.x, EXHAUST_R.y, EXHAUST_R.z + zBoost)

    fire:emit(pL, dir, intensity)
    fire:emit(pR, dir, intensity)

    if (math.random() < 0.55) then
      spark:emit(pL, dir, intensity * 0.9)
      spark:emit(pR, dir, intensity * 0.9)
    end
  end
end