local re = re
local sdk = sdk
local d2d = d2d
local imgui = imgui
local log = log
local json = json
local draw = draw
local thread = thread
local require = require
local tostring = tostring
local pairs = pairs
local ipairs = ipairs
local math = math
local string = string
local table = table
local type = type
local _ = _

local Core = require("_CatLib")
local CONST = require("_CatLib.const")
local Imgui = require("_CatLib.imgui")
local FSM = require("_CatLib.fsm")

local mod = Core.NewMod("Smart Focus Mode and Focus Attack")

if mod.Config.IsInBattleCheck == nil then
    mod.Config.IsInBattleCheck = true
end

if mod.Config.MeleeWeaponDistanceCheck == nil then
    mod.Config.MeleeWeaponDistanceCheck = true
end

if mod.Config.ValidScarsCheck == nil then
    mod.Config.ValidScarsCheck = true
end

if mod.Config.DisableAimModeOnWpOff == nil then
    mod.Config.DisableAimModeOnWpOff = true
end

if mod.Config.DisableAimModeOnMount == nil then
    mod.Config.DisableAimModeOnMount = true
end

if mod.Config.DisableAimModeOnDismount == nil then
    mod.Config.DisableAimModeOnDismount = true
end

if mod.Config.DisableAimModeOnCallPorter == nil then
    mod.Config.DisableAimModeOnCallPorter = true
end

local function IsInBattle()
    if mod.Config.IsInBattleCheck then
        return Core.IsInBattle()
    end
    return true
end

local function IsInMeleeDistance()
    if mod.Config.MeleeWeaponDistanceCheck then
        
    end
    return true
end

local TargetAccessKey_GetEnemyCharacter = Core.TypeMethod("app.TargetAccessKeyUtil", "getEnemyCharacter(app.TARGET_ACCESS_KEY, System.Boolean)")

local EffectCache = {}

---@return app.cEnemyLoopEffectHighlight
local function GetEnemyHighlightEffect(enemy)
    local array = enemy._MiniComponentHolder._EntrustEffect_LateUpdator._EffectArray

    Core.ForEach(array, function (effect)
        if effect:get_type_definition():get_full_name() == "app.cEnemyLoopEffectHighlight" then
            EffectCache[enemy] = effect
            return Core.ForEachBreak
        end
    end)

    return EffectCache[enemy]
end

---@param effect app.cEnemyLoopEffectHighlight
local function HighlightEffectHasRawScarOrWeakPoint(effect)
    if not effect then
        return true
    end

    local hasWeakPoint = false
    local parts = effect._Accessor:get_ContextEm().Parts
    if parts then
        hasWeakPoint = parts:getActiveWeakPointIndexList():get_Count() > 0
    end
    if hasWeakPoint then
        return true
    end

    local has = false
    Core.ForEach(effect._ScarList, function (scarHolder)
        if scarHolder._Parts._State == 2 then
            has = true
            return Core.ForEachBreak
        end
    end)
    return has
end

local IsBossCache = {}

local function GetFightingEnemiesHasRawScarOrWeakPoint()
    local player = Core.GetPlayerCharacter()
    local combatEnemies = player:get_CombatEnemyList()

    local has = false
    Core.ForEach(combatEnemies, function (accessKey)
        local enemy = TargetAccessKey_GetEnemyCharacter:call(nil, accessKey, true)

        if IsBossCache[enemy] == nil then
            IsBossCache[enemy] = enemy._Context._Em:get_IsBoss()
        end
        if IsBossCache[enemy] then
            has = HighlightEffectHasRawScarOrWeakPoint(GetEnemyHighlightEffect(enemy))
            -- Core.SendMessage("Enemy %s has? %s", tostring(enemy), tostring(has))
            if has then
                return Core.ForEachBreak
            end            
        end
    end)

    return has
    -- return sdk.get_managed_singleton("app.MissionManager"):getAcceptQuestTargetBrowsers():call("get_Item", 0)._Character
end

local function HasValidScars()
    if mod.Config.ValidScarsCheck then
        return GetFightingEnemiesHasRawScarOrWeakPoint()
    end
    return true
end

local function IsSmartSheath()
    -- 如果近距离且有伤口，那么即使 not in battle 也不能收刀，因为可以偷袭
    -- if not IsInBattle() then
    --     Core.SendMessage("not in battle, sheath")
    --     return true
    -- end
    if not Core.IsInBattle() then
        return false -- 在实现距离检测前，脱战状态下要允许攻击
    end
    if not IsInMeleeDistance() then
        -- Core.SendMessage("too far, sheath")
        return true
    end
    if not HasValidScars() then
        -- Core.SendMessage("no valid scars, sheath")
        return true
    end
    return false
end

local function SmartFocusAttack()
    -- if true then
    --     return true
    -- end
    if IsInBattle() and HasValidScars() and IsInMeleeDistance() then
        return true
    end
    return false
end

local Wp03_ActionToIndexCancelIndexTable = Core.TypeField("app.motion_track.Wp03Cancel", "ActionToCancelIndexTable_Wp03"):get_data()
local Wp03_WeakHitStart = Wp03_ActionToIndexCancelIndexTable:get_Item(29)

-- TODO: 只能在玩家是主机的情况下使用
-- TODO: 非战斗
-- TODO：近战距离检测

local EnemyDistanceLimit = 50
local CheckEnemyHasTearScars = true
local CheckInBattle = true

local function IsAiming()
    return sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():get_IsAim()
end

local function IsAim(command)
    return FSM.CheckCommandResult(command, 0, 61)
end

local function ThisArgIsType(args, typename)
    local typedef = Core.Cast(args[2]):get_type_definition()
   return typedef:get_full_name() == typename or typedef:is_a(typename)
end

Core.HookFunc("app.WpCommonActions.cWpOff", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnWpOff and ThisArgIsType(args, "app.WpCommonActions.cWpOff") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.WpCommonActions.cWpMoveOff", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnWpOff and ThisArgIsType(args, "app.WpCommonActions.cWpMoveOff") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.PlayerCommonAction.cPorterRideStartJumpOnto", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStartJumpOnto") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.PlayerCommonAction.cPorterRideStart", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStart") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.PlayerCommonSubAction.cPorterRideStart", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnMount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterRideStart") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.PlayerCommonAction.cPorterDismount", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnDismount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterDismount") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.PlayerCommonAction.cPorterDismountJumpOff", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnDismount and ThisArgIsType(args, "app.PlayerCommonAction.cPorterDismountJumpOff") then
        Core.DisableAimMode()
    end
end)

Core.HookFunc("app.WpCommonSubAction.cCallPorter", "doEnter()",
function (args)
    -- sdk.get_managed_singleton("app.PlayerManager"):getMasterPlayer():get_ContextHolder():get_Hunter():set_AimType(0)
    if mod.Config.DisableAimModeOnCallPorter and ThisArgIsType(args, "app.WpCommonSubAction.cCallPorter") then
        Core.DisableAimMode()
    end
end)

-- AimMode: Convert focus attack to Sheath weapon
local function FsmSmartSheathPreHook(command, operator)
-- local function SmartSheathPreHook(args)
--     local command = Core.Cast(args[3])
--     local operator = Core.Cast(args[4])
    -- local smart = SmartSheath()
    -- local input = FSM.InputCancelMotion(command, "AIM_SP_B", Wp03_WeakHitStart)
    -- if smart ~= true or input ~= false then
    --     Core.SendMessage("%s/%s", tostring(smart), tostring(input))
    -- end
    if IsSmartSheath() and FSM.InputCancelMotion(command, "AIM_SP_B", Wp03_WeakHitStart) then
        local actionID = Core.NewActionID(1, 10)
        Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false)

        local storage = thread.get_hook_storage()
        storage["retval"] = true

        return sdk.PreHookResult.SKIP_ORIGINAL
    end
end

-- Non-AimMode: Convert Sheath weapon to focus attack
local function Wp03SmartFocusAttackPreHook(command, operator)
    -- local smart = SmartFocusAttack() or true
    -- local input = FSM.InputCancelMotion(command, "ATTACK_SP_B", Wp03_WeakHitStart)
    -- if smart ~= true or input ~= false then
    --     Core.SendMessage("%s/%s", tostring(smart), tostring(input))
    -- end

    -- fixme: 自由态无法使出，应该是是因为 WpOff 的优先级太高了
    if SmartFocusAttack() and FSM.InputCancelMotion(command, "ATTACK_SP_B", Wp03_WeakHitStart) then
        local actionID = Core.NewActionID(2, 33)
        Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false)

        local storage = thread.get_hook_storage()
        storage["retval"] = true

        return sdk.PreHookResult.SKIP_ORIGINAL
    end
end

local function SmartPreHook(args)
    local command = Core.Cast(args[3])
    local operator = Core.Cast(args[4])
    
    -- local aiming = IsAim(command)
    -- local aiming = IsAiming()
    
    return FsmSmartSheathPreHook(command, operator)
    -- return Wp03SmartFocusAttackPreHook(command, operator)
    -- if aiming then
    --     return SmartSheathPreHook(command, operator)
    -- else
    --     return Wp03SmartFocusAttackPreHook(command, operator)
    -- end
end

-- 不知道 Dodge to Step 怎么触发
-- table_e573803e_7de4_47fc_873f_afc17ee07e2b

-- -- 这个函数是翻滚后
-- mod.HookFunc("app.Wp03_Pack_Export", "table_3f30fad0_6b1c_46a2_8348_76ce7f92bd5b(ace.btable.cCommandWork, ace.btable.cOperatorWork)",
-- SmartPreHook, FSM.AbortFSMPostHook)

-- -- 这个函数是AimIdle/Move/MoveStop用的，不是翻滚后
-- mod.HookFunc("app.Wp03_Pack_Export", "table_d71beb87_52ae_4a57_9710_3f9dc7b7689f(ace.btable.cCommandWork, ace.btable.cOperatorWork)",
-- SmartPreHook, FSM.AbortFSMPostHook)

-- -- 这个函数是连携态用的，不是自由态
-- -- Wp03_Export
-- mod.HookFunc("app.Wp03_Pack_Export", "table_a2347168_a7e6_4755_8c7c_2900945dd51f(ace.btable.cCommandWork, ace.btable.cOperatorWork)",
-- SmartPreHook, FSM.AbortFSMPostHook)

local function ActionSmartSheathPreHook(args)
    if IsSmartSheath() then
        local wpOffActionID = 10

        local this = Core.Cast(args[2])
        if this.get_ActionArg then
            local arg = this:get_ActionArg()
            if arg:get_IsEnableStartInputWorldDir() then
                wpOffActionID = 11
            end
        end

        local actionID = Core.NewActionID(1, wpOffActionID)
        Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false)

        local storage = thread.get_hook_storage()
        storage["retval"] = true

        -- 不能 SKIP_ORIGINAL，自由态（非连携态）按住方向键会导致收刀后立刻转为 AimWalk，然后就收刀失败，可以再按集中键，导致抽搐
        -- return sdk.PreHookResult.SKIP_ORIGINAL
    end
end


-- todo: should check character is MasterPlayer
local function HookActionEnter(typename, func)
    mod.HookFunc(typename, "doEnter()", function (args)
        local this = Core.Cast(args[2])
        if not this:get_Chara():get_IsMaster() then
            return
        end
        if this:get_type_definition():get_full_name() ~= typename then
            return
        end
        if func == nil then
            return ActionSmartSheathPreHook(args)
        else
            return func(args)
        end
    end)
end

HookActionEnter("app.Wp00Action.cPenetrateSlashHigh")
HookActionEnter("app.Wp01Action.cAimComboStart")
HookActionEnter("app.Wp02Action.cAimAttack")
HookActionEnter("app.Wp03Action.cWeakHitStart")
HookActionEnter("app.Wp04Action.cAimSwing")
HookActionEnter("app.Wp05Action.cAimStub")
HookActionEnter("app.Wp06Action.cAimAttack")
HookActionEnter("app.Wp07Action.cAimStab")
HookActionEnter("app.Wp08Action.cAxeAimSlashStart")
HookActionEnter("app.Wp08Action.cSwordAimSlashStart")
HookActionEnter("app.Wp09Action.cSwordAimSlash")
HookActionEnter("app.Wp09Action.cAxeAimSlash")

HookActionEnter("app.Wp11Action.cMultiLockonStart")
HookActionEnter("app.Wp12Action.cWeakPointSnipeStart")
HookActionEnter("app.Wp13SubAction.cWeakPointShootChargeStart")

mod.HookFunc("app.Wp10Action.cAimAttack", "doEnter()", function (args)
    local this = Core.Cast(args[2])
    if not this:get_Chara():get_IsMaster() then
        return
    end

    local isAir = this._IsAir

    if IsSmartSheath() then
        if isAir then
            -- 空中不收刀
            return sdk.PreHookResult.SKIP_ORIGINAL
        end
        
        local actionID = Core.NewActionID(1, 10)
        Core.GetPlayerCharacter():call("changeActionRequest(app.AppActionDef.LAYER, ace.ACTION_ID, System.Boolean)", 0, actionID, false)
    end
end)


mod.Menu(function ()
    local changed, configChanged = false, false

    Imgui.Rect(function ()
        imgui.text("Focus Attack Condition Checks:")
        imgui.text("If any condition is not met, the focus attack will be disabled and the weapon sheathed instead.")
    
        -- changed, mod.Config.IsInBattleCheck = imgui.checkbox("In Battle", mod.Config.IsInBattleCheck)
        -- configChanged = configChanged or changed
    
        changed, mod.Config.ValidScarsCheck = imgui.checkbox("Has valid scars or weak point", mod.Config.ValidScarsCheck)
        configChanged = configChanged or changed
    end)

    imgui.text("")

    Imgui.Rect(function ()
        imgui.text("Auto disable Focus Mode when:")
        
        changed, mod.Config.DisableAimModeOnWpOff = imgui.checkbox("WpOff##DisableAimMode", mod.Config.DisableAimModeOnWpOff)
        configChanged = configChanged or changed

        changed, mod.Config.DisableAimModeOnMount = imgui.checkbox("Mount Seikret##DisableAimMode", mod.Config.DisableAimModeOnMount)
        configChanged = configChanged or changed

        changed, mod.Config.DisableAimModeOnDismount = imgui.checkbox("Dismount Seikret##DisableAimMode", mod.Config.DisableAimModeOnDismount)
        configChanged = configChanged or changed

        changed, mod.Config.DisableAimModeOnCallPorter = imgui.checkbox("Calling Seikret##DisableAimMode", mod.Config.DisableAimModeOnCallPorter)
        configChanged = configChanged or changed
    end)

    return configChanged
end)