local P=require("\105\110\118\111\107\097\116\105\111\110\046\100\111\116\097\050\046\073\110\118\111\107\101\114")local E=require("\105\110\118\111\107\097\116\105\111\110\046\076\111\103\103\101\114")local C=require("\112\108\046\112\114\101\116\116\121")local u=require("\112\108\046\116\097\098\108\101\120")local F=require("\105\110\118\111\107\097\116\105\111\110\046\099\111\110\115\116\046\099\111\109\109\097\110\100\115")local function Z(P,E)return function(C,...)P:d(C,E,...)local u=P:methodHandler(E)local F=Convars:GetDOTACommandClient()return u(F,...)end end function GameMode.registerCommand(F,P,E,C,u)return Convars:RegisterCommand(P,Z(F,E),C,u or 0)end function GameMode.registerCommands(P)for E,C in ipairs(F)do if not C.dev or C.dev and P.env.development then P:registerCommand(C.name,C.method,C.help,C.flags)end end P:d("  \114\101\103\105\115\116\101\114 \099\111\109\109\097\110\100\115")end function GameMode.CommandSetDebug(u,P,C)if C=="\049"then u.logger.level=E.DEBUG elseif C=="\048"then u.logger.level=E.INFO else print(string.format("\105\110\118\095\100\101\098\117\103 \061 \037\100",u.logger.level<=E.DEBUG and 1 or 0))end end function GameMode.CommandDebugMisc(E,P,...) end function GameMode.CommandDumpLuaVersion(P)print(_VERSION)end function GameMode.CommandDumpGlobal(u,P,E)if not E then return end local F=_G for P in E:gmatch("\040\091\094\046\093\043\041\037\046\063")do F=F[P]end local Z=type(F)local r=C.write(F)print(string.format("\037\113 \040\037\115\041\058 \037\115",E,Z,r))if Z=="\102\117\110\099\116\105\111\110"then local P=debug.getinfo(F)print(string.format("\115\111\117\114\099\101\058 \037\115\058\037\100",P.source,P.linedefined))end end function GameMode.CommandFindGlobal(C,P,E)if not E then return end local u={}for P,C in pairs(_G)do if P:match(E)then table.insert(u,P)end end table.sort(u)print(string.format("\071\108\111\098\097\108\115 \109\097\116\099\104\105\110\103 \112\097\116\116\101\114\110 \037\113\058",E))for P,E in ipairs(u)do print(string.format(" \042 \037\115",E))end print("---")end function GameMode.CommandItemQuery(F,P,E)if not E then return end local Z=F.itemsKV:Search(E)if u.size(Z)==0 then print("\078\111 \105\116\101\109\115 \102\111\117\110\100\046")return end for P,E in pairs(Z)do local u=C.write(E)print(P,u)end end local function r(P,E)if P==nil then return nil end if E then return{P:GetAbilityIndex(),P:GetAbilityName(),P:GetLevel()}end return{name=P:GetAbilityName(),index=P:GetAbilityIndex();type=P:GetAbilityType(),level=P:GetLevel(),maxLevel=P:GetMaxLevel(),damage=P:GetAbilityDamage();damageType=P:GetAbilityDamageType(),isCastable=P:IsFullyCastable();isActivated=P:IsActivated(),isHidden=P:IsHidden();isAttributeBonus=P:IsAttributeBonus();isItem=P:IsItem(),isPassive=P:IsPassive();procsMagicStick=P:ProcsMagicStick(),isTrained=P:IsTrained(),canBeUpgraded=P:CanAbilityBeUpgraded();heroLevelToUpgrade=P:GetHeroLevelRequiredToUpgrade(),duration=P:GetDuration();castRange=P:GetCastRange(),castPoint=P:GetCastPoint(),backswingTime=P:GetBackswingTime();channelTime=P:GetChannelTime()}end function GameMode.CommandDumpAbilities(u,P,E)local F=P:GetAssignedHero()for P=0,F:GetAbilityCount()-1,1 do local u=F:GetAbilityByIndex(P)local Z=C.write(r(u,E))print(P,Z)end end function GameMode.CommandInvokeAbility(u,E,C)if not C then return end local F=E:GetAssignedHero()local Z=P(F)Z:Invoke(C)end function GameMode.CommandDumpComboGraph(C,P,E)if not E then return end local u=C.combos:createCombo(tonumber(E))if u==nil then print("\067\111\117\108\100 \110\111\116 \102\105\110\100 \099\111\109\098\111")return end print(u:todot())end function GameMode.CommandChangeMusicStatus(u,P,E,C)if not E or not C then return end E=tonumber(E)C=tonumber(C)P:SetMusicStatus(E,C)end function GameMode.CommandDumpSpecials(C,P,E)local u=require("\105\110\118\111\107\097\116\105\111\110\046\103\097\109\101\095\109\111\100\101\046\099\111\109\109\097\110\100\115\046\100\117\109\112\095\115\112\101\099\105\097\108\115")u.dump(P,{onlyScaling=E~=nil})end function GameMode.CommandDebugSpecials(u,P,E,C)local F=require("\105\110\118\111\107\097\116\105\111\110\046\103\097\109\101\095\109\111\100\101\046\099\111\109\109\097\110\100\115\046\100\101\098\117\103\095\115\112\101\099\105\097\108\115")if E=="\100\117\109\112"then F.dump(C)elseif E=="\102\105\110\100\075\101\121\115"then F.findKeys(C)elseif E=="\102\105\110\100\086\097\108\117\101\115"then F.findValues(C)end end function GameMode.CommandReinsertAbility(C,P,E)if not E then return end local u=P:GetAssignedHero()local F=u:FindAbilityByName(E)local Z=F:GetAbilityIndex()local r=F:GetLevel()u:RemoveAbility(E)F=u:AddAbility(E)F:SetAbilityIndex(Z)F:SetLevel(r)end
--- Console Commands
-- @submodule invokation.GameMode

local Invoker = require("invokation.dota2.Invoker")
local Logger = require("invokation.Logger")
local pp = require("pl.pretty")
local tablex = require("pl.tablex")

local COMMANDS = require("invokation.const.commands")

--- Console Commands
-- @section commands

local function createHandler(gameMode, methodName)
  return function(command, ...)
    gameMode:d(command, methodName, ...)

    local callback = gameMode:methodHandler(methodName)
    local player = Convars:GetDOTACommandClient()

    return callback(player, ...)
  end
end

function GameMode:registerCommand(command, methodName, help, flags)
  return Convars:RegisterCommand(command, createHandler(self, methodName), help, flags or 0)
end

function GameMode:registerCommands()
  for _, spec in ipairs(COMMANDS) do
    if not spec.dev or (spec.dev and self.env.development) then
      self:registerCommand(spec.name, spec.method, spec.help, spec.flags)
    end
  end

  self:d("  register commands")
end

--- Sets debugging on/off.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam ?string arg `"1"`: enables debugging, `"0"`: disables debugging,
--   `nil`: prints debugging value.
function GameMode:CommandSetDebug(_player, arg)
  if arg == "1" then
    self.logger.level = Logger.DEBUG
  elseif arg == "0" then
    self.logger.level = Logger.INFO
  else
    print(string.format("inv_debug = %d", self.logger.level <= Logger.DEBUG and 1 or 0))
  end
end

--- Placeholder command to run miscellaneous debug code.
--
-- Use `script_reload` to reload after changes.
--
-- @tparam CDOTAPlayer player Player who issued this console command.
-- @param[opt] ... varargs.
-- selene: allow(unused_variable)
function GameMode:CommandDebugMisc(_player, ...) end

--- Dumps Lua version.
-- @tparam CDOTAPlayer player Player who issued this console command
function GameMode:CommandDumpLuaVersion()
  print(_VERSION)
end

--- Dumps global value.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string name Dot-separated value name
function GameMode:CommandDumpGlobal(_player, name)
  if not name then
    return
  end

  -- selene: allow(global_usage)
  local value = _G

  for segment in name:gmatch("([^.]+)%.?") do
    value = value[segment]
  end

  local typ = type(value)
  local repr = pp.write(value)

  print(string.format("%q (%s): %s", name, typ, repr))

  if typ == "function" then
    local info = debug.getinfo(value)
    print(string.format("source: %s:%d", info.source, info.linedefined))
  end
end

--- Searches a global value.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string pattern Name pattern (uses `string.match` for matching)
function GameMode:CommandFindGlobal(_player, pattern)
  if not pattern then
    return
  end

  local matches = {}

  -- selene: allow(global_usage)
  for name, _ in pairs(_G) do
    if name:match(pattern) then
      table.insert(matches, name)
    end
  end

  table.sort(matches)

  print(string.format("Globals matching pattern %q:", pattern))

  for _, match in ipairs(matches) do
    print(string.format(" * %s", match))
  end

  print("---")
end

--- Queries items.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string query Query string
function GameMode:CommandItemQuery(_player, query)
  if not query then
    return
  end

  local items = self.itemsKV:Search(query)

  if tablex.size(items) == 0 then
    print("No items found.")
    return
  end

  for name, kv in pairs(items) do
    local repr = pp.write(kv)
    print(name, repr)
  end
end

local function debugAbility(a, simple)
  if a == nil then
    return nil
  end

  if simple then
    return { a:GetAbilityIndex(), a:GetAbilityName(), a:GetLevel() }
  end

  return {
    name = a:GetAbilityName(),
    index = a:GetAbilityIndex(),
    type = a:GetAbilityType(),
    level = a:GetLevel(),
    maxLevel = a:GetMaxLevel(),
    damage = a:GetAbilityDamage(),
    damageType = a:GetAbilityDamageType(),
    isCastable = a:IsFullyCastable(),
    isActivated = a:IsActivated(),
    isHidden = a:IsHidden(),
    isAttributeBonus = a:IsAttributeBonus(),
    isItem = a:IsItem(),
    isPassive = a:IsPassive(),
    procsMagicStick = a:ProcsMagicStick(),
    isTrained = a:IsTrained(),
    canBeUpgraded = a:CanAbilityBeUpgraded(),
    heroLevelToUpgrade = a:GetHeroLevelRequiredToUpgrade(),
    duration = a:GetDuration(),
    castRange = a:GetCastRange(),
    castPoint = a:GetCastPoint(),
    backswingTime = a:GetBackswingTime(),
    channelTime = a:GetChannelTime(),
  }
end

--- Dumps current hero abilities.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam[opt] string simple Simple version or verbose
function GameMode:CommandDumpAbilities(player, simple)
  local hero = player:GetAssignedHero()

  for i = 0, hero:GetAbilityCount() - 1 do
    local a = hero:GetAbilityByIndex(i)
    local repr = pp.write(debugAbility(a, simple))
    print(i, repr)
  end
end

--- Invokes ability by name.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string ability Ability name
function GameMode:CommandInvokeAbility(player, ability)
  if not ability then
    return
  end

  local hero = player:GetAssignedHero()
  local invoker = Invoker(hero)

  invoker:Invoke(ability)
end

--- Dumps combo graph in DOT format.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string id Combo id
function GameMode:CommandDumpComboGraph(_player, id)
  if not id then
    return
  end

  local combo = self.combos:createCombo(tonumber(id))

  if combo == nil then
    print("Could not find combo")
    return
  end

  print(combo:todot())
end

--- Changes music status.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string status Music status
-- @tparam string intensity Music intensity
function GameMode:CommandChangeMusicStatus(player, status, intensity)
  if not status or not intensity then
    return
  end

  status = tonumber(status)
  intensity = tonumber(intensity)

  player:SetMusicStatus(status, intensity)
end

--- Dumps Invoker ability specials values.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam[opt] string onlyScaling Dump only values that scale, ignoring fixed values
function GameMode:CommandDumpSpecials(player, onlyScaling)
  local cmd = require("invokation.game_mode.commands.dump_specials")
  cmd.dump(player, { onlyScaling = onlyScaling ~= nil })
end

--- Debug operations on ability specials KeyValues.
--
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string op Operation name (dump, findKeys, findValues)
-- @tparam string query Operation query (dump: path, findKeys: pattern, findValues: pattern)
function GameMode:CommandDebugSpecials(_player, op, query)
  local cmd = require("invokation.game_mode.commands.debug_specials")

  if op == "dump" then
    cmd.dump(query)
  elseif op == "findKeys" then
    cmd.findKeys(query)
  elseif op == "findValues" then
    cmd.findValues(query)
  end
end

--- Reinserts an ability into the current hero.
-- @tparam CDOTAPlayer player Player who issued this console command
-- @tparam string name Ability name
function GameMode:CommandReinsertAbility(player, name)
  if not name then
    return
  end

  local hero = player:GetAssignedHero()
  local ability = hero:FindAbilityByName(name)
  local index = ability:GetAbilityIndex()
  local level = ability:GetLevel()

  hero:RemoveAbility(name)

  ability = hero:AddAbility(name)
  ability:SetAbilityIndex(index)
  ability:SetLevel(level)
end
