local statemachine = require("ai.statemachine")
local DL = require("design.DesignerLibrary")
local stack = require("core.stack")
local locomotion = require("creature.locomotion")
local LeashBrain = statemachine.StateMachine.New("BaldurDragon00_Leash")
local DEFAULT = LeashBrain:State("Default")
local FALLBACK_PENDING = LeashBrain:State("Fallback_Pending")
local FALLBACK = LeashBrain:State("Fallback")
local RETREAT = LeashBrain:State("Retreat")
statemachine.AddTags(FALLBACK, "LeashFallback")
statemachine.AddTags(RETREAT, "LeashRetreat")
local fallbackConstants = {}
fallbackConstants.FirstRadiusMaxDistance = 6.5
fallbackConstants.FirstRadiusMinDistance = 10.5
fallbackConstants.SecondRadiusMinDistance = 6
fallbackConstants.SecondRadiusMaxDistance = 10.5
fallbackConstants.AggressiveRange = 3
fallbackConstants.DesiredRadius = 2
local fallbackDTree = "DTR_BALDURDRAGON00_OFFENSIVE"
local Decision = {}
local returnRadius = 2
local goPlayerCreature
function LeashBrain:OnBrainInit(ai, global, constants)
  goPlayerCreature = game.Player.FindPlayer()
  global.LeashDecision = Decision
end
function LeashBrain:UpdateData(ai, global, constants)
  if not global.useLeashing then
    local zones = game.World.FindEntityZones(ai.SpawnPosition)
    for _, zone in ipairs(zones) do
      if string.find(zone, "combatzone") ~= nil then
        global.CombatZone = zone
      end
      if string.find(zone, "fallbackzone") ~= nil then
        global.FallbackZone = zone
      end
    end
    global.useLeashing = global.CombatZone ~= nil and global.FallbackZone ~= nil
  end
  if global.useLeashing then
    Decision.CreatureInCombatZone = ai:IsInsideEntityZone(global.CombatZone)
    Decision.CreatureInFallbackZone = ai:IsInsideEntityZone(global.FallbackZone) and not Decision.CreatureInCombatZone
    Decision.PlayerInCombatZone = goPlayerCreature:IsInsideEntityZone(global.CombatZone)
    Decision.PlayerInFallbackZone = goPlayerCreature:IsInsideEntityZone(global.FallbackZone) and not Decision.PlayerInCombatZone
    Decision.BothInFallbackZone = Decision.CreatureInFallbackZone and Decision.PlayerInFallbackZone
    Decision.DistanceToPlayer = (goPlayerCreature:GetWorldPosition() - ai:GetWorldPosition()):Length()
    Decision.CloseToSpawner = (ai.SpawnPosition - ai:GetWorldPosition()):Length() < 5
    Decision.NearPlayer = Decision.DistanceToPlayer < 3
    global.LeashDecision = Decision
  end
end
function DEFAULT:Enter(ai, global, constants)
  DL.BroadcastAggro(ai, global, constants)
end
local FALLBACK_TIMER = LeashBrain:Timer("Fallback_Timer") .. {period = 5, autostart = false}
function FALLBACK_TIMER.Events:Fallback_Timer_Start(timer, ai, global, constants)
  if not timer.running then
    timer:Restart()
  end
end
function FALLBACK_TIMER.Events:Fallback_Timer_Restart(timer, ai, global, constants)
  timer:Restart()
end
function FALLBACK_TIMER.Events:Fallback_Timer_Stop(timer, ai, global, constants)
  timer:Stop()
end
function FALLBACK_TIMER:OnTimerComplete(timer, ai, global, constants)
  global.doFallback = true
end
function FALLBACK_PENDING:Enter(ai, global, constants)
  self.Active = true
  statemachine.DispatchEvent("Fallback_Timer_Start")
  self.prevFightConstants = global.fightConstants
  global.fightConstants = fallbackConstants
  self.prevDTree = global.DTree
  global.DTree = fallbackDTree
end
function FALLBACK_PENDING.Events:OnHitReaction(event, ai, global, constants)
  if self.Active then
    statemachine.DispatchEvent("Fallback_Timer_Restart")
    DL.BroadcastAggro(ai, global, constants)
  end
end
function FALLBACK_PENDING:Exit(ai, global, constants)
  self.Active = false
  statemachine.DispatchEvent("Fallback_Timer_Stop")
  global.fightConstants = self.prevFightConstants
  global.DTree = self.prevDTree
end
function FALLBACK:Enter(ai, global, constants)
  self.Active = true
  global.doFallback = true
  self.spawnerPos = game.NavMesh.ClosestLocationVertically(ai.SpawnPosition) or game.NavMesh.ClosestLocation(ai.SpawnPosition, ai)
  self.fallbackDestination = self.spawnerPos:FindConnectedLocationInRadius(returnRadius)
  self.startPos = ai:GetWorldPosition()
  self.arrived = false
  self.prevDTree = global.DTree
  global.DTree = fallbackDTree
end
function FALLBACK:Update(ai, global, constants)
  if Decision.NearPlayer then
    global.doFallback = false
    return
  end
  local myPos = ai:GetWorldPosition()
  local actuatorData = {}
  actuatorData.Position = self.fallbackDestination
  actuatorData.StopDistance = global.navData.stopDistance
  actuatorData.StartDistance = global.navData.startDistance
  actuatorData.Facing = (goPlayerCreature:GetWorldPosition() - myPos):Normalized()
  if (self.startPos - myPos):Length() < 2 and global.target then
    actuatorData.Strafe = true
    actuatorData.ApproachSpeed = global.navData.navSpeedWalk
    ai:SetCombatTarget(global.target)
  elseif (self.startPos - myPos):Length() < 4 and global.target then
    actuatorData.Strafe = false
    actuatorData.ApproachSpeed = global.navData.navSpeedWalk
    ai:SetCombatTarget(global.target)
  else
    actuatorData.ApproachSpeed = global.navData.navSpeedJog
    actuatorData.Strafe = false
  end
  if (myPos - self.fallbackDestination).length <= actuatorData.StartDistance and not self.arrived then
    self.arrived = true
    global.doFallback = false
    global.aggroState = "UNAWARE"
    actuatorData.Strafe = true
    actuatorData.ApproachSpeed = global.navData.navSpeedWalk
  end
  locomotion.SetActuator(ai, {
    Destination = actuatorData.Position,
    Facing = actuatorData.Facing,
    Strafe = actuatorData.Strafe,
    Speed = actuatorData.ApproachSpeed,
    StopDistance = actuatorData.StopDistance,
    StartDistance = actuatorData.StartDistance
  })
end
function FALLBACK.Events:OnHitReaction(event, ai, global, constants)
  if self.Active then
    global.doFallback = false
    DL.BroadcastAggro(ai, global, constants)
  end
end
function FALLBACK.Events:OnBroadcastReaction(event, ai, global, constants)
  if self.Active then
    global.doFallback = false
  end
end
function FALLBACK:Exit(ai, global, constants)
  self.Active = false
  global.doFallback = false
  global.DTree = self.prevDTree
end
function RETREAT:Enter(ai, global, constants)
  self.Active = true
  global.doRetreat = true
  self.spawnerPos = game.NavMesh.ClosestLocationVertically(ai.SpawnPosition, ai) or game.NavMesh.ClosestLocation(ai.SpawnPosition, ai)
  self.fallbackDestination = self.spawnerPos:FindConnectedLocationInRadius(returnRadius)
  self.startPos = ai:GetWorldPosition()
  self.arrived = false
  self.prevDTree = global.DTree
  global.DTree = fallbackDTree
end
function RETREAT:Update(ai, global, constants)
  local myPos = ai:GetWorldPosition()
  local actuatorData = {}
  local facingVector = (goPlayerCreature:GetWorldPosition() - ai:GetWorldPosition()):Normalized()
  actuatorData.Position = self.fallbackDestination
  actuatorData.StopDistance = global.navData.stopDistance
  actuatorData.StartDistance = global.navData.startDistance
  actuatorData.ApproachSpeed = global.navData.navSpeedJog
  actuatorData.Facing = facingVector
  actuatorData.Strafe = false
  if (myPos - self.fallbackDestination).length <= actuatorData.StartDistance and not self.arrived then
    self.arrived = true
    global.doRetreat = false
    global.aggroState = "UNAWARE"
    actuatorData.Strafe = true
  end
  locomotion.SetActuator(ai, {
    Destination = actuatorData.Position,
    Facing = actuatorData.Facing,
    Strafe = actuatorData.Strafe,
    Speed = actuatorData.ApproachSpeed,
    StopDistance = actuatorData.StopDistance,
    StartDistance = actuatorData.StartDistance
  })
end
function RETREAT:Exit(ai, global, constants)
  self.Active = false
  global.doRetreat = false
  global.DTree = self.prevDTree
end
function LeashBrain:SelectNextState(ai, global, constants)
  if global.aggroState ~= "INCOMBAT" or not global.useLeashing then
    return
  end
  if not (Decision.PlayerInCombatZone or Decision.PlayerInFallbackZone) or global.doRetreat then
    return RETREAT
  end
  if global.doFallback then
    return FALLBACK
  end
  if Decision.PlayerInCombatZone or Decision.BothInFallbackZone and Decision.NearPlayer then
    return DEFAULT
  end
  if Decision.PlayerInFallbackZone and not Decision.NearPlayer then
    if not Decision.CloseToSpawner and Decision.DistanceToPlayer > 20 then
      return FALLBACK
    else
      return FALLBACK_PENDING
    end
  end
  return FALLBACK_PENDING
end
return LeashBrain
