local profile = require("core.profile")
local PlayMove = function(ai, puppeteerObj, move)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI Play Move", ai)
  aiPuppeteer:StartMove(move)
  aiPuppeteer:OnComplete(function()
    aiPuppeteer:Clear()
    aiPuppeteer = nil
  end)
end
local Advertise_PlayMove = function(ai, puppeteerObj, move)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI Play Move", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "1", "")
  aiName = string.gsub(aiName, "0", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    aiPuppeteer:StartMove(move)
    aiPuppeteer:OnComplete(function()
      aiPuppeteer:Clear()
      aiPuppeteer = nil
    end)
  end)
end
local AlignAndPlayMove = function(ai, puppeteerObj, move, jointName)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI Align and Play Move", ai)
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Align({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      aligntime = 0.2,
      facing = puppeteerObj:GetWorldJointForward(puppeteerObj:GetJointIndex(jointName))
    })
    aiPuppeteer:OnComplete(function()
      aiPuppeteer:StartMove(move)
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end)
    end)
  else
    aiPuppeteer:Align({
      pos = puppeteerObj:GetWorldPosition(),
      aligntime = 0.2,
      facing = puppeteerObj:GetWorldForward()
    })
    aiPuppeteer:OnComplete(function()
      aiPuppeteer:StartMove(move)
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end)
    end)
  end
end
local Advertise_AlignAndPlayMove = function(ai, puppeteerObj, move, jointName)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI AlignAndPlayMove", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "1", "")
  aiName = string.gsub(aiName, "0", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Align({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        aligntime = 0.2,
        facing = puppeteerObj:GetWorldJointForward(puppeteerObj:GetJointIndex(jointName))
      })
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end)
    else
      aiPuppeteer:Align({
        pos = puppeteerObj:GetWorldPosition(),
        aligntime = 0.2,
        facing = puppeteerObj:GetWorldForward()
      })
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end)
    end
  end)
end
local GoToLocationAndPlayMove = function(ai, puppeteerObj, move, runSpeed, jointName)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI go to location & play move", ai)
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:StartMove(move)
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end)
    end, {
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      radius = 1
    })
  else
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldPosition(),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:StartMove(move)
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:Clear()
        aiPuppeteer = nil
      end)
    end, {
      pos = puppeteerObj:GetWorldPosition(),
      radius = 1
    })
  end
end
local Advertise_GoToLocationAndPlayMove = function(ai, puppeteerObj, move, runSpeed, jointName)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI GoToLocationAndPlayMove", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "1", "")
  aiName = string.gsub(aiName, "0", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end, {
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        radius = 1
      })
    else
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldPosition(),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = 1
      })
    end
  end)
end
local GoToLocationAlignAndPlayMove = function(ai, puppeteerObj, move, runSpeed, jointName)
  local aiPuppeteer = game.Puppeteer.NewForce(puppeteerObj, "LDLib - AI Go To Location, Align, and Play Move", ai)
  if jointName ~= "" and jointName ~= nil then
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Align({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        aligntime = 0.2,
        facing = puppeteerObj:GetWorldJointForward(puppeteerObj:GetJointIndex(jointName))
      })
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end)
    end, {
      pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
      radius = 1
    })
  else
    aiPuppeteer:Approach({
      pos = puppeteerObj:GetWorldPosition(),
      speed = runSpeed or 1,
      stop = true
    })
    aiPuppeteer:OnArrival(function()
      aiPuppeteer:Align({
        pos = puppeteerObj:GetWorldPosition(),
        aligntime = 0.2,
        facing = puppeteerObj:GetWorldForward()
      })
      aiPuppeteer:OnComplete(function()
        aiPuppeteer:StartMove(move)
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:Clear()
          aiPuppeteer = nil
        end)
      end)
    end, {
      pos = puppeteerObj:GetWorldPosition(),
      radius = 1
    })
  end
end
local Advertise_GoToLocationAlignAndPlayMove = function(ai, puppeteerObj, move, runSpeed, jointName)
  local aiPuppeteer = game.Puppeteer.New(puppeteerObj, "LDLib - Advertise AI GoToLocationAlignAndPlayMove", ai)
  local aiName = ai:GetName()
  aiName = string.gsub(aiName, "1", "")
  aiName = string.gsub(aiName, "0", "")
  aiName = string.upper(aiName)
  aiPuppeteer:Advertise(aiName .. ":AUTO")
  aiPuppeteer:OnEngaged(function()
    if jointName ~= "" and jointName ~= nil then
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Align({
          pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
          aligntime = 0.2,
          facing = puppeteerObj:GetWorldJointForward(puppeteerObj:GetJointIndex(jointName))
        })
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:StartMove(move)
          aiPuppeteer:OnComplete(function()
            aiPuppeteer:Clear()
            aiPuppeteer = nil
          end)
        end)
      end, {
        pos = puppeteerObj:GetWorldJointPosition(puppeteerObj:GetJointIndex(jointName)),
        radius = 1
      })
    else
      aiPuppeteer:Approach({
        pos = puppeteerObj:GetWorldPosition(),
        speed = runSpeed or 1,
        stop = true
      })
      aiPuppeteer:OnArrival(function()
        aiPuppeteer:Align({
          pos = puppeteerObj:GetWorldPosition(),
          aligntime = 0.2,
          facing = puppeteerObj:GetWorldForward()
        })
        aiPuppeteer:OnComplete(function()
          aiPuppeteer:StartMove(move)
          aiPuppeteer:OnComplete(function()
            aiPuppeteer:Clear()
            aiPuppeteer = nil
          end)
        end)
      end, {
        pos = puppeteerObj:GetWorldPosition(),
        radius = 1
      })
    end
  end)
end
local SetAnimatedMaterialAttributes = function(myObject, myMaterialName, myLayerName, materialAttributes)
  local object = myObject
  local materialName = myMaterialName or "BasicAnimatableMaterial"
  local layerName = myLayerName or "LayerX"
  if materialAttributes.Color ~= nil then
    local myColor = object:GetMaterialEntry(materialName, layerName .. ".cst_Tint")
    myColor.Value = materialAttributes.Color
  end
  if materialAttributes.AlphaBlend ~= nil then
    local myAlphaBlend = object:GetMaterialEntry(materialName, layerName .. ".cst_AlphaBlendAmount")
    myAlphaBlend.Value = materialAttributes.AlphaBlend
  end
  if materialAttributes.EmissiveColor ~= nil then
    local myEmissiveColor = object:GetMaterialEntry(materialName, layerName .. ".cst_EmissiveTint")
    myEmissiveColor.Value = materialAttributes.EmissiveColor
  end
  if materialAttributes.EmissiveIntensity ~= nil then
    local myEmissiveIntensity = object:GetMaterialEntry(materialName, layerName .. ".cst_EmissiveIntensity")
    myEmissiveIntensity.Value = materialAttributes.EmissiveIntensity
  end
  if materialAttributes.AlphaValue ~= nil then
    local myAlphaValue = object:GetMaterialEntry(materialName, layerName .. ".cst_AlphaReference")
    if myMaterialName == "BasicAnimatableMaterial" then
      if myAlphaValue.Value <= 0 then
        myAlphaValue.Value = 0
      elseif myAlphaValue.Value >= 1 then
        myAlphaValue.Value = 1
      end
      myAlphaValue.Value = 255 * (1 - materialAttributes.AlphaValue)
    else
      myAlphaValue.Value = materialAttributes.AlphaValue
    end
  end
end
local C_AXE_TARGET_DEFAULTS = {
  zoomAngle = 15,
  maxZoomRadius = 2.5,
  strafeAngle = 12,
  maxStrafeRadius = 1.4
}
local AddAxeTarget = function(obj, jointName, zoomAngle, maxZoomRadius, strafeAngle, maxStrafeRadius, passThroughTarget)
  return obj:AddAimAssist({
    Joint = jointName,
    Angle = zoomAngle or C_AXE_TARGET_DEFAULTS.zoomAngle,
    MaxRadius = maxZoomRadius or C_AXE_TARGET_DEFAULTS.maxZoomRadius,
    Group = "AXE",
    ZoomSnapScreenAngle = zoomAngle or C_AXE_TARGET_DEFAULTS.zoomAngle,
    ZoomSnapRadius = maxZoomRadius or C_AXE_TARGET_DEFAULTS.maxZoomRadius,
    AimSphereScreenAngle = strafeAngle or C_AXE_TARGET_DEFAULTS.strafeAngle,
    AimSphereRadius = maxStrafeRadius or C_AXE_TARGET_DEFAULTS.maxStrafeRadius,
    VisibilityRadius = 0.01,
    MaxDistance = 100
  })
end
local RemoveAxeTarget = function(obj, jointName, zoomAngle, maxZoomRadius, strafeAngle, maxStrafeRadius, passThroughTarget)
  obj:RemoveAimAssist({
    Joint = jointName,
    Angle = zoomAngle or C_AXE_TARGET_DEFAULTS.zoomAngle,
    MaxRadius = maxZoomRadius or C_AXE_TARGET_DEFAULTS.maxZoomRadius,
    Group = "AXE"
  })
end
local ObjectIsInLight = function(go)
  local lightRange = 20
  local lightAngle = 35
  local player = game.Player.FindPlayer()
  if not player:HasMarker("BifrostPrismBlast") then
    return false
  end
  local offset = 0
  local lowerArmIndex = player:GetJointIndex("JOMeleeWeapon2")
  local pos = player:GetWorldJointPosition(lowerArmIndex)
  local startDir = player:GetWorldJointForward(lowerArmIndex)
  local finalDir = startDir:RotateXZ(-100)
  pos = pos + finalDir * offset
  engine.DrawCone(pos, finalDir, lightAngle, lightRange, 16777215)
  local isInCone = game.AIUtil.IntersectPointCone(go:GetWorldPosition(), pos, finalDir, lightAngle, lightRange)
  if not isInCone then
    return false
  end
  local collisionParams = {
    SourceGameObject = player,
    ExcludeGameObject = player,
    RejectMaterialFlags = game.GroundType.New("kGT_CosmeticIK", "kGT_Death", "kGT_InstantDeath", "kGT_NoTargetThru")
  }
  local hit = game.World.RaycastCollision(pos, go.WorldPosition, collisionParams)
  if not hit or hit.GameObject ~= go then
    return false
  end
  return true
end
local ObjectHitByBifrost = function(obj)
  local heroLevel = game.Level.GetHeroPersistentLevel()
  if heroLevel ~= nil then
    local bifrostPrismaticProjectiles = heroLevel:FindGameObjects("bifrost_projectile*")
    for i = 1, #bifrostPrismaticProjectiles do
      if bifrostPrismaticProjectiles[i]:InsideZone(obj) then
        return true
      end
    end
  end
end
local ObjectIsInAlfLight = function(go, level)
  local alfheimCrystals = levelobjs[go].GetAlfCrystals()
  for _, crystal in ipairs(alfheimCrystals) do
    if levelobjs[crystal].CrystalIsFull() and crystal:FindLuaTableAttribute("CrystalRange") ~= nil then
      local crystalInfluenceRange = crystal:FindLuaTableAttribute("CrystalRange")
      local crystalPos = crystal.WorldPosition
      local myPos = go.WorldPosition
      if crystalInfluenceRange > math.abs(crystalPos.x - myPos.x) and crystalInfluenceRange > math.abs(crystalPos.z - myPos.z) then
        local distanceBetweenCrystals = game.AIUtil.Distance(crystalPos, myPos)
        if crystalInfluenceRange >= distanceBetweenCrystals and 0.01 < distanceBetweenCrystals then
          local hit = game.World.RaycastCollision(crystalPos, myPos)
          if not hit then
            return false
          elseif hit and hit.GameObject == go then
            return true
          end
        end
      end
    end
  end
  return false
end
return profile.WrapLibrary({
  PlayMove = PlayMove,
  Advertise_PlayMove = Advertise_PlayMove,
  AlignAndPlayMove = AlignAndPlayMove,
  Advertise_AlignAndPlayMove = Advertise_AlignAndPlayMove,
  GoToLocationAndPlayMove = GoToLocationAndPlayMove,
  Advertise_GoToLocationAndPlayMove = Advertise_GoToLocationAndPlayMove,
  GoToLocationAlignAndPlayMove = GoToLocationAlignAndPlayMove,
  Advertise_GoToLocationAlignAndPlayMove = Advertise_GoToLocationAlignAndPlayMove,
  SetAnimatedMaterialAttributes = SetAnimatedMaterialAttributes,
  AddAxeTarget = AddAxeTarget,
  RemoveAxeTarget = RemoveAxeTarget,
  ObjectIsInLight = ObjectIsInLight,
  ObjectHitByBifrost = ObjectHitByBifrost,
  ObjectIsInAlfLight = ObjectIsInAlfLight
})
